Help Getting Sente to Work

979 views
Skip to first unread message

Timothy Washington

unread,
Jul 26, 2014, 12:34:31 AM7/26/14
to clo...@googlegroups.com
Hi all, 

I'm using [com.taoensso/sente "0.15.1"], and having trouble connecting the client to the server. I'm sure it's something simple, but not obvious, as this is taken directly from the examples on sente's github page. Anyone seen and fixed the error in fig.1? The clojurescript and clojure code are in fig 2 and 3 respectively. 


WebSocket connection to 'ws://172.28.128.5:58269/chsk' failed: Error during WebSocket handshake: Unexpected response code: 404
WebSocket error: [object Event] VM1707:1689
Chsk is closed: will try reconnect (8). VM1707:1689

fig.1 - Chrome network error message 

(ns my.ns
  (:require-macros
   [cljs.core.match.macros :refer (match)] ; Optional, useful
   [cljs.core.async.macros :as asyncm :refer (go go-loop)])
  (:require
   ;;[clojure.browser.repl :as repl]
   [cljs.core.match] ; Optional, useful
   [cljs.core.async :as async :refer (<! >! put! chan)]
   [taoensso.sente :as sente :refer (cb-success?)]))


(let [{:keys [chsk ch-recv send-fn state]}
      (sente/make-channel-socket! "/chsk" {:type :auto})]

  (def chsk       chsk)
  (def ch-chsk    ch-recv) 
  (def chsk-send! send-fn) 
  (def chsk-state state))

(defn one []
  (chsk-send! [:some/request-id {:name "Rich Hickey" :type "Awesome"}]))

fig.2 - my.cljs 


(ns my.http.ns
  (:require [compojure.core :refer :all]
            ...

            ;; Sente stuff
            [clojure.core.match :as match :refer (match)] ; Optional, useful
            [clojure.core.async :as async :refer (<! <!! >! >!! put! chan go go-loop)]
            [taoensso.sente :as sente]))


(let [{:keys [ch-recv send-fn ajax-post-fn ajax-get-or-ws-handshake-fn]}
      (sente/make-channel-socket! {})]
      
  (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)
  (def chsk-send!                    send-fn))

...

(defn create-approutes [project-config browserrepl]

  (defroutes app-routes

    ;; Sente stuff
    (GET  "/chsk" req (ring-ajax-get-or-ws-handshake req)) ; tried both ring-ajax-get-or-ws-handshake and #'ring-ajax-get-or-ws-handshake
    (POST "/chsk" req (ring-ajax-post                req))        ; same here for trying both 


    (GET "/" []
         (-> (ring-resp/response (with-browser-repl "index.html" browserrepl))
             (ring-resp/content-type "text/html")))

    (route/resources "/" {:root "resources/public/"})
    (route/not-found "Not Found")))

fig.3 - my.clj 


Tim Washington 

Daniel Kersten

unread,
Jul 26, 2014, 1:49:39 AM7/26/14
to clo...@googlegroups.com
My server looks like this and its been working for me for the past few months without issue:


(defroutes ws-routes
  (GET "/cmd/chsk" req (ring-ajax-get-or-ws-handshake req))
  (POST "/cmd/chsk" req (ring-ajax-post req)))





