rawhat / glisten

A pure gleam TCP library
Apache License 2.0
67 stars 12 forks source link

User Messages don't seem to work and there is no doc to how to use it correctly #23

Open brandonpollack23 opened 1 month ago

brandonpollack23 commented 1 month ago
import gleam/bytes_builder
import gleam/erlang/process
import gleam/int
import gleam/io
import gleam/iterator
import gleam/option.{None, Some}
import gleam/otp/actor
import gleam/otp/task
import glisten

pub const clrf = <<"\r\n":utf8>>

type MyMessage {
  Integer(i: Int)
  String(s: String)
}

pub fn main() {
  io.println("Logs from your program will appear here!")

  let int_subject = process.new_subject()
  let string_subject = process.new_subject()
  let selector =
    process.new_selector()
    |> process.selecting(int_subject, Integer)
    |> process.selecting(string_subject, String)

  let assert Ok(subject) =
    glisten.handler(fn(_conn) { #(Nil, Some(selector)) }, fn(msg, state, conn) {
      case msg {
        glisten.Packet(_) -> io.debug("Received a packet")
        glisten.User(Integer(some_int)) ->
          io.debug("Received a user message int: " <> int.to_string(some_int))
        glisten.User(String(s)) ->
          io.debug("Received a user message string: " <> s)
      }

      let assert Ok(_) =
        bytes_builder.new()
        |> bytes_builder.append(<<"HTTP/1.1 200 OK":utf8, clrf:bits>>)
        |> bytes_builder.append(clrf)
        |> glisten.send(conn, _)

      actor.continue(state)
    })
    |> glisten.serve(4221)

  task.async(fn() {
    process.sleep(1000)
    process.send(int_subject, 42)
    process.send(string_subject, "Hello, world!")
  })

  iterator.repeatedly(fn() {
    case process.select(selector, 5000) {
      Ok(Integer(some_int)) ->
        io.debug("Received a user message int: " <> int.to_string(some_int))
      Ok(String(s)) -> io.debug("Received a user message string: " <> s)
      Error(_) -> io.debug("ERROR")
    }
  })
  |> iterator.run

  process.sleep_forever()
}

These messages are (naturally since the subject is created on the initial process) not sent to the handler. How can we achieve this?

rawhat commented 1 month ago

This question has come up a few times. The approach you'd generally need to take is: create the Subject in your on_init, send it to whatever you want to be able to communicate with it, and then send to it.

The complication comes from the fact that each connection is its own handler.

It's a little difficult to come up with generalized examples because it sort of depends on what you're trying to accomplish. The most common thing I've seen questions about is some sort of pub-sub setup. In that case, you'd likely want to use some type of "registry" process. You would "register" your handler when a connection is opened and send over its Subject. And then that would know how to delegate sending messages to your handler.

If you're just messing around with things with your example, and you know you'd only ever have one connection, you have a few options.

1) You don't explicitly need the serve setup if that's the case, as that spins up multiple "acceptor" processes. You could drop down to the lower-level functions to listen/accept yourself and then do something similar to (2).

2) A similar setup to what you have that should work (I have not explicitly tested this) is the following:

let parent = process.new_subject()
let get_handler = process.new_selector() |> process.selecting(parent, fn(subj) { subj })

glisten.handler(fn(_conn) {
  let receiver = process.new_subject()
  process.send(parent, receiver)
  let user_selector = process.new_selector() |> process.selecting(receiver, fn(subj) { subj })
  #(Nil, Some(user_selector))
}, fn(msg, state, conn) {
  case msg {
    User(Integer(..)) -> ...
    ...
  }
})
|> glisten.serve(...)

let handler = process.select(get_handler, 1_000)
process.send(handler, Integer(...))
process.send(handler, String(...))

In the code example above in a more robust setup would be the "registry" I mentioned above. That is a bit more involved, so I will leave that out for now.

Hope this helps! Let me know if you have any other questions.