update wrap-browserchannel arguments

passing in event handlers is obviously going to be the most common
usage (all apps won't necessarily need to change the default options,
though many will want to). so it seems silly to hide the event handlers
as an option... i feel like it's better as it's own separate
argument.
This commit is contained in:
Gered 2016-05-15 22:16:06 -04:00
parent eff6566ac5
commit f889985fe1
4 changed files with 90 additions and 102 deletions

View file

@ -39,7 +39,7 @@
(def handler
(-> app-routes
(browserchannel/wrap-browserchannel {:events event-handlers})
(browserchannel/wrap-browserchannel event-handlers)
(wrap-defaults site-defaults)))
(defn run-jetty []

View file

@ -528,7 +528,7 @@
;; creates a session agent wrapping session data and
;; adds the session to sessions
(defn- create-session-agent
[req options]
[req events options]
(let [{initial-rid "RID" ;; identifier for forward channel
app-version "CVER" ;; client can specify a custom app-version
old-session-id "OSID"
@ -573,7 +573,7 @@
;; when the client never connects with a backchannel
(send-off session-agent refresh-session-timeout)
;; register application-level browserchannel session events
(let [{:keys [on-open on-close on-receive]} (:events options)]
(let [{:keys [on-open on-close on-receive]} events]
(if on-close (add-listener id :close on-close))
(if on-receive (add-listener id :map on-receive))
(if on-open (on-open id req)))
@ -701,10 +701,10 @@
;; POST req client -> server is a forward channel
;; session might be nil, when this is the first POST by client
(defn- handle-forward-channel
[req session-agent options]
[req session-agent events options]
(let [[session-agent is-new-session] (if session-agent
[session-agent false]
[(create-session-agent req options) true])
[(create-session-agent req events options) true])
;; maps contains whatever the messages to the server
maps (get-maps req)]
;; if maps were received in this request, we should forward to listeners
@ -743,7 +743,7 @@
;; GET req server->client is a backwardchannel opened by client
(defn- handle-backward-channel
[req session-agent options]
[req session-agent events options]
(let [type (get-in req [:query-params "TYPE"])
rid (get-in req [:query-params "RID"])]
(cond
@ -769,7 +769,7 @@
;; get to /<base>/bind is client->server msg
;; post to /<base>/bind is initiate server->client channel
(defn- handle-bind-channel
[req options]
[req events options]
(let [SID (get-in req [:query-params "SID"])
;; session-agent might be nil, then it will be created by
;; handle-forward-channel.
@ -790,8 +790,8 @@
(when-let [AID (get-in req [:query-params "AID"])]
(send-off session-agent acknowledge-arrays AID)))
(condp = (:request-method req)
:post (handle-forward-channel req session-agent options)
:get (handle-backward-channel req session-agent options))))))
:post (handle-forward-channel req session-agent events options)
:get (handle-backward-channel req session-agent events options))))))
;; straight from google
(def standard-headers
@ -836,26 +836,21 @@
(defn wrap-browserchannel
"adds browserchannel support to a ring handler.
the most important option that all applications will want to provide
is :events. this should be a map of event handler functions:
events should be a map of event handler functions. you only need to
include handler functions for events you care about.
{:on-open (fn [session-id request] ...)
:on-close (fn [session-id request reason] ...)
:on-receive (fn [session-id request data] ...)}
:on-open is called when new client browserchannel sessions are created.
:on-close is called when clients disconnect.
:on-receive is called when data is received from the client via the
forward channel.
for all events, request is a Ring request map and can be used to access
up to date cookie or (http) session data, or other client info. you
cannot use these event handlers to update the client's (http) session
data as no response is returned via these event handler functions.
for other supported options, see
for the supported options, see
net.thegeez.browserchannel.server/default-options"
[handler & [options]]
[handler events & [options]]
(let [options (merge default-options options)
base (str (:base options))]
(-> (fn [req]
@ -867,7 +862,7 @@
(handle-test-channel req options)
(.startsWith uri (str base "/bind"))
(handle-bind-channel req options)
(handle-bind-channel req events options)
:else
(handler req))))

View file

@ -25,31 +25,30 @@
(use-fixtures :each reset-sessions-fixture async-output-fixture)
(defn app
[request & [options]]
[request & {:keys [events options]}]
((-> (fn [{:keys [uri] :as request}]
(if (or (= "" uri)
(= "/" uri))
(response/response "Hello, world!")
(response/not-found "not found")))
(wrap-browserchannel (or options default-options))
(wrap-browserchannel events (or options default-options))
(wrap-test-async-adapter (or options default-options)))
request))
(deftest create-session-test
(let [options default-options
resp (app (-> (mock/request
(let [resp (app (-> (mock/request
:post "/channel/bind")
(mock/query-string
{"VER" protocol-version
"RID" 1
"CVER" protocol-version
"zx" (random-string)
"t" 1}))
options)]
"t" 1})))]
(wait-for-agent-send-offs)
(is (= 200 (:status resp)))
(is (contains-all-of? (:headers resp)
(dissoc (:headers options) "Content-Type")))
(is (contains-all-of?
(:headers resp)
(dissoc (:headers default-options) "Content-Type")))
(let [arrays (get-response-arrays (:body resp))
[[id [c session-id host-prefix version]]] (first arrays)]
(is (= "c" c))
@ -111,9 +110,8 @@
session-id))
(deftest create-session-and-open-backchannel-test
(let [options default-options
; 1. forwardchannel request to create session
create-resp (app (->new-session-request) options)
(let [; 1. forwardchannel request to create session
create-resp (app (->new-session-request))
session-id (get-session-id create-resp)
; 2. backchannel request (long request, async response)
back-resp (app (-> (mock/request
@ -136,8 +134,9 @@
(is (= 200 (:status async-resp)))
; 4. verify session is still connected, backchannel request still alive
(is (not (:closed? async-resp)))
(is (contains-all-of? (:headers async-resp)
(dissoc (:headers options) "Content-Type")))
(is (contains-all-of?
(:headers async-resp)
(dissoc (:headers default-options) "Content-Type")))
(is (connected? session-id))
(is (:has-back-channel? status)))))
@ -157,9 +156,8 @@
"t" 1})))
(deftest send-data-to-client-test
(let [options default-options
; 1. forwardchannel request to create session
create-resp (app (->new-session-request) options)
(let [; 1. forwardchannel request to create session
create-resp (app (->new-session-request))
session-id (get-session-id create-resp)
; 2. backchannel request (long request, async response)
back-resp (app (->new-backchannel-request session-id))]
@ -173,16 +171,16 @@
(is (= 200 (:status back-resp)))
(is (= 200 (:status async-resp)))
(is (not (:closed? async-resp)))
(is (contains-all-of? (:headers async-resp)
(dissoc (:headers options) "Content-Type")))
(is (contains-all-of?
(:headers async-resp)
(dissoc (:headers default-options) "Content-Type")))
; 5. verify data was sent
(is (= (-> arrays ffirst second (get "__edn") (edn/read-string))
{:foo "bar"})))))
(deftest send-multiple-data-to-client-test
(let [options default-options
; 1. forwardchannel request to create session
create-resp (app (->new-session-request) options)
(let [; 1. forwardchannel request to create session
create-resp (app (->new-session-request))
session-id (get-session-id create-resp)
; 2. backchannel request (long request, async response)
back-resp (app (->new-backchannel-request session-id))]
@ -197,8 +195,9 @@
(is (= 200 (:status back-resp)))
(is (= 200 (:status async-resp)))
(is (not (:closed? async-resp)))
(is (contains-all-of? (:headers async-resp)
(dissoc (:headers options) "Content-Type")))
(is (contains-all-of?
(:headers async-resp)
(dissoc (:headers default-options) "Content-Type")))
; 5. verify data was sent
(is (= (get-edn-from-arrays arrays 0)
{:foo "bar"}))
@ -206,9 +205,8 @@
"hello, world")))))
(deftest send-data-to-client-before-backchannel-created
(let [options default-options
; 1. forwardchannel request to create session
create-resp (app (->new-session-request) options)
(let [; 1. forwardchannel request to create session
create-resp (app (->new-session-request))
session-id (get-session-id create-resp)]
(wait-for-agent-send-offs)
; 2. send data to client. no backchannel yet, so it should be queued in a buffer
@ -224,16 +222,16 @@
(is (= 200 (:status back-resp)))
(is (= 200 (:status async-resp)))
(is (not (:closed? async-resp)))
(is (contains-all-of? (:headers async-resp)
(dissoc (:headers options) "Content-Type")))
(is (contains-all-of?
(:headers async-resp)
(dissoc (:headers default-options) "Content-Type")))
; 5. verify data was sent
(is (= (get-edn-from-arrays arrays 0)
{:foo "bar"}))))))
(deftest send-data-to-client-after-backchannel-reconnect
(let [options default-options
; 1. forwardchannel request to create session
create-resp (app (->new-session-request) options)
(let [; 1. forwardchannel request to create session
create-resp (app (->new-session-request))
session-id (get-session-id create-resp)
; 2. backchannel request (long request, async response)
back-resp (app (->new-backchannel-request session-id))]
@ -272,10 +270,10 @@
(let [options (-> default-options
(assoc :keep-alive-interval 2))
; 1. forwardchannel request to create session
create-resp (app (->new-session-request) options)
create-resp (app (->new-session-request) :options options)
session-id (get-session-id create-resp)
; 2. backchannel request (long request, async response)
back-resp (app (->new-backchannel-request session-id))]
back-resp (app (->new-backchannel-request session-id) :options options)]
(wait-for-agent-send-offs)
(is (:heartbeat-timeout @(get @sessions session-id)))
; 3. wait for heartbeat/keepalive interval to elapse
@ -286,7 +284,8 @@
(is (= 200 (:status back-resp)))
(is (= 200 (:status async-resp)))
(is (not (:closed? async-resp)))
(is (contains-all-of? (:headers async-resp)
(is (contains-all-of?
(:headers async-resp)
(dissoc (:headers options) "Content-Type")))
; 5. verify data was sent ("noop" is the standard keepalive message that is sent)
(is (= (get-raw-from-arrays arrays 0)
@ -296,7 +295,7 @@
(let [options (-> default-options
(assoc :keep-alive-interval 2))
; 1. forwardchannel request to create session
create-resp (app (->new-session-request) options)
create-resp (app (->new-session-request) :options options)
session-id (get-session-id create-resp)]
(wait-for-agent-send-offs)
(is (connected? session-id))
@ -311,10 +310,10 @@
(let [options (-> default-options
(assoc :keep-alive-interval 2))
; 1. forwardchannel request to create session
create-resp (app (->new-session-request) options)
create-resp (app (->new-session-request) :options options)
session-id (get-session-id create-resp)
; 2. backchannel request (long request, async response)
back-resp (app (->new-backchannel-request session-id))]
back-resp (app (->new-backchannel-request session-id) :options options)]
(wait-for-agent-send-offs)
(is (:heartbeat-timeout @(get @sessions session-id)))
; 3. close backchannel. we do it via clear-back-channel directly as it better
@ -331,7 +330,7 @@
; reset async response output. hacky necessity due to the way we capture this output
(reset! async-output {})
; 5. second backchannel request
(let [back-resp (app (->new-backchannel-request session-id))]
(let [back-resp (app (->new-backchannel-request session-id) :options options)]
(wait-for-agent-send-offs)
(is (:heartbeat-timeout @(get @sessions session-id)))
; 6. wait for heartbeat/keepalive interval to elapse
@ -350,7 +349,7 @@
(let [options (-> default-options
(assoc :session-timeout-interval 3))
; 1. forwardchannel request to create session
create-resp (app (->new-session-request) options)
create-resp (app (->new-session-request) :options options)
session-id (get-session-id create-resp)]
(wait-for-agent-send-offs)
(is (connected? session-id))
@ -364,10 +363,10 @@
(let [options (-> default-options
(assoc :session-timeout-interval 3))
; 1. forwardchannel request to create session
create-resp (app (->new-session-request) options)
create-resp (app (->new-session-request) :options options)
session-id (get-session-id create-resp)
; 2. backchannel request (long request, async response)
back-resp (app (->new-backchannel-request session-id))]
back-resp (app (->new-backchannel-request session-id) :options options)]
(wait-for-agent-send-offs)
(is (connected? session-id))
(is (not (:session-timeout @(get @sessions session-id))))
@ -379,9 +378,8 @@
(is (not (:session-timeout @(get @sessions session-id))))))
(deftest session-timeout-is-reactivated-after-backchannel-close-test
(let [options default-options
; 1. forwardchannel request to create session
create-resp (app (->new-session-request) options)
(let [; 1. forwardchannel request to create session
create-resp (app (->new-session-request))
session-id (get-session-id create-resp)
; 2. backchannel request (long request, async response)
back-resp (app (->new-backchannel-request session-id))]
@ -406,14 +404,13 @@
(swap! event-output assoc :on-close {:session-id session-id
:request request
:reason reason}))
options (-> default-options
(assoc :events {:on-open on-open
:on-close on-close}))
events {:on-open on-open
:on-close on-close}
; 1. forwardchannel request to create session
create-resp (app (->new-session-request) options)
create-resp (app (->new-session-request) :events events)
session-id (get-session-id create-resp)
; 2. backchannel request (long request, async response)
back-resp (app (->new-backchannel-request session-id))]
back-resp (app (->new-backchannel-request session-id) :events events)]
(wait-for-agent-send-offs)
; 3. verify that on-open event was fired and was passed correct arguments
; (see on-open function defined above)
@ -441,13 +438,12 @@
(swap! received conj {:session-id session-id
:request request
:data data}))
options (-> default-options
(assoc :events {:on-receive on-receive}))
events {:on-receive on-receive}
; 1. forwardchannel request to create session
create-resp (app (->new-session-request) options)
create-resp (app (->new-session-request) :events events)
session-id (get-session-id create-resp)
; 2. backchannel request (long request, async response)
back-resp (app (->new-backchannel-request session-id))]
back-resp (app (->new-backchannel-request session-id) :events events)]
(wait-for-agent-send-offs)
; 3. forwardchannel request so simulate sending data from client to server
(let [forward-resp (app (-> (mock/request
@ -463,7 +459,8 @@
(mock/body
{"count" "1"
"ofs" "0"
"req0___edn" (pr-str {:foo "bar"})})))
"req0___edn" (pr-str {:foo "bar"})}))
:events events)
; 4. parse response body
[len status] (string/split (:body forward-resp) #"\n" 2)
[backchannel-present last-bkch-array-id outstanding-bytes] (json/parse-string status)]
@ -491,13 +488,12 @@
(swap! received conj {:session-id session-id
:request request
:data data}))
options (-> default-options
(assoc :events {:on-receive on-receive}))
events {:on-receive on-receive}
; 1. forwardchannel request to create session
create-resp (app (->new-session-request) options)
create-resp (app (->new-session-request) :events events)
session-id (get-session-id create-resp)
; 2. backchannel request (long request, async response)
back-resp (app (->new-backchannel-request session-id))]
back-resp (app (->new-backchannel-request session-id) :events events)]
(wait-for-agent-send-offs)
; 3. forwardchannel request so simulate sending data from client to server
; (2 maps included)
@ -515,7 +511,8 @@
{"count" "2"
"ofs" "0"
"req0___edn" (pr-str {:foo "bar"})
"req1___edn" (pr-str {:second "map"})})))
"req1___edn" (pr-str {:second "map"})}))
:events events)
; 4. parse response body
[len status] (string/split (:body forward-resp) #"\n" 2)
[backchannel-present last-bkch-array-id outstanding-bytes] (json/parse-string status)]
@ -577,20 +574,19 @@
(let [received (atom [])
on-receive (fn [session-id request data]
(swap! received conj data))
options (-> default-options
(assoc :events {:on-receive on-receive}))
events {:on-receive on-receive}
; 1. forwardchannel request to create session
create-resp (app (->new-session-request) options)
create-resp (app (->new-session-request) :events events)
session-id (get-session-id create-resp)
; 2. backchannel request (long request, async response)
back-resp (app (->new-backchannel-request session-id))]
back-resp (app (->new-backchannel-request session-id) :events events)]
(wait-for-agent-send-offs)
; 3. forwardchannel request to send data from client to server
(let [forward-resp (app (->new-forwardchannel-request session-id 0 "hello 1"))
(let [forward-resp (app (->new-forwardchannel-request session-id 0 "hello 1") :events events)
result1 (parse-forward-response forward-resp)]
(wait-for-agent-send-offs)
; 4. a second forwardchannel request to send more data from client to server
(let [forward2-resp (app (->new-forwardchannel-request session-id 0 "hello 2"))
(let [forward2-resp (app (->new-forwardchannel-request session-id 0 "hello 2") :events events)
result2 (parse-forward-response forward2-resp)]
(wait-for-agent-send-offs)
(is (= 0 (:last-bkch-array-id result1)))
@ -609,14 +605,13 @@
(let [received (atom [])
on-receive (fn [session-id request data]
(swap! received conj data))
options (-> default-options
(assoc :events {:on-receive on-receive}))
events {:on-receive on-receive}
; 1. forwardchannel request to create session
create-resp (app (->new-session-request) options)
create-resp (app (->new-session-request) :events events)
session-id (get-session-id create-resp)]
(wait-for-agent-send-offs)
; 2. forwardchannel request to send data from client to server
(let [forward-resp (app (->new-forwardchannel-request session-id 0 :foobar))
(let [forward-resp (app (->new-forwardchannel-request session-id 0 :foobar) :events events)
result (parse-forward-response forward-resp)]
(wait-for-agent-send-offs)
(is (= 0 (:backchannel-present result)))
@ -635,8 +630,7 @@
(swap! received conj {:session-id session-id
:request request
:data data}))
options (-> default-options
(assoc :events {:on-receive on-receive}))
events {:on-receive on-receive}
; 1. forwardchannel request to create session. encode a map to be sent as part
; of this request (encoded in same format as any other forwardchannel request
; would do to send client->server data)
@ -652,7 +646,7 @@
{"count" "1"
"ofs" "0"
"req0___edn" (pr-str {:msg "hello, world"})}))
options)
:events events)
session-id (get-session-id create-resp)]
(wait-for-agent-send-offs)
(is (connected? session-id))
@ -668,10 +662,9 @@
(let [received (atom [])
on-receive (fn [session-id request data]
(swap! received conj data))
options (-> default-options
(assoc :events {:on-receive on-receive}))
events {:on-receive on-receive}
; 1. forwardchannel request to create session
create-resp (app (->new-session-request) options)
create-resp (app (->new-session-request) :events events)
session-id (get-session-id create-resp)
str-to-send "this will get queued but not sent because no backchannel is open"
queued-str (pr-str (encode-map str-to-send))]
@ -680,7 +673,7 @@
(send-data session-id str-to-send)
(wait-for-agent-send-offs)
; 3. forwardchannel request to send data from client to server
(let [forward-resp (app (->new-forwardchannel-request session-id 0 42))
(let [forward-resp (app (->new-forwardchannel-request session-id 0 42) :events events)
result (parse-forward-response forward-resp)]
(wait-for-agent-send-offs)
; 4. verify that the response to the forwardchannel request contained correct backchannel
@ -707,7 +700,7 @@
options (-> default-options
(assoc :fail-fn fail-fn))
; 2. forwardchannel request to create session
create-resp (app (->new-session-request) options)
create-resp (app (->new-session-request) :options options)
session-id (get-session-id create-resp)]
(wait-for-agent-send-offs)
; 3. queue up a bunch of data to be sent. no backchannel has been created yet,
@ -718,7 +711,7 @@
(send-data session-id "fail")
(send-data session-id :fourth-queued)
; 4. backchannel request (long request, async response)
(let [back-resp (app (->new-backchannel-request session-id) options)]
(let [back-resp (app (->new-backchannel-request session-id) :options options)]
(wait-for-agent-send-offs)
; 5. an exception should have been thrown during the flush-buffer call, but
; we should have sent out some of the data before then
@ -735,7 +728,7 @@
(reset! async-output {})
; 7. re-open backchannel. all 4 items should still be in the buffer and will
; get flushed again at this point
(let [back-resp (app (->new-backchannel-request session-id) options)]
(let [back-resp (app (->new-backchannel-request session-id) :options options)]
(wait-for-agent-send-offs)
; 8. at this point, all 4 items should have been sent and the backchannel request
; should still be active

View file

@ -12,13 +12,13 @@
(use-fixtures :each async-output-fixture)
(defn app
[request & [options]]
[request & {:keys [events options]}]
((-> (fn [{:keys [uri] :as request}]
(if (or (= "" uri)
(= "/" uri))
(response/response "Hello, world!")
(response/not-found "not found")))
(wrap-browserchannel (or options default-options))
(wrap-browserchannel events (or options default-options))
(wrap-test-async-adapter (or options default-options)))
request))
@ -59,7 +59,7 @@
"MODE" "init"
"zx" (random-string)
"t" 1})
options)
:options options)
body (json/parse-string (:body resp))]
(is (= 200 (:status resp)))
(is (contains-all-of? (:headers resp) (:headers default-options)))