Skip to content

Instantly share code, notes, and snippets.

@andrewvc
Created September 13, 2012 15:34
Show Gist options
  • Save andrewvc/3715123 to your computer and use it in GitHub Desktop.
Save andrewvc/3715123 to your computer and use it in GitHub Desktop.

Noir Async!

Why?

  • Async HTTP now common, it should be easy in clojure!
  • Lamina is probably my favorite clojure lib, but it's hard to use directly
  • The existing interfaces bridging Noir and Lamina are tricky

Let's make something simple!

; Plain Noir
(defpage "/route" []
  "I am plain")

; Noir-async
(defpage-async "/async-route" [] conn
  (async-push "I am slightly fancier"))

Things to note

  • Explicit connection object (great for passing between threads)
  • Explicit push through the connection
  • sync and async can live side-by-side

Let's get a little fancier

; A delayed response
(defpage-async "/route" [] conn
  (set-timeout 3000 #(async-push "Why hello!")))

Things to note

  • set-timeout is provided by noir-async.utils
  • Since set-timeout uses a timer, this is returned from a separate thread

Let's blow some chunks

; An HTTP chunked response
(defpage-async "/always-chunky" [] conn
  (async-push conn {:status 200 :chunked true})
  (async-push conn "chunk one")
  (async-push conn "chunk two")
  (close-connection conn))

Things to note

  • We send the initial header using the map syntax, marking :chunked true
  • We must explicitly close the connection

A Websocket echo

(defpage-async "/echo" [] conn
  (on-receive conn (fn [m] (async-push conn m))))

Things to note

  • on-receive sets up a callback
  • messages are plain strings

A Final, Super-Fancy Example

(require ['noir-async :as 'na])
(use 'lamina.core)
(na/defpage-async "/river" {} conn
  (when (not (na/websocket? conn)) ; Send chunked header to non-websockets
    (na/async-push conn {:status 200 :chunked true}))
  
  ;; Send some inital messages after connect
  (na/async-push
   conn
   (json-chunk {"entity" "system" "name" "current-nodes" "body" (nmgr/json-nodes)}))
  (na/async-push
   conn
   (json-chunk {"entity" "system" "name" "current-job" "body" @jmgr/current-job}))
  
  ;; Redirect a lamina stream straight into the socket via lamina.core/siphon
  (let [output (na/writable-channel conn)]
    (siphon (map* json-chunk ctrl/emitter) output)
    (siphon (map* json-chunk jmgr/emitter) output)))

Design Decisions

  • Started as an extraction from actual projects
  • Attempted to reduce API to as few methods as possible
  • Used explicit conn object rather than a binding for threadsafety
  • Lamina is overkill for most people
  • Callbacks are simpler than channels
  • Nothing removed (channels can be pulled out of conn)

Find out more

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment