Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

discussion: implementing phoenix like websocket channels #75

Open
tcoopman opened this issue Jun 21, 2021 · 7 comments
Open

discussion: implementing phoenix like websocket channels #75

tcoopman opened this issue Jun 21, 2021 · 7 comments

Comments

@tcoopman
Copy link
Contributor

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
@aantron
Copy link
Owner

aantron commented Jun 21, 2021

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.

@tcoopman
Copy link
Contributor Author

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?

@aantron
Copy link
Owner

aantron commented Jun 22, 2021

Yes, if you'd like to make an example (that is still about as simple as most examples), I'd merge it :)

@tcoopman
Copy link
Contributor Author

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.

@aantron
Copy link
Owner

aantron commented Jun 25, 2021

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.

@tcoopman
Copy link
Contributor Author

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

@tcoopman
Copy link
Contributor Author

tcoopman commented Jul 8, 2021

I've made my current WIP public: https://github.com/tcoopman/dream-channels

tchibanda24 pushed a commit to tchibanda24/dream that referenced this issue Feb 5, 2025
Add a high-level mirage-compatible module for paf-le
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants