User Messages don't seem to work and there is no doc to how to use it correctly
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?
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.
-
You don't explicitly need the
servesetup 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). -
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.
Hi @rawhat! First of all thanks for the lovely library. It's been very fun to use.
I'm facing somewhat of a similar problem as the one described here. I'm working on a simple IRC type chat server for protohackers.
The code for that is here: https://github.com/nineluj/protohackers-gleam/blob/main/budget_chat/src/budget_chat.gleam#L7
For messaging between actors, I tried following the logic in the code above, and reviewed the gleam_erlang/process docs to get an understanding of Subject and Selectors. Unfortunately, I'm unable to properly receive any User(user_message) messages in my loop function.
Main logs:
❯ gleam run
Compiled in 0.02s
Running budget_chat.main
INFO Starting server...
CRIT Waiting to send admin message...
INFO New connection established from 127.0.0.1:38938
DEBG Got name: [alice]
DEBG Accepted name [alice]
INFO New connection established from 127.0.0.1:38948
DEBG Got name: [bob]
DEBG Accepted name [bob]
INFO User [bob] sent message: is anyone home?
CRIT Sending Admin message!
Client 1:
❯ nc localhost 33337
Welcome to budgetchat! What can I call you? Enter name: alice
Name: [alice] was accepted
Client 2:
❯ nc localhost 33337
Welcome to budgetchat! What can I call you? Enter name: bob
Name: [bob] was accepted
is anyone home?
Neither the admin message nor the message from client 2/"bob" get processed in the handler function.
I'd appreciate any help I can get if you have some spare time, and once I figure this out then we could also include this code in the examples/ folder to make it easier for others to use the library if you think that's a good idea.
Many thanks!
Never mind, I got it working using group_registry. I can still put in the chat server code in the examples directory if you want.