Skip to content

Commit 37a6744

Browse files
committed
v0.9.0
1 parent 53e594e commit 37a6744

File tree

5 files changed

+65
-128
lines changed

5 files changed

+65
-128
lines changed

CHANGELOG.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
## v0.9.0-SNAPSHOT / unreleased
1+
## v0.9.0 / 2014 Mar 29
22

3-
> This is a **non-breaking* release focused on efficiency+reliability improvements for very high stress environments.
3+
> This is a **non-breaking** release focused on efficiency+reliability improvements for very high stress environments.
44
55
* Documentation improvements.
6-
* CHANGE: server>user Ajax push is now more reliable against dodgy connections.
7-
* NEW: server>user sends are now automatically+transparently batched for greater efficiency in _very_ high throughput environments. The server-side `make-channel-socket!` has picked up some knobs for this, but the defaults are sensible.
6+
* **CHANGE**: server>user Ajax push is now more reliable against dodgy connections.
7+
* **NEW**: server>user sends are now automatically+transparently batched for greater efficiency in _very_ high throughput environments. The server-side `make-channel-socket!` has picked up some knobs for this, but the defaults are sensible.
88

99

1010
## v0.8.2 / 2014 Mar 7
1111

12-
* NEW: Copy improved error messages to server-side API.
13-
* CHANGE: Provide entire, unfiltered Ring request map to server-side API.
12+
* **NEW**: Copy improved error messages to server-side API.
13+
* **CHANGE**: Provide entire, unfiltered Ring request map to server-side API.
1414

1515

1616
## v0.8.1 / 2014 Mar 4
1717

18-
* NEW: Improved error messsages for malformed events.
18+
* **NEW**: Improved error messsages for malformed events.
1919

2020

2121
## v0.8.0 / 2014 Feb 24
2222

23-
* NEW: Initial public release.
23+
* **NEW**: Initial public release.

README.md

