I'm using the Sente library for communicating over websockets. The communication is already working but the session seem to be missing. I'm setting up Sente like this:
;; According to https://github.com/ptaoussanis/sente
(let [{:keys [ch-recv send-fn connected-uids
ajax-post-fn ajax-get-or-ws-handshake-fn]}
(sente/make-channel-socket! (get-sch-adapter) {})]
(def ring-ajax-post ajax-post-fn)
(def ring-ajax-get-or-ws-handshake ajax-get-or-ws-handshake-fn)
(def ch-chsk ch-recv) ; ChannelSocket's receive channel
(def chsk-send! send-fn) ; ChannelSocket's send API fn
(def connected-uids connected-uids)) ; Watchable, read-only atom
(defmulti handle-message
"Multimethod to handle messages coming from the clients"
:id)
(defmethod handle-message
:default
[{:as ev-msg :keys [event id ?data ring-req ?reply-fn send-fn]}]
(let [session (:session ring-req)
uid (:uid session)]
(printf "Ring request: %s\n" ring-req)
(printf "Session: %s\n" session)
(printf "UID: %s\n" uid)
(printf "Unhandled event: %s\n" event)
(printf "Thread id: %s\n" (.getId (Thread/currentThread)))
(flush)
(when ?reply-fn
(?reply-fn {:umatched-event-as-echoed-from-from-server event}))))
(defmethod handle-message :chsk/ws-ping [arg]
(println "ping")) ; Ignore pings
(mount/defstate ^{:on-reload :noop}
socket-server
:start (sente/start-server-chsk-router!
ch-chsk
(fn [arg]
(handle-message arg)
(println "\n"))
#_{:simple-auto-threading? true})
:stop (socket-server))
When I send a message from the client, which is authenticated, I get this output:
Ring request: {:identity nil, :cookies {"ring-session" {:value "8df3a322-313a-4f16-873c-9ea7275af723"}}, :remote-addr "0:0:0:0:0:0:0:1", :params {:client-id "7a2d8913-ad69-4c82-83ed-30ffb1e3c50a"}, :flash nil, :route-params {}, :headers {"origin" "file://", "host" "localhost:3000", "upgrade" "websocket", "user-agent" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) projectx/0.1.0 Chrome/52.0.2743.82 Electron/1.3.3 Safari/537.36", "cookie" "ring-session=8df3a322-313a-4f16-873c-9ea7275af723", "connection" "Upgrade", "pragma" "no-cache", "sec-websocket-key" "g9lEvc59vyaE6RLbM36iyQ==", "accept-language" "en-US", "sec-websocket-version" "13", "accept-encoding" "gzip, deflate", "sec-websocket-extensions" "permessage-deflate; client_max_window_bits", "cache-control" "no-cache"}, :async-channel #object[org.httpkit.server.AsyncChannel 0x8958245 "/0:0:0:0:0:0:0:1:3000<->/0:0:0:0:0:0:0:1:64374"], :server-port 3000, :content-length 0, :form-params {}, :compojure/route [:get "/web-socket"], :websocket? true, :session/key nil, :query-params {"client-id" "7a2d8913-ad69-4c82-83ed-30ffb1e3c50a"}, :content-type nil, :character-encoding "utf8", :uri "/web-socket", :server-name "localhost", :query-string "client-id=7a2d8913-ad69-4c82-83ed-30ffb1e3c50a", :body nil, :multipart-params {}, :scheme :http, :request-method :get, :session {}}
Session: {}
UID: null
Unhandled event: [:projectx.core/boo "boo!"]
Thread id: 38
The handlers are quite simple and are set up like this:
(def app-routes
(routes
(GET "/web-socket" req (socket/ring-ajax-get-or-ws-handshake req))
(POST "/web-socket" req (socket/ring-ajax-post req))
#'service-routes
(route/not-found
"page not found")))
(defn app [] (middleware/wrap-base #'app-routes))
middleware/wrap-base contains the session handling and it looks like this:
(defn wrap-identity [handler]
(fn [request]
(println (:cookies request))
(if-let [current-user-id (get-in request [:session :identity])]
(if-let [current-user (when current-user-id (db/get-user-by-id {:id current-user-id}))]
(handler (assoc request :current-user current-user))
(handler (-> request
(dissoc :identity)
(dissoc-in [:session :identity])))))
(handler request))))
(defn wrap-auth [handler]
(let [backend (session-backend)]
(-> handler
wrap-identity
(wrap-authentication backend)
(wrap-authorization backend))))
(defn wrap-base [handler]
(-> ((:middleware defaults) handler)
wrap-auth
(wrap-defaults
(-> site-defaults
(assoc-in [:params :keywordize] true) ; Needed by Sente: https://github.com/ptaoussanis/sente#on-the-server-clojure-side
(assoc-in [:params :urlencoded] true) ; Needed by Sente: https://github.com/ptaoussanis/sente#on-the-server-clojure-side
(assoc-in [:security :anti-forgery] false)
(assoc-in [:session :store] (ttl-memory-store (* 60 30)))))))
This is working just fine for the AJAX requests. I even tried setting :uid in the session as well as starting the channel in the client after the user logged in, and nothing changed the fact that the session remained empty.
The problem was on the client side. The connection has to be established after the user logged in for that session to appear in the socket. I was following the code example that looks like this:
with the misunderstanding that calling start-router was going to establish the communication but instead it's established as soon as make-channel-socket! is called; so, it shouldn't be called until the session is ready.