jacobobryant / biff

A Clojure web framework for solo developers.
https://biffweb.com
MIT License
829 stars 40 forks source link

How does Biff set up websockets? #216

Closed filipencopav closed 1 month ago

filipencopav commented 1 month ago

I am very confused. I am very new to websockets, so I decided to explore them while also exploring biff. I stumble upon a few pages: 1 and 2 are Biff docs, and 3 and 4.

First, I noticed the following api being used[1]:

   :ws {:on-connect (fn [ws]
                      (swap! chat-clients conj ws))
        :on-text (fn [ws text]
                   (doseq [ws @chat-clients]
                     (jetty/send! ws (rum/render-static-markup
                                       [:div#messages {:hx-swap-oob "beforeend"}
                                        [:p "new message: " text]]))))
        :on-close (fn [ws status-code reason]
                    (swap! chat-clients disj ws))}})

And [2]:

   :ws {:on-connect (fn [ws]
                      (prn :connect (swap! chat-clients update channel-id (fnil conj #{}) ws)))
        :on-close (fn [ws status-code reason]
                    (prn :disconnect
                         (swap! chat-clients
                                (fn [chat-clients]
                                  (let [chat-clients (update chat-clients channel-id disj ws)]
                                    (cond-> chat-clients
                                      (empty? (get chat-clients channel-id)) (dissoc channel-id)))))))}})

Since biff is a ring app, I checked out ring's spec for websockets. It was confusing and I didn't understand it completely, but I was looking for something that resembles the above. So I found something[3]:

(defprotocol Listener
  (on-open    [listener socket])
  (on-message [listener socket message])
  (on-pong    [listener socket data])
  (on-error   [listener socket throwable])
  (on-close   [listener socket code reason]))

Since it is a protocol, I suppose all the symbols above that start with on- mean keys in a map. Or, methods implemented on some class. I don't understand this precise mechanism of the ring spec, but it shouldn't matter for now. However, I see that it has on-message, and not on-text, like the examples from the Biff documentation. So I get confused.

Furthermore,

A websocket response is a map that represents a WebSocket, and may be returned from a handler in place of a response map.

So, in order to respond with a websocket in ring, we must return a map of the form #:ring.websocket{...}. That's how I understand it, at least. So what is the deal with :ws {...} map, that doesn't even contain :on-message?

Then I look at [4], and decide to run jetty/ws-upgrade-response on the aforementioned :ws {...} map. Alas, it returns something that resembles [1] and [2], together with the 101 status and connection upgrade headers:

user> (jetty/ws-upgrade-response {:on-connect (fn [ws]
                                                nil)
                                  :on-text (fn [ws message]
                                             nil)
                                  :on-message (fn [ws message]
                                                nil)
                                  :on-close (fn [ws status-code reason])})
{:status 101,
 :headers {"upgrade" "websocket", "connection" "upgrade"},
 :ws
 {:on-connect #function[user/eval48832/fn--48833],
  :on-text #function[user/eval48832/fn--48835],
  :on-message #function[user/eval48832/fn--48837],
  :on-close #function[user/eval48832/fn--48839]}}

But now I am more confused.

Does biff not follow ring specification, and instead rely on jetty9-specific functionality?

Furthermore, :on-text implies :on-binary necessarily, but in the ring spec [1], both text and binary frames are handled by the on-message function of the websocket listener. So how do I actually use websockets, and, furthermore, where can I get a good documentation on this? If there isn't one yet, or if this api is specific to Biff, will something be written to expand on how this stuff should be used?

Thanks in advance.

jacobobryant commented 1 month ago

Biff websockets don't go through the Ring spec, since the Ring spec/official Ring Jetty adapter didn't support websockets until somewhat recently. Biff uses info.sunng/ring-jetty9-adapter which has had support for websockets for a while. Biff is currently using version 0.17.2 of that library; if you view the repo at that tag you can see its docs for websockets.

Now that the official ring/ring-jetty-adapter lib does support websockets, Biff should probably start using that. I've written up some thoughts here: https://github.com/jacobobryant/biff/issues/217