update READMEs
This commit is contained in:
parent
d68f199d6e
commit
f83cc16c60
134
README.md
134
README.md
|
@ -13,112 +13,52 @@ The javascript api of BrowserChannel is open-source and part of the
|
|||
Google Closure library. The server component is not, as is noted in
|
||||
the Google Closure book ("Closure: The Definitive Guide by Michael Bolin").
|
||||
|
||||
[1]: http://closure-library.googlecode.com/svn-history/r144/docs/closure_goog_net_browserchannel.js.html
|
||||
[1]: https://google.github.io/closure-library/api/source/closure/goog/net/browserchannel.js.src.html
|
||||
|
||||
## Usage
|
||||
|
||||
[clj-browserchannel][2] is the main library containing both the server- and
|
||||
client-side functionality you'll use in your web apps.
|
||||
|
||||
In order to use the server implementation of BrowserChannel you'll need to
|
||||
use an async adapter. Currently the provided options are:
|
||||
|
||||
* [clj-browserchannel-jetty-adapter][3]
|
||||
* [clj-browserchannel-immutant-adapter][4]
|
||||
|
||||
[2]: https://github.com/gered/clj-browserchannel/tree/master/clj-browserchannel
|
||||
[3]: https://github.com/gered/clj-browserchannel/tree/master/clj-browserchannel-jetty-adapter
|
||||
[4]: https://github.com/gered/clj-browserchannel/tree/master/clj-browserchannel-immutant-adapter
|
||||
|
||||
You can find more information on usage of all of these components by
|
||||
following any of the above links to them.
|
||||
|
||||
## Demo
|
||||
|
||||
clj-browserchannel-demo is an example chat application using a server
|
||||
side implementation for BrowserChannel written in Clojure. The server
|
||||
component is for BrowserChannel version 8.
|
||||
The [chat-demo][2] application is an example chat application using a
|
||||
client-side and server-side implementation for BrowserChannel written in
|
||||
Clojure/ClojureScript. The server component is for BrowserChannel version 8.
|
||||
The client component serves as a wrapper over `goog.net.BrowserChannel`.
|
||||
|
||||
This enables client->server and server->client communication in
|
||||
ClojureScript and Closure web apps, without any javascript
|
||||
dependencies other than the Google Closure [library][2].
|
||||
[2]: https://github.com/gered/clj-browserchannel/tree/master/chat-demo
|
||||
|
||||
[2]: https://developers.google.com/closure/library/
|
||||
|
||||
The example runs in at least:
|
||||
The chat-demo web app runs in at least:
|
||||
|
||||
* Chrome
|
||||
* Firefox
|
||||
* Internet Explorer 5.5+ (!!)
|
||||
* Android browser
|
||||
|
||||
## Jetty Async
|
||||
|
||||
When there are long lasting connections between a client and a
|
||||
webserver it is desirable to not have a thread per
|
||||
connection. Therefore this demo runs with with an asynchronous Jetty
|
||||
adapter. This adapter is compatible with Ring.
|
||||
|
||||
The adapter is based on [ring-jetty-async-adapter][3] by Mark McGranaghan.
|
||||
|
||||
[3]: https://github.com/mmcgrana/ring/tree/jetty-async
|
||||
|
||||
An implementation on top of Netty, through [Aleph][4] is in
|
||||
development.
|
||||
|
||||
[4]: https://github.com/ztellman/aleph
|
||||
|
||||
## Related and alternative frameworks
|
||||
|
||||
* Websockets - Websockets solve the same problems as BrowserChannel,
|
||||
however BrowserChannel works on almost all existing clients.
|
||||
* socket.io - [socket.io][5] provides a similar api as BrowserChannel on
|
||||
top of many transport protocols, including websockets. BrowserChannel
|
||||
only has two transport protocols: XHR and forever frames (for IE) in
|
||||
streaming and non-streaming mode.
|
||||
* socket.io - [socket.io][3] provides a similar api as BrowserChannel on
|
||||
top of many transport protocols, including websockets. BrowserChannel
|
||||
only has two transport protocols: XHR and forever frames (for IE) in
|
||||
streaming and non-streaming mode.
|
||||
|
||||
[5]: http://socket.io
|
||||
|
||||
## Run
|
||||
;; compile cljs
|
||||
lein run -m tasks.build-dev-js
|
||||
;; compile cljs in advanced mode
|
||||
lein run -m tasks.build-advanced-js
|
||||
lein run -m chat-demo.core
|
||||
|
||||
Open two windows at [http://localhost:8080/index.html](http://localhost:8080/index.html) (Advanced compiled)
|
||||
or [http://localhost:8080/index-dev.html](http://localhost:8080/index-dev.html) and start chatting!
|
||||
|
||||
## Run on Heroku
|
||||
Use the Heroku Clojure [buildpack][7].
|
||||
|
||||
heroku config:add BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-clojure.git
|
||||
|
||||
This project additionally
|
||||
requires two build tasks to compile the ClojureScript during deployment.
|
||||
|
||||
Enable [user_env_compile][6]:
|
||||
|
||||
heroku labs:enable user_env_compile -a <YOUR_APP_NAME>
|
||||
|
||||
Add this config var:
|
||||
|
||||
heroku config:add LEIN_BUILD_TASK="run -m tasks.build-dev-js, run -m tasks.build-advanced-js"
|
||||
|
||||
[6]: https://devcenter.heroku.com/articles/labs-user-env-compile
|
||||
[7]: https://github.com/heroku/heroku-buildpack-clojure.git
|
||||
|
||||
### Note on disconnections on Heroku
|
||||
I have found that Heroku does not immediately report when a connection to a client
|
||||
is broken. If the client is able to reconnect this is not a problem,
|
||||
as this is supported by the BrowserChannel API. However when you
|
||||
unplug the internet cable the client cannot reconnect and the server
|
||||
must timeout the session. Ussually this happens when trying to send the next
|
||||
heartbeat to the client. On Heruko this does not report an error, even
|
||||
though there is no connection to the client. So instead of the
|
||||
connection timeing out on a heartbeat (after seconds/a minute) the
|
||||
connection will only timeout after the connection is timed out by the
|
||||
server (4 minutes by default). The Netty implementation has the same
|
||||
problem on Heroku. Deployments on Amazon Web Services do not have this
|
||||
problem.
|
||||
|
||||
## Configuration:
|
||||
See default-options in src/net/thegeez/browserchannel.clj
|
||||
And the :response-timeout option in src/net/thegeez/jetty_async_adapter.clj
|
||||
|
||||
### Debug / Play around
|
||||
BrowserChannel has a helpful debug window. Uncomment the debug-window
|
||||
and .setChannelDebug lines in cljs/bc/core.cljs to enable the logging window.
|
||||
|
||||
## Todo
|
||||
- Release backend as library
|
||||
- Handling acknowledgements by client and callbacks on queued arrays
|
||||
- Host prefixes
|
||||
- Heroku disconnection
|
||||
- Replace session listeners, possibly with lamina
|
||||
- Explore other event based Java webservers, such as Netty and Webbit
|
||||
[3]: http://socket.io
|
||||
|
||||
## Other BrowserChannel implementations
|
||||
Many thanks to these authors, their work is the only open-source
|
||||
|
@ -131,18 +71,22 @@ in C++ by Andy Hochhaus - Has the most extensive [documentation][libevent-doc] o
|
|||
in Node.js/Javascript by Joseph Gentle
|
||||
|
||||
[libevent]: http://code.google.com/p/libevent-browserchannel-server
|
||||
[libevent-doc]: http://code.google.com/p/libevent-browserchannel-server/wiki/BrowserChannelProtocol
|
||||
[libevent-doc]: http://web.archive.org/web/20121226064550/http://code.google.com/p/libevent-browserchannel-server/wiki/BrowserChannelProtocol
|
||||
[ruby]: https://github.com/dturnbull/browserchannel
|
||||
[node]: https://github.com/josephg/node-browserchannel
|
||||
|
||||
## About
|
||||
|
||||
Written by:
|
||||
Gijs Stuurman / [@thegeez][twt] / [Blog][blog] / [GitHub][github]
|
||||
Gijs Stuurman /
|
||||
[@thegeez](http://twitter.com/thegeez) /
|
||||
[Blog](http://thegeez.github.com) /
|
||||
[GitHub](https://github.com/thegeez)
|
||||
|
||||
[twt]: http://twitter.com/thegeez
|
||||
[blog]: http://thegeez.github.com
|
||||
[github]: https://github.com/thegeez
|
||||
Many updates in this fork by:
|
||||
Gered King /
|
||||
[@geredking](http://twitter.com/geredking) /
|
||||
[GitHub](https://github.com/gered)
|
||||
|
||||
### License
|
||||
|
||||
|
|
|
@ -3,19 +3,84 @@
|
|||
Immutant async adapter for BrowserChannel.
|
||||
|
||||
See also: [clj-browserchannel][1]
|
||||
|
||||
[1]:https://github.com/gered/clj-browserchannel
|
||||
|
||||
|
||||
## Leiningen
|
||||
|
||||
[gered/clj-browserchannel-immutant-adapter "0.0.1"]
|
||||
[gered/clj-browserchannel-immutant-adapter "0.0.2"]
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
This library does not directly include Immutant as a dependency so
|
||||
that it's not tied to a specific version of Immutant. You will need
|
||||
to also include Immutant as a dependency directly in your project.
|
||||
|
||||
[See the Immutant project for the relevant dependency line.][2]
|
||||
|
||||
[2]: https://github.com/immutant/immutant
|
||||
|
||||
To enable server-side BrowserChannel functionality, you simply
|
||||
need to add the `wrap-immutant-async-adapter` middleware to your
|
||||
Ring handler. For example:
|
||||
|
||||
```clj
|
||||
(ns your-app
|
||||
(:require
|
||||
; ...
|
||||
[net.thegeez.browserchannel.server :refer [wrap-browserchannel]
|
||||
[net.thegeez.browserchannel.immutant-async-adapter :refer [wrap-immutant-async-adapter]]
|
||||
[immutant.web :as immutant]
|
||||
; ...
|
||||
))
|
||||
|
||||
(def event-handlers
|
||||
; ... browserchannel event handler map ...
|
||||
)
|
||||
|
||||
(def your-app-routes
|
||||
; ...
|
||||
)
|
||||
|
||||
(def ring-handler
|
||||
(-> your-app-routes
|
||||
; other middleware
|
||||
(wrap-browserchannel event-handlers)
|
||||
(wrap-immutant-async-adapter)))
|
||||
|
||||
(defn -main [& args]
|
||||
(immutant/run
|
||||
#'ring-handler
|
||||
{:port 8080}))
|
||||
```
|
||||
|
||||
By default, BrowserChannel async requests will timeout after 4 minutes.
|
||||
This default time period is based on what Google uses currently in
|
||||
their BrowserChannel usage on e.g. Gmail. If you would like to change this,
|
||||
simple pass in `:response-timeout` with a new time in milliseconds to
|
||||
`wrap-immutant-async-adapter`:
|
||||
|
||||
```clj
|
||||
(def ring-handler
|
||||
(-> your-app-routes
|
||||
; other middleware
|
||||
(wrap-browserchannel event-handlers)
|
||||
; 2 minute async request timeout
|
||||
(wrap-immutant-async-adapter {:response-timeout (* 2 60 1000})))
|
||||
```
|
||||
|
||||
This timeout period directly controls the maximum time that a
|
||||
back channel request can remain open for before it gets closed
|
||||
and the client must open a new one.
|
||||
|
||||
## About
|
||||
|
||||
Written by:
|
||||
Gered King / [@geredking][twt] / [GitHub][github]
|
||||
|
||||
[twt]: http://twitter.com/geredking
|
||||
[github]: https://github.com/gered
|
||||
Gered King /
|
||||
[@geredking](http://twitter.com/geredking) /
|
||||
[GitHub](https://github.com/gered)
|
||||
|
||||
### License
|
||||
|
||||
|
|
|
@ -1,25 +1,105 @@
|
|||
# clj-browserchannel-jetty-adapter
|
||||
|
||||
Jetty async adapter for BrowserChannel. This now simply makes use of
|
||||
whatever Jetty version that Ring's [ring-jetty-adapter][1] is using
|
||||
and just adds the required async handling configuration on top of it.
|
||||
[1]:https://github.com/ring-clojure/ring/tree/master/ring-jetty-adapter
|
||||
Jetty async adapter for BrowserChannel.
|
||||
|
||||
See also: [clj-browserchannel][1]
|
||||
|
||||
[1]:https://github.com/gered/clj-browserchannel
|
||||
|
||||
|
||||
## Leiningen
|
||||
|
||||
[gered/clj-browserchannel-jetty-adapter "0.1.0"]
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
This library does not directly include Jetty as a dependency so that
|
||||
it's not tied to one specific version of Jetty (allowing you to upgrade
|
||||
to newer versions easier as long as they remain compatible with this
|
||||
library). You will need to include these Ring dependencies at a minimum:
|
||||
|
||||
* `ring/ring-core`
|
||||
* `ring/ring-servlet`
|
||||
* `ring/ring-jetty-adapter`
|
||||
|
||||
The top-level `ring` dependency includes all of these. See the
|
||||
[Ring][2] project page for more information and the relevant
|
||||
dependency lines for your `project.clj`.
|
||||
|
||||
[2]: https://github.com/ring-clojure/ring
|
||||
|
||||
To enable server-side BrowserChannel functionality, you should
|
||||
start Jetty with the included `run-jetty` function. For example:
|
||||
|
||||
```clj
|
||||
(ns your-app
|
||||
(:require
|
||||
; ...
|
||||
[net.thegeez.browserchannel.server :refer [wrap-browserchannel]
|
||||
[net.thegeez.browserchannel.jetty-async-adapter :refer [run-jetty]]
|
||||
; ...
|
||||
))
|
||||
|
||||
(def event-handlers
|
||||
; ... browserchannel event handler map ...
|
||||
)
|
||||
|
||||
(def your-app-routes
|
||||
; ...
|
||||
)
|
||||
|
||||
(def ring-handler
|
||||
(-> your-app-routes
|
||||
; other middleware
|
||||
(wrap-browserchannel event-handlers)))
|
||||
|
||||
(defn -main [& args]
|
||||
(run-jetty
|
||||
#'handler
|
||||
{:join? false
|
||||
:port 8080}))
|
||||
```
|
||||
|
||||
The `run-jetty` function takes the exact same set of options as
|
||||
`ring.adapter.jetty/run-jetty` does. See that function for more
|
||||
information.
|
||||
|
||||
One additional option is made available to configure the length of
|
||||
time that BrowserChannel async requests will remain open before
|
||||
timing out. By default this timeout is 4 minutes. This default
|
||||
time period is based on what Google uses currently in their
|
||||
BrowserChannel usage on e.g. Gmail.
|
||||
|
||||
To change this, the option you need to pass in to `run-jetty` is
|
||||
`:response-timeout` with a new time in milliseconds:
|
||||
|
||||
```clj
|
||||
(run-jetty
|
||||
#'handler
|
||||
{:join? false
|
||||
:port 8080
|
||||
; 2 minute async request timeout
|
||||
:response-timeout (* 2 60 1000)})
|
||||
```
|
||||
|
||||
This timeout period directly controls the maximum time that a
|
||||
back channel request can remain open for before it gets closed
|
||||
and the client must open a new one.
|
||||
|
||||
|
||||
## About
|
||||
|
||||
Written by:
|
||||
Gijs Stuurman / [@thegeez][twt] / [Blog][blog] / [GitHub][github]
|
||||
Gijs Stuurman /
|
||||
[@thegeez](http://twitter.com/thegeez) /
|
||||
[Blog](http://thegeez.github.com) /
|
||||
[GitHub](https://github.com/thegeez)
|
||||
|
||||
[twt]: http://twitter.com/thegeez
|
||||
[blog]: http://thegeez.github.com
|
||||
[github]: https://github.com/thegeez
|
||||
Updates in this fork by:
|
||||
Gered King /
|
||||
[@geredking](http://twitter.com/geredking) /
|
||||
[GitHub](https://github.com/gered)
|
||||
|
||||
### License
|
||||
|
||||
|
|
|
@ -1,146 +1,480 @@
|
|||
# clj-browserchannel-server
|
||||
# clj-browserchannel
|
||||
|
||||
Cross-browser compatible, real-time, bi-directional
|
||||
communication between ClojureScript and Clojure using Google Closure
|
||||
BrowserChannel.
|
||||
|
||||
See also: [clj-browserchannel][0]
|
||||
[0]:https://github.com/gered/clj-browserchannel
|
||||
See also: [clj-browserchannel][1]
|
||||
|
||||
## goog.net.BrowserChannel
|
||||
[1]: https://github.com/gered/clj-browserchannel
|
||||
|
||||
From the Google Closure API: "A [BrowserChannel][1] simulates a
|
||||
bidirectional socket over HTTP. It is the basis of the Gmail Chat IM
|
||||
connections to the server."
|
||||
The javascript api of BrowserChannel is open-source and part of the
|
||||
Google Closure library. The server component is not, as is noted in
|
||||
the Google Closure book ("Closure: The Definitive Guide by Michael Bolin").
|
||||
|
||||
[1]: http://closure-library.googlecode.com/svn-history/r144/docs/closure_goog_net_browserchannel.js.html
|
||||
## Leiningen
|
||||
|
||||
## Demo
|
||||
[gered/clj-browserchannel "0.3"]
|
||||
|
||||
This project is a server side implementation for BrowserChannel
|
||||
written in Clojure. The server component is for BrowserChannel version 8.
|
||||
You will also need to include one of the async adapters as an
|
||||
additional dependency. See [here][1] for more information..
|
||||
|
||||
This enables client->server and server->client communication in
|
||||
ClojureScript and Closure web apps, without any javascript
|
||||
dependencies other than the Google Closure [library][2].
|
||||
|
||||
[2]: https://developers.google.com/closure/library/
|
||||
## Basic Concepts
|
||||
|
||||
The browserchannel client side runs in at least:
|
||||
Communication between client and server in a web app using
|
||||
BrowserChannel occurs after a BrowserChannel **session** is
|
||||
established.
|
||||
|
||||
* Chrome
|
||||
* Firefox
|
||||
* Internet Explorer 5.5+ (!!)
|
||||
* Android browser
|
||||
Just before this initial connection/handshake is done, the client
|
||||
will perform up to 3 different test HTTP requests automatically
|
||||
to determine the supported capabilities of the client browser and
|
||||
what the network connection is like. After this testing is
|
||||
complete, another HTTP request will be sent to establish the
|
||||
session.
|
||||
|
||||
## Jetty Async
|
||||
There are two "channels" used in a BrowserChannel session:
|
||||
|
||||
When there are long lasting connections between a client and a
|
||||
webserver it is desirable to not have a thread per
|
||||
connection. Therefore this demo runs with with an asynchronous Jetty
|
||||
adapter. This adapter is compatible with Ring.
|
||||
* **Forward Channel** - used to initiate the session and also used
|
||||
to transmit messages from the client to the server. Behind the
|
||||
scenes this is a quick HTTP POST request each time the client
|
||||
wants to send something. Multiple messages can be batched into
|
||||
a single request.
|
||||
* **Backward Channel** - (or just "back channel") a long running
|
||||
HTTP GET request that is used to transmit messages from the
|
||||
server to the client. On IE 9 and earlier, the back channel
|
||||
is implemented using "forever frames" while on every other
|
||||
browser XHR streaming is used.
|
||||
|
||||
The adapter is based on [ring-jetty-async-adapter][3] by Mark McGranaghan.
|
||||
As mentioned above, the forward channel also is used to initiate
|
||||
the BrowserChannel session, so the first HTTP request after the
|
||||
initial tests is a forward channel request. Upon success, the
|
||||
client will automatically initiate a back channel request.
|
||||
|
||||
[3]: https://github.com/mmcgrana/ring/tree/jetty-async
|
||||
Because the back channel is a long running HTTP request, over the
|
||||
course of a long session multiple back channel requests will be
|
||||
opened and closed over time. This is normal and to be expected
|
||||
as connections time out, etc. The client and server will
|
||||
automatically manage this.
|
||||
|
||||
## Netty
|
||||
> Regarding terminology, when reviewing BrowserChannel code,
|
||||
> you will see client-to-server messages commonly referred to as
|
||||
> 'maps' and server-to-client messages as 'arrays.'
|
||||
> clj-browserchannel simplifies this somewhat from the
|
||||
> perspective of your application's code, as messages sent
|
||||
> in either direction can be any arbitrary Clojure value that
|
||||
> can be serialized to a string as EDN.
|
||||
|
||||
The server component can also run on top of Netty, through [Aleph][4].
|
||||
A **session ID** is used to identify a client's BrowserChannel
|
||||
session. clj-browserchannel uses UUID's as session IDs. These IDs
|
||||
are generated by the server. Unlike HTTP Session IDs that you may
|
||||
be more familiar with, BrowserChannel session IDs are regenerated
|
||||
far more frequently. Each time a reconnection occurs (a full
|
||||
session reconnection, not just a back channel reconnect), a new
|
||||
session ID is generated for the client. For example, each time a
|
||||
user refreshes the page in their browser they will be given a new
|
||||
BrowserChannel session and corresponding ID. If a network issue
|
||||
occurs and forces the client to automatically reconnect (even
|
||||
without a browser page refresh), a new BrowserChannel session
|
||||
is still established with a different ID.
|
||||
|
||||
[4]: https://github.com/ztellman/aleph
|
||||
BrowserChannel supports simple message receipt acknowledgements.
|
||||
When the client sends the server a message (via the forward
|
||||
channel), the client immediately receives acknowledgement based
|
||||
on whether the HTTP POST request was successful or not (as the
|
||||
server returns a standard reply on success).
|
||||
|
||||
## Related and alternative frameworks
|
||||
For server-to-client messages, it is a little more complicated.
|
||||
Each message sent along the back channel is given an "array id"
|
||||
(which is included in the data written to the back channel).
|
||||
When the client reads the data from the back channel it makes
|
||||
note of the corresponding array ids, and on the next request
|
||||
to the server (either a forward channel or back channel request)
|
||||
includes an `AID` parameter with the most recent array id that
|
||||
has been read. The server then uses this `AID` value to mark
|
||||
sent items as acknowledged.
|
||||
|
||||
* Websockets - Websockets solve the same problems as BrowserChannel,
|
||||
however BrowserChannel works on almost all existing clients.
|
||||
* socket.io - [socket.io][5] provides a similar api as BrowserChannel on
|
||||
top of many transport protocols, including websockets. BrowserChannel
|
||||
only has two transport protocols: XHR and forever frames (for IE) in
|
||||
streaming and non-streaming mode.
|
||||
I recommend reading [this description of the BrowserChannel protocol][2]
|
||||
for far more in-depth details on everything mentioned in this
|
||||
section (moreso detailing the client side of the protocol, but
|
||||
still very helpful information).
|
||||
|
||||
[5]: http://socket.io
|
||||
[2]: http://web.archive.org/web/20121226064550/http://code.google.com/p/libevent-browserchannel-server/wiki/BrowserChannelProtocol
|
||||
|
||||
## Run
|
||||
;; compile cljs
|
||||
lein run -m tasks.build-dev-js
|
||||
;; compile cljs in advanced mode
|
||||
lein run -m tasks.build-advanced-js
|
||||
lein run -m chat-demo.core
|
||||
As well it may be useful to try out the [chat-demo][3] app
|
||||
while monitoring ongoing XHR requests. In particular, pay
|
||||
attention to HTTP requests over `/channel/test` and
|
||||
`/channel/bind`.
|
||||
|
||||
Open two windows at [http://localhost:8080/index.html](http://localhost:8080/index.html) (Advanced compiled)
|
||||
or [http://localhost:8080/index-dev.html](http://localhost:8080/index-dev.html) and start chatting!
|
||||
[3]: https://github.com/gered/clj-browserchannel/tree/master/chat-demo
|
||||
|
||||
## Run on Heroku
|
||||
Use the Heroku Clojure [buildpack][7].
|
||||
`/channel/test` is where all the previously mentioned test
|
||||
HTTP requests will be sent to during the initial BrowserChannel
|
||||
session connection process. `/channel/bind` is where all
|
||||
forward and back channel HTTP requests are sent to.
|
||||
|
||||
heroku config:add BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-clojure.git
|
||||
> Note that your browser's inspector may not show you the
|
||||
> response body of back channel requests (the long running
|
||||
> HTTP GET requests) until they finish. By default in
|
||||
> clj-browserchannel, these will automatically timeout
|
||||
> after 4 minutes.
|
||||
|
||||
This project additionally
|
||||
requires two build tasks to compile the ClojureScript during deployment.
|
||||
|
||||
Enable [user_env_compile][6]:
|
||||
## Usage
|
||||
|
||||
heroku labs:enable user_env_compile -a <YOUR_APP_NAME>
|
||||
|
||||
Add this config var:
|
||||
### Server-Side
|
||||
|
||||
heroku config:add LEIN_BUILD_TASK="run -m tasks.build-dev-js, run -m tasks.build-advanced-js"
|
||||
An example using Immutant as the web server with the BrowserChannel
|
||||
async adapter for it. This assumes you have added 3 dependencies
|
||||
in your `project.clj`:
|
||||
|
||||
[6]: https://devcenter.heroku.com/articles/labs-user-env-compile
|
||||
[7]: https://github.com/heroku/heroku-buildpack-clojure.git
|
||||
* clj-browserchannel (that's _this_ library)
|
||||
* [clj-browserchannel-immutant-adapter][4]
|
||||
* [Immutant][5]
|
||||
|
||||
### Note on disconnections on Heroku
|
||||
I have found that Heroku does not immediately report when a connection to a client
|
||||
is broken. If the client is able to reconnect this is not a problem,
|
||||
as this is supported by the BrowserChannel API. However when you
|
||||
unplug the internet cable the client cannot reconnect and the server
|
||||
must timeout the session. Ussually this happens when trying to send the next
|
||||
heartbeat to the client. On Heruko this does not report an error, even
|
||||
though there is no connection to the client. So instead of the
|
||||
connection timeing out on a heartbeat (after seconds/a minute) the
|
||||
connection will only timeout after the connection is timed out by the
|
||||
server (4 minutes by default). The Netty implementation has the same
|
||||
problem on Heroku. Deployments on Amazon Web Services do not have this
|
||||
problem.
|
||||
[4]: https://github.com/gered/clj-browserchannel/tree/master/clj-browserchannel-immutant-adapter
|
||||
[5]: https://github.com/immutant/immutant
|
||||
|
||||
## Configuration:
|
||||
See default-options in src/net/thegeez/browserchannel.clj
|
||||
And the :response-timeout option in src/net/thegeez/jetty_async_adapter.clj
|
||||
```clj
|
||||
(ns your-app
|
||||
(:gen-class)
|
||||
(:require
|
||||
; this next line obviously not required.
|
||||
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
|
||||
; ...
|
||||
[net.thegeez.browserchannel.server :as browserchannel :refer [wrap-browserchannel]
|
||||
[net.thegeez.browserchannel.immutant-async-adapter :refer [wrap-immutant-async-adapter]]
|
||||
[immutant.web :as immutant]
|
||||
; ...
|
||||
))
|
||||
|
||||
## Todo
|
||||
- Handling acknowledgements by client and callbacks on queued arrays
|
||||
- Host prefixes
|
||||
- Heroku disconnection
|
||||
- Replace session listeners, possibly with lamina
|
||||
- Explore other event based Java webservers, such as Netty and Webbit
|
||||
(def event-handlers
|
||||
{:on-open
|
||||
(fn [session-id ring-request]
|
||||
; called when a new session is established for a client
|
||||
)
|
||||
|
||||
:on-close
|
||||
(fn [session-id ring-request reason]
|
||||
; called when an existing client session is closed
|
||||
; (for any reason)
|
||||
)
|
||||
|
||||
:on-receive
|
||||
(fn [session-id ring-request data]
|
||||
; called when a client has sent data to the server
|
||||
)})
|
||||
|
||||
## Other BrowserChannel implementations
|
||||
Many thanks to these authors, their work is the only open-source
|
||||
documentation on the BrowserChannel protocol.
|
||||
(def your-app-routes
|
||||
; ...
|
||||
)
|
||||
|
||||
* [libevent-browserchannel-server][libevent]
|
||||
in C++ by Andy Hochhaus - Has the most extensive [documentation][libevent-doc] on the BrowserChannel protocol
|
||||
* [browserchannel][ruby] in Ruby by David Turnbull
|
||||
* [node-browserchannel][node]
|
||||
in Node.js/Javascript by Joseph Gentle
|
||||
(def ring-handler
|
||||
(-> your-app-routes
|
||||
(wrap-browserchannel event-handlers)
|
||||
(wrap-defaults site-defaults)
|
||||
(wrap-immutant-async-adapter)))
|
||||
|
||||
[libevent]: http://code.google.com/p/libevent-browserchannel-server
|
||||
[libevent-doc]: http://code.google.com/p/libevent-browserchannel-server/wiki/BrowserChannelProtocol
|
||||
[ruby]: https://github.com/dturnbull/browserchannel
|
||||
[node]: https://github.com/josephg/node-browserchannel
|
||||
(defn -main [& args]
|
||||
(immutant/run
|
||||
#'ring-handler
|
||||
{:port 8080}))
|
||||
```
|
||||
|
||||
All the server-side BrowserChannel magic happens inside of the
|
||||
`wrap-browserchannel` middleware. The `event-handlers` map
|
||||
shown above includes all the different events you can respond to
|
||||
on the server.
|
||||
|
||||
Of important note, in each of these handlers, `ring-request` is
|
||||
a full Ring HTTP request map like you see with any other normal
|
||||
HTTP requests in a Clojure web app. You can use this to access
|
||||
the user's HTTP session for example (but not to change it).
|
||||
|
||||
The return value of these event handlers is ignored.
|
||||
|
||||
##### Ring Middleware
|
||||
|
||||
As mentioned above, `wrap-browserchannel` is what you should add
|
||||
to your web app's Ring handler to make all the server-side
|
||||
BrowserChannel functionality work. It takes a map of
|
||||
event handlers and another optional options map as the last
|
||||
argument. Options you don't pass in will have their default
|
||||
value used instead.
|
||||
|
||||
See [here][6] for a description of the various options you can
|
||||
use. Most applications probably won't need to change any of
|
||||
these.
|
||||
|
||||
[6]: https://github.com/gered/clj-browserchannel/blob/master/clj-browserchannel/src/net/thegeez/browserchannel/server.clj#L838
|
||||
|
||||
##### Sending Data
|
||||
|
||||
```clj
|
||||
(use 'net.thegeez.browserchannel.server)
|
||||
|
||||
; send any clojure data to a client identified by session-id
|
||||
(send-data! session-id {:msg "Hello, world!"})
|
||||
(send-data! session-id :foobar)
|
||||
(send-data! session-id [:a :b :c])
|
||||
(send-data! session-id "a string of text")
|
||||
|
||||
; send something to all clients
|
||||
(send-data-to-all! {:broadcast "yada yada"})
|
||||
```
|
||||
|
||||
Sending is done asynchronously. If the client does not currently
|
||||
have a back channel request active, then the data will be queued
|
||||
up in a buffer which will be flushed once a back channel
|
||||
becomes available.
|
||||
|
||||
The send functions also take optional callbacks for various
|
||||
events relevant to message sending:
|
||||
|
||||
* `on-sent` occurs when the data has been written to the client's
|
||||
back channel. Remember that this may or may not happen immediately!
|
||||
|
||||
* `on-confirm` occurs when the client acknowledges receipt of the
|
||||
message. This will almost certainly have a long-ish delay before
|
||||
being triggered. As such it is also at risk of not being raised
|
||||
all the time (e.g. if the client disconnects before the next
|
||||
client acknowledgement info can be sent to the server).
|
||||
|
||||
* `on-error` occurs when there was some kind of error writing the
|
||||
the message to the client's back channel or if the client's
|
||||
session is terminated before the message could be written to a
|
||||
back channel.
|
||||
|
||||
```clj
|
||||
(send-data!
|
||||
session-id
|
||||
{:important-data 42}
|
||||
{:on-sent (fn []
|
||||
(println "message written to back channel"))
|
||||
:on-confirm (fn []
|
||||
(println "receipt confirmed by client"))
|
||||
:on-error (fn []
|
||||
(println "message could not be sent"))})
|
||||
```
|
||||
|
||||
The return value of these callback functions is ignored.
|
||||
|
||||
##### Session Management
|
||||
|
||||
Various functions are available to query the status of a client's
|
||||
connection or to manipulate it.
|
||||
|
||||
```clj
|
||||
(use 'net.thegeez.browserchannel.server)
|
||||
|
||||
; does a client have an active session?
|
||||
; NOTE: may have an active session but no currently active back channel!
|
||||
(connected? session-id)
|
||||
=> true
|
||||
|
||||
; more detailed info about a client's session
|
||||
(get-status session-id)
|
||||
=> {:connected? true
|
||||
:has-back-channel? true
|
||||
:last-acknowledged-array-id 42
|
||||
:outstanding-backchannel-bytes 0}
|
||||
|
||||
(get-status a-different-session-id)
|
||||
=> {:connected? false}
|
||||
|
||||
; "politely" tells a client we are going to disconnect them (and to
|
||||
; not attempt to reconnect), and then disconnects them
|
||||
(close! session-id)
|
||||
|
||||
; forcefully disconnects a client. note that the client may just
|
||||
; try to reconnect right away if you use this function
|
||||
(disconnect! session-id)
|
||||
|
||||
```
|
||||
|
||||
|
||||
### Client-Side
|
||||
|
||||
Client-side use of clj-browserchannel is very simple.
|
||||
|
||||
```clj
|
||||
(ns your-app.client
|
||||
(:require
|
||||
[net.thegeez.browserchannel.client :as browserchannel]))
|
||||
|
||||
(def event-handlers
|
||||
{:on-open
|
||||
(fn []
|
||||
; called when a new session is established
|
||||
)
|
||||
|
||||
:on-opening
|
||||
(fn []
|
||||
; called when a new session connection is in progress
|
||||
)
|
||||
|
||||
:on-close
|
||||
(fn [due-to-error? pending undelivered]
|
||||
; called when the browserchannel session is closed
|
||||
; (for any reason)
|
||||
)
|
||||
|
||||
:on-receive
|
||||
(fn [data]
|
||||
; called when data has been received from the server
|
||||
)
|
||||
|
||||
:on-sent
|
||||
(fn [delivered]
|
||||
; called when data has been sent successfully to the server
|
||||
)
|
||||
|
||||
:on-error
|
||||
(fn [error-code]
|
||||
; called when a connection error occurs.
|
||||
)})
|
||||
|
||||
(defn ^:export run []
|
||||
(browserchannel/connect! event-handlers))
|
||||
```
|
||||
|
||||
* `on-open` self-explanatory. Also called on reconnects.
|
||||
|
||||
* `on-opening` called after a connection has been initiated but before
|
||||
it has been established.
|
||||
|
||||
* `on-close` called when the session is closed (either closed by the
|
||||
client or server). `due-to-error?` is true/false depending on if
|
||||
the close was caused by an error (if true, then `on-error` would
|
||||
have been called just before this event). `pending` will contain
|
||||
a list of any messages that were queued up to be sent and may
|
||||
or may not have been received by the server. `undelivered` will
|
||||
contain a list of messages that were queued up and definitely
|
||||
were not received by the server. This event is also fired if
|
||||
a connection attempt fails (even though `on-open` won't have been
|
||||
fired in this case).
|
||||
|
||||
* `on-receive` called when some data is received from the server.
|
||||
|
||||
* `on-sent` called when data has been sent successfully to the server.
|
||||
`delivered` is a list of the messages that were sent.
|
||||
|
||||
* `on-error` called when an error occurs. See [here][7] for a list of
|
||||
error codes and what they mean. In BrowserChannel, when an error
|
||||
occurs, the session is always disconnected (`on-close` will always
|
||||
fire immediately after this event). This is just how the
|
||||
BrowserChannel implementation included in Google Closure works.
|
||||
clj-browserchannel will automatically try to reconnect in the
|
||||
event of an error.
|
||||
|
||||
The `connect!` function also takes an additional and optional map of
|
||||
options. Any options not specified will have their default values
|
||||
used instead. See [here][8] for a description of the available
|
||||
options and their defaults.
|
||||
|
||||
[7]: https://github.com/gered/clj-browserchannel/blob/master/clj-browserchannel/src/net/thegeez/browserchannel/client.cljs#L18
|
||||
[8]: https://github.com/gered/clj-browserchannel/blob/master/clj-browserchannel/src/net/thegeez/browserchannel/client.cljs#L249
|
||||
|
||||
##### Sending Data
|
||||
|
||||
```clj
|
||||
(use 'net.thegeez.browserchannel.client)
|
||||
|
||||
; basically identical to server-side sending
|
||||
(send-data! {:msg "Hello, world!"})
|
||||
(send-data! :foobar)
|
||||
(send-data! [:a :b :c])
|
||||
(send-data! "a string of text")
|
||||
```
|
||||
|
||||
Sending is done asynchronously.
|
||||
|
||||
Also a nice feature of BrowserChannel is that you can queue up
|
||||
messages to be sent to the server _before_ a connection is
|
||||
established. If you do this, the messages will be delivered to the
|
||||
server in the same HTTP POST request that is used to establish the
|
||||
new BrowserChannel session (thereby saving some extra round-trips).
|
||||
|
||||
Like with server-side sending, the `send-data!` function also
|
||||
can take optional callbacks for various message sending events:
|
||||
|
||||
* `on-sent` occurs when the data has been successfully sent
|
||||
to the server (via the forward channel). This will usually
|
||||
occur pretty quickly after `send-data!` is called.
|
||||
|
||||
* `on-error` called when any kind of error occurs that prevents
|
||||
the message from being sent. This error will be raised just
|
||||
before the main `on-error` event handler.
|
||||
|
||||
```clj
|
||||
(send-data!
|
||||
{:important-data 42}
|
||||
{:on-sent (fn []
|
||||
(println "message sent to server successfully"))
|
||||
:on-error (fn []
|
||||
(println "error sending message to server"))})
|
||||
```
|
||||
|
||||
The return value of these callback functions is ignored.
|
||||
|
||||
##### Session Management
|
||||
|
||||
Various functions are available to query the status of the
|
||||
current BrowserChannel session.
|
||||
|
||||
```clj
|
||||
(use 'net.thegeez.browserchannel.client)
|
||||
|
||||
; is there currently an active session?
|
||||
(connected?)
|
||||
=> true
|
||||
|
||||
; disconnects/closes any active browserchannel session
|
||||
; also can be used in the on-close event handler to cancel
|
||||
; any reconnection attempt if needed
|
||||
(disconnect!)
|
||||
|
||||
(channel-state)
|
||||
=> :opened
|
||||
```
|
||||
|
||||
For a list and descriptions of the different BrowserChannel
|
||||
connection states (returned by `channel-state`) see [here][9].
|
||||
|
||||
[9]: https://github.com/gered/clj-browserchannel/blob/master/clj-browserchannel/src/net/thegeez/browserchannel/client.cljs#L10
|
||||
|
||||
|
||||
### Other Notes
|
||||
|
||||
##### BrowserChannel Session Timeouts
|
||||
|
||||
The server-side options do include a session timeout (`session-timeout-interval`)
|
||||
which is the length of time of inactivity before a BrowserChannel
|
||||
session is automatically closed (timed out).
|
||||
|
||||
However it is important to note that this timeout period is _only_
|
||||
activated when the client does not have an active back channel. If
|
||||
the client is able to consistently re-open back channels as they
|
||||
close automatically over time (which is normal behaviour), then the
|
||||
BrowserChannel session will never time out.
|
||||
|
||||
`session-timeout-interval` is mainly intended to automatically
|
||||
cleanup sessions when the client (for whatever reason) suddenly
|
||||
becomes unable to re-open a back channel or the user closes the
|
||||
browser or something like that. At this point the session timeout
|
||||
interval will activate and eventually elapse and clean up the session.
|
||||
|
||||
|
||||
## About
|
||||
|
||||
Written by:
|
||||
Gijs Stuurman / [@thegeez][twt] / [Blog][blog] / [GitHub][github]
|
||||
Gijs Stuurman /
|
||||
[@thegeez](http://twitter.com/thegeez) /
|
||||
[Blog](http://thegeez.github.com) /
|
||||
[GitHub](https://github.com/thegeez)
|
||||
|
||||
[twt]: http://twitter.com/thegeez
|
||||
[blog]: http://thegeez.github.com
|
||||
[github]: https://github.com/thegeez
|
||||
Many updates in this fork by:
|
||||
Gered King /
|
||||
[@geredking](http://twitter.com/geredking) /
|
||||
[GitHub](https://github.com/gered)
|
||||
|
||||
### License
|
||||
|
||||
|
|
Reference in a new issue