Lines changed: 52 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
**[API docs][]** | **[CHANGELOG][]** | [other Clojure libs][] | [Twitter][] | [contact/contributing](#contact--contributing) | current ([semantic][]) version:
22

33
```clojure
4-
[com.taoensso/sente "0.8.2"] ; < v1.0.0 API is subject to change
4+
[com.taoensso/sente "0.9.0"] ; < v1.0.0 API is subject to change
55
```
66

77
# Sente, channel sockets for Clojure
@@ -12,43 +12,42 @@
1212
1313
**Sente** is a small client+server library that makes it easy to build **reliable, high-performance realtime web applications with Clojure**.
1414

15+
Or: **We don't need no [Socket.IO][]**
1516
Or: **The missing piece in Clojure's web application story**
16-
Or: **We don't need no Socket.IO**
1717
Or: **Clojure(Script) + core.async + WebSockets/Ajax = _The Shiz_**
1818

1919
(I'd also recommend checking out James Henderson's [Chord][] and Kevin Lynagh's [jetty7-websockets-async][] as possible alternatives!)
2020

2121
## What's in the box™?
22-
* **Bidirectional a/sync comms** over both **WebSockets** and **Ajax** (auto-selecting).
23-
* **Robust**: auto keep-alives, buffering, mode fallback, reconnects. **It just works™**.
24-
* [edn][] rocks. So **send edn, get edn**: no json here.
22+
* **Bidirectional a/sync comms** over both **WebSockets** and **Ajax** (auto-fallback).
23+
* **Robust**: auto keep-alives, buffering, protocol selection, reconnects. **It just works™**.
24+
* Efficient design incl. transparent event batching for **low-bandwidth use, even over Ajax**.
25+
* Full, **transparent support for [edn][]** over the wire (JSON, XML, and other arbitrary string-encoded formats may be used as edn strings).
2526
* **Tiny, simple API**: `make-channel-socket!` and you're good to go.
2627
* Automatic, sensible support for users connected with **multiple clients** and/or devices simultaneously.
27-
* **Flexible model**: use it anywhere you'd use WebSockets or Ajax.
28-
* Normal **Ring security model**: auth as you like, HTTPS when available, CSRF support, etc.
29-
* **Fully documented, with examples** (more forthcoming).
30-
* Small: **less than 600 lines of code** for the entire client+server implementation.
28+
* **Flexible model**: use it anywhere you'd use WebSockets/Ajax/Socket.IO, etc.
29+
* Standard **Ring security model**: auth as you like, HTTPS when available, CSRF support, etc.
30+
* **Fully documented, with examples**.
31+
* Small: **~600 lines of code** for the entire client+server implementation.
3132
* **Supported servers**: currently only [http-kit][], but easily extended. [PRs welcome](https://github.com/ptaoussanis/sente/issues/2) to add support for additional servers!
3233

3334

3435
### Capabilities
3536

36-
Protocol | client>server | client>server + ack/reply | server>clientS push |
37-
------------------- | ------------- | ------------------------- | ------------------- |
38-
WebSockets | ✓ (native) | ✓ (emulated) | ✓ (native) |
39-
Ajax | ✓ (emulated) | ✓ (native) | ✓ (emulated) |
37+
Protocol | client>server | client>server + ack/reply | server>user push |
38+
------------------- | ------------- | ------------------------- | ---------------- |
39+
WebSockets | ✓ (native) | ✓ (emulated) | ✓ (native) |
40+
Ajax | ✓ (emulated) | ✓ (native) | ✓ (emulated) |
4041

41-
So the underlying protocol's irrelevant. Sente gives you a unified API that exposes the best of both WebSockets (bidirectionality + performance) and Ajax (optional evented ack/reply model).
42+
So you can ignore the underlying protocol and deal directly with Sente's unified API. It's simple, and exposes the best of both WebSockets (bidirectionality + performance) and Ajax (optional evented ack/reply model).
4243

4344

4445
## Getting started
4546

46-
> Note that there's also a full [example project][] in this repo. Call `lein start-dev` in that dir to get a (headless) development repl that you can connect to with [Cider][] (emacs) or your IDE.
47-
4847
Add the necessary dependency to your [Leiningen][] `project.clj`. This'll provide your project with both the client (ClojureScript) + server (Clojure) side library code:
4948

5049
```clojure
51-
[com.taoensso/sente "0.8.2"]
50+
[com.taoensso/sente "0.9.0"]
5251
```
5352

5453
### On the server (Clojure) side
@@ -130,13 +129,13 @@ You're good to go! The client will automatically initiate a WebSocket or repeati
130129

131130
#### Client-side API
132131

133-
* `ch-recv` is a **core.async channel** that'll receive **`event`**s.
134-
* `chsk-send!` is a `(fn [event & [?timeout-ms ?cb-fn]])`.
132+
* `ch-recv` is a **core.async channel** that'll receive `event`s.
133+
* `chsk-send!` is a `(fn [event & [?timeout-ms ?cb-fn]])`. This is for standard **client>server req>resp calls**.
135134

136135
#### Server-side API
137136

138-
* `ch-recv` is a **core.async channel** that'll receive **`event-msg`**s.
139-
* `chsk-send!` is a `(fn [user-id event])`.
137+
* `ch-recv` is a **core.async channel** that'll receive `event-msg`s.
138+
* `chsk-send!` is a `(fn [user-id event])`. This is for async **server>user PUSH calls**.
140139

141140
===============
142141

@@ -155,9 +154,7 @@ Term | Form
155154
* The server can likewise use `chsk-send!` to send `event`s to _all_ the clients (browser tabs, devices, etc.) of a particular connected user by his/her `user-id`.
156155
* The server can also use an `event-msg`'s `?reply-fn` to _reply_ to a client `event` using an _arbitrary edn value_.
157156

158-
===============
159-
160-
**And that's 80% of what you need to know to get going**. The remaining documentation is mostly for fleshing out the new patterns that this API enables.
157+
> It's worth noting that the server>user push `(chsk-send! <user-id> <event>)` takes a mandatory **user-id** argument. See the FAQ later for more info.
161158
162159
### Ajax/Sente comparison: client>server
163160

@@ -171,8 +168,7 @@ Term | Form
171168
(do-something! content))
172169
:error (fn [xhr text-status] (error-handler!))})
173170

