Chord
A lightweight Clojure/ClojureScript library designed to bridge the gap between the triad of CLJ/CLJS, web-sockets and core.async.
Usage
Include the following in your project.clj
:
[jarohen/chord "0.2.2"]
Example project
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.
ClojureScript
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:" (: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:" message))))
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.
Clojure
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)]
(println "Message received:" message)
(>! ws-ch "Hello client from server!")
(close! ws-ch)))))
This can take 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))}
(go
(let [{:keys [message]} (<! ws-ch)]
(println "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})
Bug reports/pull requests/comments/suggestions etc?
Yes please! Please submit these in the traditional GitHub manner.
Thanks
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
Changes
0.2.2
No breaking changes. Adding in wrap-websocket-handler
to provide an
macro-less alternative to with-channel
.
Thanks to Malcolm Sparks for the tip!
0.2.1
No breaking changes. Added ability to pass custom buffered channels to use instead of the default unbuffered channels.
Thanks to Timo Sulg (timgluz) for the PR!
0.2.0
Breaking change - CLJS namespace now chord.client
due to recent
versions of the CLJS compiler not liking single-segment namespaces
Thanks to Joshua Griffith (hadronzoo) for the PR!
0.1.1
No breaking changes - added adapter around http-kit for Clojure support.
0.1.0
Initial release.
License
Copyright © 2013 James Henderson
Distributed under the Eclipse Public License, the same as Clojure.