A lightweight Clojure/ClojureScript library designed to bridge the gap between the triad of CLJ/CLJS, web-sockets and core.async.
Include the following in your project.clj
:
[jarohen/chord "0.3.1"]
There is an simple example server/client project under the
example-project
directory. The client sends websocket messages to
the server, that get echoed back to the client and written on the
page.
You can run it with lein dev
- an alias that starts up an http-kit
server using frodo and automatically re-compiles the CLJS.
Chord only has one function, chord.client/ws-ch
, which takes a
web-socket URL and returns a channel. When the connection opens
successfully, this channel then returns a two-way channel that you can
use to communicate with the web-socket server:
(:require [chord.client :refer [ws-ch]]
[cljs.core.async :refer [<! >! put! close!]])
(:require-macros [cljs.core.async.macros :refer [go]])
(go
(let [ws (<! (ws-ch "ws://localhost:3000/ws"))]
(>! ws "Hello server from client!")))
Messages that come from the server are received as a map with a
:message
key:
(go
(let [ws (<! (ws-ch "ws://localhost:3000/ws"))]
(js/console.log "Got message from server:" (pr-str (:message (<! ws))))))
Errors in the web-socket channel are returned as a map with an
:error
key:
(go
(let [ws (<! (ws-ch "ws://localhost:3000/ws"))
{:keys [message error]} (<! ws)]
(if error
(js/console.log "Uh oh:" error)
(js/console.log "Hooray! Message:" (pr-str message)))))
As of 0.3.0, you can pass a :format
option, to pass messages over
the channel as EDN (default), as raw strings, or JSON (0.3.1). Valid
formats are #{:edn :json :json-kw :str}
, defaulting to :edn
.
(:require [cljs.core.async :as a])
(ws-ch "ws://localhost:3000/ws"
{:format :json-kw})
As of 0.2.1, you can configure the buffering of the channel by (optionally) passing custom read/write channels, as follows:
(:require [cljs.core.async :as a])
(ws-ch "ws://localhost:3000/ws"
{:read-ch (a/chan (a/sliding-buffer 10))
:write-ch (a/chan 5)})
By default, Chord uses unbuffered channels, like core.async itself.
Chord wraps the websocket support provided by http-kit, a fast Clojure web server compatible with Ring.
Again, there’s only one entry point to remember here: a wrapper around
http-kit’s with-channel
macro. The only difference is that, rather
than using http-kit’s functions to interface with the channel, you can
use core.async’s primitives.
Chord’s with-channel
is used as follows:
(:require [chord.http-kit :refer [with-channel]]
[clojure.core.async :refer [<! >! put! close! go]])
(defn your-handler [req]
(with-channel req ws-ch
(go
(let [{:keys [message]} (<! ws-ch)]
(prn "Message received:" message)
(>! ws-ch "Hello client from server!")
(close! ws-ch)))))
This can take a :format
option, and custom buffered read/write
channels as well:
(require '[clojure.core.async :as a])
(defn your-handler [req]
(with-channel req ws-ch
{:read-ch (a/chan (a/dropping-buffer 10))
:format :str} ; again, :edn is default
(go
(let [{:keys [message]} (<! ws-ch)]
(prn "Message received:" message)
(>! ws-ch "Hello client from server!")
(close! ws-ch)))))
You can also use the wrap-websocket-handler
middleware, which will
put a :ws-channel
key in the request map:
(require '[chord.http-kit :refer [wrap-websocket-handler]]
'[clojure.core.async :as a])
(defn your-handler [{:keys [ws-channel] :as req}]
(go
(let [{:keys [message]} (<! ws-channel)]
(println "Message received:" message)
(>! ws-channel "Hello client from server!")
(close! ws-channel))))
(start-server (-> #'your-handler wrap-websocket-handler) {:port 3000})
You can pass custom channels to wrap-websocket-handler
as a second
(optional) parameter:
(start-server (-> #'your-handler
(wrap-websocket-handler {:read-ch ...}))
{:port 3000})
Yes please! Please submit these in the traditional GitHub manner.
Thanks to Thomas Omans (eggsby) for (unknowingly!) providing the idea of how to combine two core.async channels together! https://gist.github.com/eggsby/6102537
Copyright © 2013 James Henderson
Distributed under the Eclipse Public License, the same as Clojure.