174-
;;; Using Sente:
175-
(chsk-send!
171+
(chsk-send! ; Using Sente
176172
[:some/request-id {:name "Rich Hickey" :type "Awesome"}] ; event
177173
8000 ; timeout
178174
;; Optional callback:
@@ -187,132 +183,69 @@ Some important differences to note:
187183
* The Ajax request is slow to initialize, and bulky (HTTP overhead).
188184
* The Sente request is pre-initialized (usu. WebSocket), and lean (edn protocol).
189185

190-
### Ajax/Sente comparison: server>clientS push
186+
### Ajax/Sente comparison: server>user push
191187

192188
* Ajax would require clumsy long-polling setup, and wouldn't easily support users connected with multiple clients simultaneously.
193-
* Sente: `(chsk-send! "bob-username" [:some/alert-id <edn-payload>])`.
194-
195-
196-
### An example of event routing using core.match
197-
198-
You can do this any way you find convenient, but [core.match][] is a nice fit and works well with both Clojure and ClojureScript:
199-
200-
```clojure
201-
;;;; Server-side (.clj), in `my-server-side-routing-ns` ------------------------
202-
203-
(defn- event-msg-handler
204-
[{:as ev-msg :keys [ring-req event ?reply-fn]} _]
205-
(let [session (:session ring-req)
206-
uid (:uid session)
207-
[id data :as ev] event]
208-
209-
(timbre/debugf "Event: %s" ev)
210-
(match [id data]
211-
212-
[:foo/bar _] ; Events matching [:foo/bar <anything>] shape
213-
(do (do-some-work!)
214-
(?reply-fn (str "Echo: " event))) ; Reply with a string
189+
* Sente: `(chsk-send! "destination-user-id" [:some/alert-id <edn-payload>])`.
215190

216-
[:my-app/request-fruit fruit-name]
217-
(?reply-fn {:some-data-key "some-data-val"
218-
:your-fruit fruit-name}) ; Reply with a map
219191

220-
:else
221-
(do (timbre/warnf "Unmatched event: %s" ev)
222-
(when-not (:dummy-reply-fn? (meta ?reply-fn)) ; not `reply!`
223-
(?reply-fn (format "Unmatched event, echo: %s" ev)))))))
192+
### FAQ
224193

225-
;; Will start a core.async go loop to handle `event-msg`s as they come in:
226-
(sente/start-chsk-router-loop! event-msg-handler ch-chsk)
227-
```
194+
#### What is the `user-id` provided to the server>user push fn?
228195

229-
```clojure
230-
;;;; Client-side (.cljs), in `my-client-side-ns` -------------------------------
196+
For the server to push events, we need a destination. Traditionally we might push to a _client_ (e.g. browser tab). But with modern rich web applications and the increasing use of multiple simultaneous devices (tablets, mobiles, etc.) - the value of a _client_ push is diminishing. You'll often see applications (even by Google) struggling to deal with these cases.
231197

232-
(defn- event-handler [[id data :as ev] _]
233-
(logf "<! %s" id)
234-
(match [id data]
198+
Sente offers an out-the-box solution by pulling the concept of identity one level higher and dealing with unique _users_ rather than clients. **What constitutes a user is entirely at the discretion of each application**:
235199

236-
;; An event from `ch-ui` that our UI has generated:
237-
[:on.keypress/div#msg-input _] (do-something!)
200+
* Each user-id may have zero _or more_ connected clients at any given time.
201+
* Each user-id _may_ survive across clients (browser tabs, devices), and sessions.
238202

239-
;; A channel socket event pushed from our server:
240-
[:chsk/recv [:my-app/alert-from-server payload]]
241-
(do (logf "Pushed payload received from server!: %s" payload)
242-
(do-something! payload))
203+
**Set the user's `:uid` Ring session key to give him/her an identity**.
243204

244-
[:chsk/state [:first-open _]] (logf "Channel socket successfully established!")
205+
If you want a simple _per-session_ identity, generate a _random uuid_. If you want an identity that persists across sessions, try use something with _semantic meaning_ that you may already have like a database-generated user-id, a login email address, a secure URL fragment, etc.
245206

246-
[:chsk/state new-state] (logf "Chsk state change: %s" new-state)
247-
[:chsk/recv payload] (logf "From server: %s" payload)
248-
:else (logf "Unmatched <!: %s" id)))
207+
> Note that user-ids are used **only** for server>user push. client>server requests don't take a user-id.
249208
250-
(let [ch-chsk ch-chsk ; Chsk events (incl. async events from server)
251-
ch-ui (chan) ; Channel for your own UI events, etc. (optional)
252-
ch-merged (async/merge [ch-chsk ch-ui])]
253-
;; Will start a core.async go loop to handle `event`s as they come in:
254-
(sente/start-chsk-router-loop! event-handler ch-merged))
255-
```
209+
#### Will Sente work with [React][]/[Reagent][]/[Om][]/etc.?
256210

257-
### FAQ
211+
Sure! I use it with Reagent myself. Sente's just a client<->server comms mechanism.
258212

259-
#### What is Sente useful for?
213+
#### What if I need to use JSON, XML, raw strings, etc.?
260214

261-
[Single-page web applications](http://en.wikipedia.org/wiki/Single-page_application), realtime web applications, web applications that need to support efficient, high-performance push-to-client capabilities.
215+
Sente uses edn as an _implementation detail_ of its transfer format. Anything sent with Sente will arrive at the other end as _Clojure data_.
262216

263-
Sente's channel sockets are basically a **replacement for both traditional WebSockets and Ajax** that is IMO:
217+
Send a map, get a map. Send a vector, get a vector. Send a string, get a string.
264218

265-
* More flexible.
266-
* Easier+faster to work with (esp. for rapid prototyping).
267-
* More efficient in most cases, and never less efficient.
219+
And since JSON, XML, etc. are all string-encoded formats, using them with Sente is trivial: just **send the encoded data as a string**, and remember to decode it on the other end however you like.
268220

269-
#### Any disadvantages?
221+
Relative to network transfer times, the cost of (for example) `json->edn->json->data` vs `json->data` is negligable. It's also worth noting that the additional encoding isn't actually going to waste, it's buying you features implemented transparently by Sente like protocol negotiation and event batching. These can often outweigh any additional encoding cost anyway.
270222

271-
I've been using something similar to Sente in production for a couple months, but this particular public implementation is relatively immature. There aren't currently any known security holes, but I wouldn't rule out big or small bugs in the short term.
223+
#### How do I route client/server events?
272224

273-
I'd like to try head for a `v1.0.0` w/in the next 2 months (~end of April).
225+
However you like! If you don't have many events, a simple `cond` will probably do. I use [core.match][] myself since it's a nice fit and works well with both Clojure and ClojureScript. The [reference example project][] has a fully-baked example.
274226

275-
#### Is there HTTPS support?
227+
#### Security: is there HTTPS support?
276228

277229
Yup, it's automatic for both Ajax and WebSockets. If the page serving your JavaScript (ClojureScript) is running HTTPS, your Sente channel sockets will run over HTTPS and/or the WebSocket equivalent (WSS).
278230

279-
#### CSRF security?
231+
#### Security: CSRF protection?
280232

281233
**This is important**. Sente has support, but you'll need to do a couple things on your end:
282234

283235
1. Server-side: you'll need to use middleware like `ring-anti-forgery` to generate and check CSRF codes. The `ring-ajax-post` handler should be covered (i.e. protected).
284236
2. Client-side: you'll need to pass the page's csrf code to the `make-channel-socket!` constructor.
285237

286-
#### What about authentication/authorization?
287-
288-
Auth isn't something Sente is opinionated about, so you can+should **use whatever standard Ring auth mechanism you normally would**.
289-
290-
Sente **does require** that you **include a unique user id** (`:uid key`) in authenticated Ring sessions.
291-
292-
> You'll then provide that id as an argument when calling the server-side's `chsk-send!` fn for server>clientS push.
293-
294-
So basically: mod your normal auth/login procedure to ensure that a `:uid` key is present in each authenticated session. The id should be unique per user (i.e. consistent over all that user's browser tabs and devices, etc.). It could be a unique username string, unique integer, uuid string, unique url, etc.
295-
296-
> The sessionized user id is necessary to support a consistent+secure user identity over multiple requests that may be received over multiple protocols.
297-
298-
The un/authenticated Ring session will be provided to all your handlers as usual, so you're free to do the usual server-side security checks: is this user authenticated (logged in?), is this user authorized to view the requested resource (authorization), etc.
299-
300-
#### Why isn't `x` documented?
301-
302-
Sorry, just haven't had the time (yet)! Am currently in the process of launching a couple products and only released Sente now to de-stress and take a couple days off work. It was a case of releasing what I could put together in a weekend, or not releasing anything. **PR's are very welcome for any improvements, incl. to documentation+examples**!
303-
304-
If you have a question you might also want to take a look at the source code which is short + quite approachable. Otherwise feel free to open an issue and I'll try reply ASAP.
305-
306-
#### Will Sente work with [React][]/[Reagent][]/[Om][]/etc.?
238+
The [reference example project][] has a fully-baked example.
307239

308-
Sure! Sente's just a client<->server message mechanism, it's completely unopinionated about the shape or architecture of your application.
240+
#### Examples: wherefore art thou?
309241

242+
There's a full [reference example project][] in the repo. Call `lein start-dev` in that dir to get a (headless) development repl that you can connect to with [Cider][] (emacs) or your IDE.
310243

311-
## This project supports the CDS and ![ClojureWerkz](https://raw.github.com/clojurewerkz/clojurewerkz.org/master/assets/images/logos/clojurewerkz_long_h_50.png) goals
244+
Further instructions are provided in the relevant namespace.
312245

313-
* [CDS][], the **Clojure Documentation Site**, is a **contributer-friendly** community project aimed at producing top-notch, **beginner-friendly** Clojure tutorials and documentation. Awesome resource.
246+
#### Any other questions?
314247

315-
* [ClojureWerkz][] is a growing collection of open-source, **batteries-included Clojure libraries** that emphasise modern targets, great documentation, and thorough testing. They've got a ton of great stuff, check 'em out!
248+
If I've missed something here, feel free to open a GitHub issue or pop me an email!
316249

317250
## Contact & contributing
318251

@@ -332,7 +265,7 @@ Copyright &copy; 2012-2014 Peter Taoussanis. Distributed under the [Eclipse Publ
332265
[other Clojure libs]: <https://www.taoensso.com/clojure-libraries>
333266
[Twitter]: <https://twitter.com/ptaoussanis>
334267
[semantic]: <http://semver.org/>
335-
[example project]: <https://github.com/ptaoussanis/sente/tree/master/example-project>
268+
[reference example project]: <https://github.com/ptaoussanis/sente/tree/master/reference-example-project>
336269
[Leiningen]: <http://leiningen.org/>
337270
[CDS]: <http://clojure-doc.org/>
338271
[ClojureWerkz]: <http://clojurewerkz.org/>
@@ -350,3 +283,4 @@ Copyright &copy; 2012-2014 Peter Taoussanis. Distributed under the [Eclipse Publ
350283
[Om]: <https://github.com/swannodette/om>
351284
[Chord]: <https://github.com/james-henderson/chord>
352285
[jetty7-websockets-async]: <https://github.com/lynaghk/jetty7-websockets-async>
286+
[Socket.IO]: <http://socket.io/>

project.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
(defproject com.taoensso/sente "0.9.0-SNAPSHOT"
1+
(defproject com.taoensso/sente "0.9.0"
22
:author "Peter Taoussanis <https://www.taoensso.com>"
33
:description "Clojure channel sockets library"
44
:url "https://github.com/ptaoussanis/sente"

reference-example-project/project.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
[org.clojure/clojurescript "0.0-2173"]
1616
[org.clojure/core.async "0.1.278.0-76b25b-alpha"]
1717
;;
18-
[com.taoensso/sente "0.9.0-SNAPSHOT"] ; <--- Sente
18+
[com.taoensso/sente "0.9.0"] ; <--- Sente
1919
[com.taoensso/timbre "3.1.6"]
2020
;;
2121
[http-kit "2.1.18"] ; <--- http-kit (currently required)

src/taoensso/sente.cljx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@
7979
#+cljs
8080
(:require-macros [cljs.core.async.macros :as asyncm :refer (go go-loop)]))
8181

82+
;;;; TODO
83+
;; * Performance optimization: client>server event buffering.
84+
8285
;;;; Shared (client+server)
8386

8487
(defn- chan? [x]

0 commit comments

Comments
 (0)