Skip to content

Commit

Permalink
Merge pull request #194 from theasp/dev-cljs-server
Browse files Browse the repository at this point in the history
Server adapters for express and dogfort on node.js
  • Loading branch information
ptaoussanis committed Jan 31, 2016
2 parents 88eb8b1 + 4f790b7 commit c69f0ea
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/taoensso/sente/server_adapters/dogfort.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
(ns taoensso.sente.server-adapters.dogfort
"Sente on node.js with dogfort (https://github.com/whamtet/dogfort)"
{:author "Matthew Molloy <whamtet@gmail.com>"}
(:require
[taoensso.sente.server-adapters.generic-node :as generic-node]))

;; Dogfort doesn't need anything special, use use the generic-node-ws
;; adapter.
(def dogfort-adapter generic-node/generic-node-adapter)
(def sente-web-server-adapter dogfort-adapter)
67 changes: 67 additions & 0 deletions src/taoensso/sente/server_adapters/express.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
(ns taoensso.sente.server-adapters.express
"Sente on node.js with express"
{:author "Andrew Phillips <theasp@gmail.com>"}
(:require
[taoensso.sente :as sente]
[taoensso.sente.server-adapters.generic-node :as generic-node]
[taoensso.timbre :as timbre :refer-macros (tracef debugf infof warnf errorf)]))

;; This adapter works differently that the others as sente is
;; expecting ring requests but express uses http.IncomingMessage.
;; While most of this adapter could be used for similar
;; implementations there will be assumptions here that the following
;; express middleware, or equivalents, ae in place:
;; - cookie-parser
;; - body-parser
;; - csurf
;; - express-session
;; - express-ws

;; See example-project for an implementation, as it's a bit
;; different than something built on ring.

(defn- make-ring-req [req res ring-req]
"Emulate req as used by the ring library by processing the "
(let [fake-params (merge (js->clj (.-params req) :keywordize-keys true)
(js->clj (.-body req) :keywordize-keys true)
(js->clj (.-query req) :keywordize-keys true)
(:query ring-req))
ring-req (assoc ring-req
:response res
:body req
:params fake-params)]
(tracef "Emulated ring request: %s" ring-req)
ring-req))


(defn wrap-ring-req [ring-fn req res ring-req]
"Run a function that takes a ring request by converting a
req, res, and fake ring-req map"
(let [ring-req (make-ring-req req res ring-req)]
(ring-fn ring-req)))

(defn csrf-token [ring-req]
"Get a valid token from csurf"
(.csrfToken (:body ring-req)))

(def default-options {:csrf-token-fn csrf-token})

(defn make-express-adapter
"Provide a custom make-channel-socket-server! that wraps calls with
wrap-ring-req as the functions from the real
make-channel-socket-server! require ring-reqs. As a bonus you don't
need to specify the adapter."
[options]
(tracef "Making express adapter")
(let [ch (sente/make-channel-socket-server!
(generic-node/GenericNodeServerChanAdapter.)
(merge default-options options))]

(assoc ch
:ajax-get-or-ws-handshake-fn
(fn [req res & [next ring-req]]
(wrap-ring-req (:ajax-get-or-ws-handshake-fn ch) req res ring-req))

:ajax-post-fn
(fn [req res & [next ring-req]]
(wrap-ring-req (:ajax-post-fn ch) req res ring-req)))))
92 changes: 92 additions & 0 deletions src/taoensso/sente/server_adapters/generic_node.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
(ns taoensso.sente.server-adapters.generic-node
"Sente on node.js using ws and http libraries"
{:author "Andrew Phillips <theasp@gmail.com> & Matthew Molloy <whamtet@gmail.com>"}
(:require
[taoensso.sente.interfaces :as i]
[taoensso.timbre :as timbre :refer-macros (tracef debugf infof warnf errorf)]))

(defn- is-ws-open? [ws]
(= (.-readyState ws)
(.-OPEN ws)))

(deftype GenericNodeWsAdapter [callbacks-map ws]
i/IServerChan
(-sch-send! [this msg close-after-send?]
(let [pre-open? (is-ws-open? ws)]
(try
(.send ws msg)
(catch :default e))
(when close-after-send?
(.close ws))
pre-open?))

(sch-open? [server-ch]
(is-ws-open? ws))

(sch-close! [server-ch]
(let [pre-open? (is-ws-open? ws)]
(.close ws)
pre-open?)))

(defn- make-ws-chan
[{:keys [on-open on-close on-msg] :as callbacks-map} ws]
(tracef "Making websocket adapter")
(let [chan (new GenericNodeWsAdapter callbacks-map ws)]
(on-open chan)
(.on ws "message"
(fn [data flags]
(on-msg chan data)))
(.onclose ws
(fn [code message]
(on-close chan code)))))

(deftype GenericNodeAjaxAdapter [response-open? response]
i/IServerChan
(-sch-send! [this msg close-after-send?]
(let [pre-open? @response-open?]
(if close-after-send?
(do
(reset! response-open? false)
(.end response msg))
(try
(.write response msg)
(catch :default e)))
pre-open?))

(sch-open? [this]
@response-open?)

(sch-close! [this]
(if @response-open?
(.end response))
(reset! response-open? false)))

(defn- make-ajax-chan
[{:keys [on-open on-close on-msg] :as callbacks-map} response body]
(tracef "Making ajax adapter")
(let [response-open? (atom true)
chan (new GenericNodeAjaxAdapter response-open? response)]
(on-open chan)
(.on body "data"
(fn [data]
(on-msg chan data)))
;; TODO: If this is bad, bad, what should we do?
#_(.on response ;bad, bad!
"finish"
#(on-close chan)))
;; For AJAX connections, if we reply blank then the route
;; matcher will fail. dogfort will send a 404 response and
;; close the connection. to keep it open we just send this
;; instead of a ring response. This should have no
;; detrimental effect on other servers.
{:keep-alive true})

(deftype GenericNodeServerChanAdapter []
i/IServerChanAdapter
(ring-req->server-ch-resp [server-ch-adapter ring-req callbacks-map]
(let [{:keys [websocket response body]} ring-req]
(if websocket
(make-ws-chan callbacks-map websocket)
(make-ajax-chan callbacks-map response body)))))

(def generic-node-adapter (GenericNodeServerChanAdapter.))

0 comments on commit c69f0ea

Please sign in to comment.