(defn run
  [& [routes]]
  (let [site (http-handler/site (compojure/routes ws-routes routes))
        webserver (httpkit/run-server site {:port 3002})

        {:keys [ch-recv send-fn ajax-post-fn ajax-get-or-ws-handshake-fn
                connected-uids]} (sente/make-channel-socket! {})]

    (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
    (def server webserver)
    (sente/start-chsk-router-loop! sente-message-router ch-chsk)
    webserver))


Where sente-message-router is a function that receives messages from the client.

In my actual codebase, there is a little more additional code for setting up client-id's and CSRF tokens too.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Bob Hutchison

unread,
Jul 26, 2014, 7:50:57 AM7/26/14
to clo...@googlegroups.com

Hi Tim,

I think I went through this when I was first starting with Sente. You need to setup the CSRF handling part, in the example code by including ring-anti-forgery/wrap-anti-forgery in your routes. *AND* you have to make sure that you are using one of those routes before you try to use Sente’s connection stuff, otherwise your session won’t have certain critical attributes set (e.g. __anti-forgery-token). I think the 404s are from the anti-forgery stuff rejecting your request. And I think Sente is assuming it’s setup. This was a while ago so maybe I’m not remembering it quite right.

Cheers,
Bob

Daniel Kersten

unread,
Jul 26, 2014, 8:28:44 AM7/26/14
to clo...@googlegroups.com
Speaking of CSRF, I have a slightly different setup from the Sente examples where I use sente for all my communication after the initial load, whereas the Sente examples use normal HTTP to perform logins and only use Sente after login. This way the login request can set the client-id and CSRF token. In my setup I can't do this because I do all requests over Sente (and my html/js files are served by a static web server, so I can't set it from those requests either). 

What I do to get around this is have a <script src="/init"> before my clojurescript js and the /init route sets up the client-id token and returns "var csrf_token = ...". Then on the client I can pass the token to sente as js/csrf_token.

I left this out in my previous mail because I thought it wasn't relevant and wanted to keep the code simple, but I guess it might be useful after all if you are doing something similar to what I'm doing.

Btw, as far as I can remember, sente has an option to turn CSRF off - at least, I've used it without CSRF. Obviously you should only do that during development :)

Timothy Washington

unread,
Jul 27, 2014, 12:49:45 PM7/27/14
to clo...@googlegroups.com
Ok, got it. 

It was indeed the CSRF issue. I included that middleware to fix the issue. For anyone else struggling with this, There's a sente/example-project with some code that uses the csrf middleware. 

But also in my case, I'm using austin's browser-repl. Now, sente's default behaviour is to use the URL of your connected client. However, my connected repl client is "172.28.128.5:58269", which is not the actual port of the webserver. Http-kit defaults to port 8090, so I needed a URL of "ws://172.28.128.5:8090/chsk". And sente let's you pass in a :chsk-url-fn, letting you specify your own URL. And that's how I got around that problem. Hope that's clear, and that it helps someone. 


  (ns bkell.core
    (:require-macros
     [cljs.core.match.macros :refer (match)] ; Optional, useful
     [cljs.core.async.macros :as asyncm :refer (go go-loop)])
    (:require
     [clojure.browser.repl :as repl]
     [cljs.core.match]
     [cljs.core.async :as async :refer (<! >! put! chan)]
     [taoensso.sente :as sente :refer (cb-success?)]))

  (defn chsk-url-fn [path {:as window-location :keys [protocol host pathname]} websocket?]

  (let [{:keys [chsk ch-recv send-fn state]}
        (sente/make-channel-socket! "/chsk" {:type :auto
                                                             :chsk-url-fn chsk-url-fn})]

    (def chsk       chsk)
    (def ch-chsk    ch-recv) ; ChannelSocket's receive channel
    (def chsk-send! send-fn) ; ChannelSocket's send API fn
    (def chsk-state state))

  (defn one []
    (chsk-send! [:some/request-id {:name "Rich Hickey" :type "Awesome"}]))

  (defn hello []
    (js/alert "Hello World"))



Thanks guys 

Tim Washington 

Peter Taoussanis

unread,
Jul 28, 2014, 2:30:38 AM7/28/14
to clo...@googlegroups.com
Hey Tim, sorry for not responding earlier - didn't notice your post till now (not often on the group).

Glad you found a solution (thanks Dan, Bob!). 

Just to clarify one thing though - a CSRF middleware is definitely recommended, but shouldn't be _necessary_. If Sente detects that no CSRF token is present in the Ring request during handshake, it'll print a warning to the JS console - but it should still continue to function normally after that.

It sounds to me like the issue you were having was with the default `chsk-url-fn`. A misconfigured CSRF middleware would result in a 403 error (forbidden), not a 404 error (not found).

The change you made for Austin looks good, but you will need to make an allowance for cases where the `websocket?` argument is false (i.e. for Ajax connections). You can see the `default-chsk-url-fn` as an example:

(def default-chsk-url-fn
  "`window-location` keys:
    :protocol ; \"http:\" ; Note the :
    :hostname ; \"example.org\"
    :host     ; \"example.org:80\"
    :pathname ; \"/foo/bar\"
    :search   ; \"?q=baz\"
    :hash     ; \"#bang\""
  (fn [path {:as window-location :keys [protocol host pathname]} websocket?]
    (str (if-not websocket? protocol (if (= protocol "https:") "wss:" "ws:"))
         "//" host (or path pathname))))


I've also amended the relevant docstrings to hopefully make all this clearer for folks in future. Feel free to ping me here or on GitHub if I was unclear somewhere or if you've got any follow-up questions.

Cheers! :-)

