Ring adapter(HTTP server) with async and websocket extenstion

(:use org.httpkit.server)

The server uses an event-driven, non-blocking I/O model that makes it lightweight and scalable. It's written to conform to the standard Clojure web server Ring spec, with asynchronous and websocket extenstion. HTTP Kit is (almost) drop-in replacement of ring-jetty-adapter

A new unified Async/Websocket API(working in progress)

Hello, Clojure HTTP server

run-server starts a Ring-compatible HTTP server, routing using compojure

(defn app [req]
  {:status  200
   :headers {"Content-Type" "text/html"}
   :body    "hello HTTP!"})
(run-server app {:port 8080})

Options:

  • :ip: which IP to bind, default to 0.0.0.0
  • :port: which port listens incomming request, default to 8090
  • :thread: How many threads to compute response from request, default to 4
  • :worker-name-prefix: woker thread name prefix, default to worker-: worker-1 worker-2....
  • :queue-size: max requests queued waiting for threadpool to compute response before reject, 503(Service Unavailable) is returned to client if queue is full, default to 20K
  • :max-body: length limit for request body in bytes, 413(Request Entity Too Large) is returned if exceeds this limit, default to 8388608(8M)
  • :max-line: length limit for HTTP inital line and per header, 414(Request-URI Too Long) will be returned if exceeds this limit, default to 4096(4K), relevant discusstion on stackoverflow

Websocket extenstion

  • if-ws-request or when-ws-request: get the websocket connection, returns websocket handshake
  • on-mesg: register a fn to be called when there is string message from client
  • send-mesg: send string message to client. Clojure data can be converted to string using JSON or edn
  • close-conn: close the websocket connection
  • on-close: register a fn to be called when the connecton is closed

For WebSocket Secure connection, one option is stud (self-signed certificate may not work with websocket)

A realtime chart example: https://github.com/http-kit/chat-websocket

(defn chat-handler [req]
  (when-ws-request req ws-con  ; ws-con bind to the websocket connection
     (on-mesg ws-con (fn [msg]
                         (send-mesg ws-con msg) ;; echo back
                         (close-conn ws-con)))  ;; close the connection
     (on-close ws-con (fn [status] (println ws-con "closed")))))

(run-server chat-handler {:port 8080})

Asynchronous (long polling) extenstion

respond accept a standard ring-response (:status :headers :body) or just the :body

All request-level middlewares applied to req before passing to aync-handler (like any other handler)

The response is sent to client without response-level middlewares applied (a limitation, suggestions welcome)

Optional timeout facilities: timer

A realtime chart example: https://github.com/http-kit/chat-polling

(defn async-handler [req]
  ; respond is a one-time function, can be called on any thread to send the response to client
  (async-response respond
                  ; just :body, can also be {:status _ :headers _ :body _}
                  (future (respond "hello world async"))))

(run-server async-handler {:port 8080})

Routing with Compojure

Compojure Can be used to do the routing, based on uri and method

(:use [compojure.route :only [files not-found]]
      [compojure.handler :only [site]] ; form, query params decode; cookie; session, etc
      [compojure.core :only [defroutes GET POST DELETE ANY context]]
      org.httpkit.server)

(defn show-landing-page [req] ;; ordinary clojure function, accept request map
  ;; return landing page's html string. possible template library:
  ;; mustache (https://github.com/shenfeng/mustache.clj, https://github.com/fhd/clostache...)
  ;; enlive (https://github.com/cgrand/enlive),  hiccup(https://github.com/weavejester/hiccup)
  )

(defn update-userinfo [req]          ;; ordinary clojure function
  (let [user-id (-> req :params :id)    ; param from uri
        password (-> req :params :password)] ; form param
    ....
    ))

(defroutes all-routes
  (GET "/" [] show-landing-page)
  (GET "/ws" [] chat-handler)     ;; websocket
  (GET "/async" [] async-handler) ;; asynchronous(long polling)
  (context "/user/:id" []
           (GET / [] get-user-by-id)
           (POST / [] update-userinfo))
  (route/files "/static/") ;; static file url prefix /static, in `public` folder
  (route/not-found "<p>Page not found.</p>")) ;; all other, return 404

(run-server (site #'all-routes) {:port 8080})
  

Recommended server deployment

http-kit runs alone happly, handy for development and quick deployment. Use reverse proxy like Nginx, Lighthttpd, etc in serious production is encouraged. They also add https support.

  • They are fast, heavily optimized for static content.
  • They can be configured to compress the content sent to browsers

Sample Nginx configration:

upstream http_backend {
    server 127.0.0.1:8090;  # http-kit listen on 8090
    # keepalive(resue TCP connection) improves performance
    keepalive 32;  # both http-kit and nginx are good at concurrency
}

server {
    location /static/ {  # static content
        alias   /var/www/xxxx/public/;
    }
    location / {
        proxy_pass  http://http_backend;

        # tell http-kit to keep the connection
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;

        access_log  /var/log/nginx/xxxx.access.log;
    }
}