From 6674d171c3b781396b2ef0429acfe8f8cc856abe Mon Sep 17 00:00:00 2001 From: gered Date: Mon, 9 May 2016 12:45:17 -0400 Subject: [PATCH] update client & server send fn's to allow sending even non-map data i personally think this was an important change as the default encoding of maps to be sent as json is somewhat lossy (keywords converted to strings, would never be able to get this 100% perfect using json alone without a more complex encoding solution). encoding to edn is the perfect solution to this. since browserchannel pretty much requires the final sent data to be json encoded, we just serialize the clojure data being sent to an edn string within a root json object. this does ultimately mean we have to do a little bit of pre-parsing of sent and received maps to encode/decode properly and make it completely transparent to application code, but i think it's an acceptable tradeoff. --- chat-demo/src/chat_demo/client.cljs | 9 ++-- chat-demo/src/chat_demo/server.clj | 6 +-- .../net/thegeez/browserchannel/client.cljs | 47 +++++++++++++------ .../src/net/thegeez/browserchannel/server.clj | 46 +++++++++++------- 4 files changed, 70 insertions(+), 38 deletions(-) diff --git a/chat-demo/src/chat_demo/client.cljs b/chat-demo/src/chat_demo/client.cljs index 66c30d1..235cae6 100644 --- a/chat-demo/src/chat_demo/client.cljs +++ b/chat-demo/src/chat_demo/client.cljs @@ -9,7 +9,7 @@ (enable-console-print!) (defn say [text] - (browserchannel/send-map {:msg text})) + (browserchannel/send-data {:msg text})) (defn toggle-element [elem] (dom/toggle-attr! elem :disabled (not (dom/attr elem :disabled)))) @@ -37,10 +37,9 @@ :on-receive (fn [data] - (let [msg (get data "msg")] - (dom/append! (by-id "room") - (-> (dom/create-element "div") - (dom/set-text! (str "MSG::" msg))))))}) + (dom/append! (by-id "room") + (-> (dom/create-element "div") + (dom/set-text! (str "MSG::" (:msg data))))))}) (defn ^:export run [] (browserchannel/init! event-handlers)) \ No newline at end of file diff --git a/chat-demo/src/chat_demo/server.clj b/chat-demo/src/chat_demo/server.clj index 0ebd153..2c13ce3 100644 --- a/chat-demo/src/chat_demo/server.clj +++ b/chat-demo/src/chat_demo/server.clj @@ -16,17 +16,17 @@ {:on-open (fn [session-id request] (println "client" session-id "connected") - (browserchannel/send-map-to-all {"msg" (str "client " session-id " connected")})) + (browserchannel/send-data-to-all {:msg (str "client " session-id " connected")})) :on-close (fn [session-id request reason] (println "client" session-id "disconnected. reason: " reason) - (browserchannel/send-map-to-all {"msg" (str "client " session-id " disconnected. reason: " reason)})) + (browserchannel/send-data-to-all {:msg (str "client " session-id " disconnected. reason: " reason)})) :on-receive (fn [session-id request m] (println "client" session-id "sent" m) - (browserchannel/send-map-to-all m))}) + (browserchannel/send-data-to-all m))}) (def app-routes (routes diff --git a/clj-browserchannel/src/net/thegeez/browserchannel/client.cljs b/clj-browserchannel/src/net/thegeez/browserchannel/client.cljs index 1822ac1..928bb71 100644 --- a/clj-browserchannel/src/net/thegeez/browserchannel/client.cljs +++ b/clj-browserchannel/src/net/thegeez/browserchannel/client.cljs @@ -1,5 +1,6 @@ (ns net.thegeez.browserchannel.client (:require + [cljs.reader :as reader] [dommy.core :refer-macros [sel1]] goog.net.BrowserChannel goog.net.BrowserChannel.Handler @@ -33,15 +34,32 @@ 11 :bad-response 12 :active-x-blocked}) -(defn- queued-map->clj - [queued-map] - {:context (aget queued-map "context") - :map (js->clj (aget queued-map "map")) - :map-id (aget queued-map "mapId")}) +(defn encode-map + [data] + (doto (js-obj) + (aset "__edn" (pr-str data)))) -(defn- array-of-queued-map->clj +(defn decode-map + [m] + (let [m (js->clj m)] + (if (contains? m "__edn") + (reader/read-string (str (get m "__edn"))) + m))) + +(defn decode-queued-map + [queued-map] + (let [m (js->clj queued-map) + data (get m "map")] + (merge + {:context (get m "context") + :map-id (get m "mapId")} + (if (contains? data "__edn") + {:data (reader/read-string (str (get data "__edn")))} + {:map data})))) + +(defn decode-queued-map-array [queued-map-array] - (mapv queued-map->clj queued-map-array)) + (mapv decode-queued-map queued-map-array)) (defn channel-state [] (.getState channel)) @@ -56,9 +74,10 @@ (.setLevel level) (.addHandler (or f #(js/console.log %))))) -(defn send-map - [m & [{:keys [on-success]}]] - (.sendMap channel (clj->js m) {:on-success on-success})) +(defn send-data + [data & [{:keys [on-success]}]] + (if data + (.sendMap channel (encode-map data) {:on-success on-success}))) (defn connect! [& [{:keys [base] :as options}]] @@ -89,16 +108,16 @@ (set! (.-channelClosed handler) (fn [ch pending undelivered] (if on-close - (on-close (array-of-queued-map->clj pending) - (array-of-queued-map->clj undelivered))))) + (on-close (decode-queued-map-array pending) + (decode-queued-map-array undelivered))))) (set! (.-channelHandleArray handler) (fn [ch m] (if on-receive - (on-receive (js->clj m))))) + (on-receive (decode-map m))))) (set! (.-channelSuccess handler) (fn [ch delivered] (if on-sent - (on-sent (array-of-queued-map->clj delivered))) + (on-sent (decode-queued-map-array delivered))) (doseq [m delivered] (let [{:keys [on-success] :as context} (aget m "context")] (if on-success diff --git a/clj-browserchannel/src/net/thegeez/browserchannel/server.clj b/clj-browserchannel/src/net/thegeez/browserchannel/server.clj index f12e968..03f165f 100644 --- a/clj-browserchannel/src/net/thegeez/browserchannel/server.clj +++ b/clj-browserchannel/src/net/thegeez/browserchannel/server.clj @@ -3,8 +3,9 @@ (:import [java.util.concurrent ScheduledExecutorService Executors TimeUnit Callable ScheduledFuture]) (:require - [ring.middleware.params :as params] + [clojure.edn :as edn] [clojure.data.json :as json] + [ring.middleware.params :as params] [net.thegeez.browserchannel.async-adapter :as async-adapter])) ;; @todo: out of order acks and maps - AKH the maps at least is taken care of. @@ -139,6 +140,16 @@ [p] (str "[" (first p) "," (second p) "]")) +(defn- decode-map + [m] + (if (contains? m "__edn") + (edn/read-string (get m "__edn")) + m)) + +(defn- encode-map + [data] + {"__edn" (pr-str data)}) + ;;;;;; end of utils @@ -583,25 +594,27 @@ ;; convience function to send data to a session ;; the data will be queued until there is a backchannel to send it ;; over -(defn- send-string - [session-id string] +(defn- send-map + [session-id m] (when-let [session-agent (get @sessions session-id)] - (send-off session-agent #(-> % - (queue-string string) - flush-buffer)))) + (let [string (json/write-str m)] + (send-off session-agent #(-> % + (queue-string string) + flush-buffer))))) -(defn send-map - "sends a map to the client identified by session-id over the backchannel. +(defn send-data + "sends data to the client identified by session-id over the backchannel. if there is currently no available backchannel for this client, the data is queued until one is available." - [session-id m] - (send-string session-id (json/write-str m))) + [session-id data] + (if data + (send-map session-id (encode-map data)))) -(defn send-map-to-all - "sends a map to all currently connected clients over their backchannels." - [m] +(defn send-data-to-all + "sends data to all currently connected clients over their backchannels." + [data] (doseq [[session-id _] @sessions] - (send-map session-id m))) + (send-data session-id data))) (defn connected? "returns true if a client with the given session-id is currently connected." @@ -714,8 +727,9 @@ ;; backchannelPresent = 0 for false, 1 for true ;; send as json for XHR and IE (do - (doseq [map maps] - (notify-listeners (:id @session-agent) req :map map)) + (doseq [m maps] + (let [decoded (decode-map m)] + (notify-listeners (:id @session-agent) req :map decoded))) (let [status (session-status @session-agent)] {:status 200 :headers (:headers options)