Peter Taoussanis

unread,
Jul 28, 2014, 2:45:52 AM7/28/14
to clo...@googlegroups.com

Timothy Washington

unread,
Jul 28, 2014, 8:09:26 PM7/28/14
to clo...@googlegroups.com
Hey Peter, 

Thanks for getting back to me. Good explanation here. 

Wrt the CSRF issue, I'll try running again without it. Now, a port changed fixed the issue for me. But before I passed in a chsk-url-fn, I was still getting a 404, even when trying with a browser (ie, using the correct port). 

That 404 went away when I included the CSRF middleware. I still had to pass in a custom chsk-url-fn, but I could use it through the browser, for example. 

And I'm just grokking the false `websocket?` allowance now. Ie, an https ajax connection would give me a sente URL of "wws://172.28.128.5:8090/chsk". Is that right? What about plain http?


Cheers 

Tim Washington 



Peter Taoussanis

unread,
Jul 29, 2014, 12:51:49 AM7/29/14
to clo...@googlegroups.com
Wrt the CSRF issue, I'll try running again without it.

Out of curiosity, sure - but if you've already gone to the effort of setting up the CSRF I'd leave it in (better to have it) :-)
 
Now, a port changed fixed the issue for me. But before I passed in a chsk-url-fn, I was still getting a 404, even when trying with a browser (ie, using the correct port).

Not sure about that, may be that something in your setup is tripping up the default `chsk-url-fn`. Would appreciate a GitHub issue on it and I'll take a closer look - may be a bug.

That 404 went away when I included the CSRF middleware. I still had to pass in a custom chsk-url-fn, but I could use it through the browser, for example. 

My guess is that the 404 is going away because you're passing in a custom `chsk-url-fn`, not because of the CSRF middleware. I.e. it's a broken chsk url that's causing the 404, not anything to do with the CSRF.


And I'm just grokking the false `websocket?` allowance now. Ie, an https ajax connection would give me a sente URL of "wws://172.28.128.5:8090/chsk". Is that right? What about plain http?

The `chsk-url-fn` will be called with:
1. The path as given to the client-side `make-channel-socket!` fn (usu. "/chsk").
2. A window location map of the current page.
3. A bool indicating whether Sente is requesting a WebSocket (true) or Ajax (false) connection.

From these, your fn will need to produce an URL that matches your server-side channel socket route.

For WebSocket connections you'll want the URL to start with "ws://" (insecure) or "wss://" (secure).
For Ajax connections you'll want the URL to start with "http://" (insecure) or "https://" (secure).

I wouldn't stress too much about that though - let's start with a GitHub issue since it's quite possible I can mod the default fn to cover your use-case automatically. Ideally you shouldn't need to fiddle with any of this :-)

Cheers!


Timothy Washington

unread,
Jul 30, 2014, 11:24:17 AM7/30/14
to clo...@googlegroups.com
Hey Peter, 

Responses are inlined. 


