update READMEs

This commit is contained in:
Gered 2016-05-16 18:56:52 -04:00
parent d68f199d6e
commit f83cc16c60
4 changed files with 629 additions and 206 deletions

134
README.md
View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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