dream
dream copied to clipboard
discussion: implementing phoenix like websocket channels
I'm looking and playing around a bit with dream and I have a hobby project that would definitely benefit from something like channels in dream.
So I created this issue to start some discussion around how to implement this for dream. I haven't thought deeply about this yet, but I was wondering if you could give some input on what a good way of tackling this would be.
Questions I have at the moment:
- What would be the best way of handling runtime state
- I don't see an example of a long running websocket (the examples all send
Dream.close_websocket
) how would that work? - I would need a reference to the
websocket
so that I can broadcast, reply to other people,... Not sure what the best way to do that either
I don't see an example of a long running websocket (the examples all send Dream.close_websocket) how would that work?
You can simply pass the reference that you get to the WebSocket to any other function, store it in data structures, etc. There's no reason why you must close it inside that callback.
I would need a reference to the websocket so that I can broadcast, reply to other people,... Not sure what the best way to do that either
I think the above answers this, as well, but let me know if not.
What would be the best way of handling runtime state
What are the options? You should be able to handle it in any way in OCaml. A simple example would be some kind of mutable map from channel names to WebSocket lists, so (string, Dream.websocket list) Hashtbl.t
. There might be drawbacks to that depending on what you want to implement exactly, but that's at least one way to start off with.
Thanks, so, I'll create a recursive function and a mutable structure (Hashtbl for example), something like this:
module Channel = struct
let joined = Hashtbl.create ...
let join .... = update Hashtbl
end
let rec websocket_listener websocket =
match%lwt Dream.receive websocket with
| Some msg ->
let key = key_from_message msg in
let%lwt () = Channel.join ~key ~websocket in
ws_loop websocket
| _ ->
Dream.close_websocket websocket
tested something similar to this and it seems to work.
Would this be something that would be useful to document in a simple chat example?
Yes, if you'd like to make an example (that is still about as simple as most examples), I'd merge it :)
I've been thinking a bit more about how to model this, feedback is welcome:
type topic =
| Topic of string
| WithSubtopic of (string * string)
type payload = Payload of string
type answers = answer list
and answer =
| Reply of string
| Broadcast of string
| Stop of string
type 'a channel =
{ handle_join : topic -> payload -> 'a * answers
; handle_message : 'a -> payload -> 'a * answers
}
val channels : (string * 'a channel) list -> Dream.websocket -> unit Lwt.t
The idea is that you can do something like this:
Dream.websocket
@@ Socket.channels
[ ( "public_chat"
, { handle_join = (fun topic payload -> (initial state, [ replies ]))
; handle_message = (fun state payload -> (new state, [ replies ]))
} )
; ( "private_chat:123"
, { handle_join = (fun topic payload -> (initial state, [ replies ]))
; handle_message = (fun state payload -> (new state, [ replies ]))
} )
] )
This is a first brain dump, so it's not complete and it has at least one big flaw at the moment. A type 'a channel
is no good, each channel can have a different type. I'll need an other structure for that.
The use of channels
will force all the channels in one call to Socket.channels
to have the same type parameter, so this might be more reasonable than I think you are suggesting :)
EDIT: that is, your concern about the type parameter may be unfounded, and it's actually fine to have it... if I have not misunderstood.
My original idea was that every Channel could have it's own state (and thus also decide on how the state would look like).
So for example a topic chat:123
would have some state and product:xxx
would have some other.
Although maybe that's not really a requirement that I have at the moment...
I'm first going to try to play with this a bit and focus on the answer. The idea is that you can reply: Broadcast hi
and it's a message that goes to everyone in the channel chat:123
I've made my current WIP public: https://github.com/tcoopman/dream-channels