On Tue, Jul 29, 2014 at 12:51 AM, Peter Taoussanis <ptaou...@gmail.com> wrote:
Wrt the CSRF issue, I'll try running again without it.

Out of curiosity, sure - but if you've already gone to the effort of setting up the CSRF I'd leave it in (better to have it) :-)
 
Yep, I agree ;) 
 
 
Now, a port changed fixed the issue for me. But before I passed in a chsk-url-fn, I was still getting a 404, even when trying with a browser (ie, using the correct port).

Not sure about that, may be that something in your setup is tripping up the default `chsk-url-fn`. Would appreciate a GitHub issue on it and I'll take a closer look - may be a bug.

Ok, new github issues is here.
 

That 404 went away when I included the CSRF middleware. I still had to pass in a custom chsk-url-fn, but I could use it through the browser, for example. 

My guess is that the 404 is going away because you're passing in a custom `chsk-url-fn`, not because of the CSRF middleware. I.e. it's a broken chsk url that's causing the 404, not anything to do with the CSRF.

Actually, that 404 did go away when I included the CSRF middleware. And that's weird, I'll admit, because the webserver was not running on ws://172.28.128.5:58269/chsk

However, I was still experiencing the broken chsk channel. That's when I included the custom chsk-url-fn. The tricky thing, I think, is that the browser-repl uses a different port (i default browser-repl is 9000, ii. austin's browser-repl port changes each session), than the running http-kit server (8090). So when make-channel-socket! calls chsk-url-fn (which is the default-chsk-url-fn), (encore/get-window-location) is returning the URL with the browser-repl's port. I'm not sure how you'd get around that if you're using a browser-repl, vs just running in a plain browser runtime. Seems to come down to the behaviour of (.-location js/window) (in encore/get-window-location). Ie, in my browser-repl, invoking `(.-location js/window)` gives me "http://172.28.128.5:33283/347/repl/start?...", (not http://172.28.128.5:8090/...). 

cljs.user> (.-location js/window)                                                                                                                                                                                                    

283%2Frobots.txt%22%7D>


 
And I'm just grokking the false `websocket?` allowance now. Ie, an https ajax connection would give me a sente URL of "wws://172.28.128.5:8090/chsk". Is that right? What about plain http?

The `chsk-url-fn` will be called with:
1. The path as given to the client-side `make-channel-socket!` fn (usu. "/chsk").
2. A window location map of the current page.
3. A bool indicating whether Sente is requesting a WebSocket (true) or Ajax (false) connection.

From these, your fn will need to produce an URL that matches your server-side channel socket route.

For WebSocket connections you'll want the URL to start with "ws://" (insecure) or "wss://" (secure).
For Ajax connections you'll want the URL to start with "http://" (insecure) or "https://" (secure).

I wouldn't stress too much about that though - let's start with a GitHub issue since it's quite possible I can mod the default fn to cover your use-case automatically. Ideally you shouldn't need to fiddle with any of this :-)

Cheers!


Nice one - thanks Peter.  


Tim Washington 

Peter Taoussanis

unread,
Jul 31, 2014, 12:35:45 AM7/31/14
to clo...@googlegroups.com
Ok, new github issues is here.

Great, thanks - will follow up there later today! Cheers :-)

Daniel Kersten

unread,
Jul 31, 2014, 4:06:45 AM7/31/14
to clo...@googlegroups.com

Hi Timothy, I just wanted to note that you can control the port Austin uses through environment variables. I do this so I can port forward, for example.

This probably won't help you though, as httpkit and the browser repl can't run on the same port.

Timothy Washington

unread,
Aug 1, 2014, 6:25:40 PM8/1/14
to clo...@googlegroups.com
Hey Daniel, 

Yes, that's true. I've run into that, as I've gotten used to a lot of gymnastics when setting up these tools, lol. So thanks for the heads up. Those friendly reminders are needed :) 


Tim Washington 
Reply all
Reply to author
Forward
0 new messages