diff --git a/clj-browserchannel-jetty-adapter/.gitignore b/clj-browserchannel-jetty-adapter/.gitignore new file mode 100644 index 0000000..2c811ef --- /dev/null +++ b/clj-browserchannel-jetty-adapter/.gitignore @@ -0,0 +1,10 @@ +/target +/lib +/classes +/checkouts +pom.xml +*.jar +*.class +.lein-deps-sum +.lein-failures +.lein-plugins \ No newline at end of file diff --git a/clj-browserchannel-jetty-adapter/README.md b/clj-browserchannel-jetty-adapter/README.md new file mode 100644 index 0000000..6ea483c --- /dev/null +++ b/clj-browserchannel-jetty-adapter/README.md @@ -0,0 +1,16 @@ +# clj-browserchannel-jetty-adapter + +Jetty async adapter for BrowserChannel + +## About + +Written by: +Gijs Stuurman / [@thegeez][twt] / [Blog][blog] / [GitHub][github] + +[twt]: http://twitter.com/thegeez +[blog]: http://thegeez.github.com +[github]: https://github.com/thegeez + +### License + +Copyright (c) 2012 Gijs Stuurman and released under an MIT license. diff --git a/clj-browserchannel-jetty-adapter/project.clj b/clj-browserchannel-jetty-adapter/project.clj new file mode 100644 index 0000000..358296a --- /dev/null +++ b/clj-browserchannel-jetty-adapter/project.clj @@ -0,0 +1,9 @@ +(defproject net.thegeez/clj-browserchannel-jetty-adapter "0.0.1" + :description "Jetty async adapter for BrowserChannel" + :url "" + :dependencies [[org.clojure/clojure "1.3.0"] + [ring/ring-core "1.1.0-beta3"] + [ring/ring-servlet "1.1.0-beta3" :exclusions [javax.servlet/servlet-api]] + [org.eclipse.jetty/jetty-server "8.1.2.v20120308"];; includes ssl + [net.thegeez/clj-browserchannel-server "0.0.1"] + ]) diff --git a/clj-browserchannel-jetty-adapter/src/net/thegeez/jetty_async_adapter.clj b/clj-browserchannel-jetty-adapter/src/net/thegeez/jetty_async_adapter.clj new file mode 100644 index 0000000..8e3b605 --- /dev/null +++ b/clj-browserchannel-jetty-adapter/src/net/thegeez/jetty_async_adapter.clj @@ -0,0 +1,92 @@ +(ns net.thegeez.jetty-async-adapter + "BrowserChannel adapter for the Jetty webserver, with async HTTP." + (:import (org.eclipse.jetty.server.handler AbstractHandler) + (org.eclipse.jetty.server Server Request Response) + (org.eclipse.jetty.server.nio SelectChannelConnector) + (org.eclipse.jetty.continuation Continuation ContinuationSupport ContinuationListener) + (org.eclipse.jetty.io EofException) + (javax.servlet.http HttpServletRequest)) + (:require [ring.util.servlet :as servlet] + [net.thegeez.async-adapter :as async-adapter])) + +;; Based on ring-jetty-async-adapter by Mark McGranaghan +;; (https://github.com/mmcgrana/ring/tree/jetty-async) +;; This has failed write support + +(deftype JettyAsyncResponse [continuation] + async-adapter/IAsyncAdapter + (head [this status headers] + (doto (.getServletResponse continuation) + (servlet/set-status status) + (servlet/set-headers (assoc headers + "Transfer-Encoding" "chunked")) + (.flushBuffer))) + (write-chunk [this data] + (doto (.getWriter (.getServletResponse continuation)) + (.write data) + (.flush)) + (when (.checkError (.getWriter (.getServletResponse continuation))) + (throw async-adapter/ConnectionClosedException))) + (close [this] + (.complete continuation))) + + + + +(defn- proxy-handler + "Returns an Jetty Handler implementation for the given Ring handler." + [handler options] + (proxy [AbstractHandler] [] + (handle [target ^Request base-request ^HttpServletRequest request response] + (let [request-map (servlet/build-request-map request) + response-map (handler request-map)] + (condp = (:async response-map) + nil + (do + (servlet/update-servlet-response response response-map) + (.setHandled base-request true)) + :http + (let [reactor (:reactor response-map) + ;; continuation lives until written to! + continuation (.startAsync request) + emit (JettyAsyncResponse. continuation)] + (.addContinuationListener continuation + (proxy [ContinuationListener] [] + (onComplete [c] nil) + (onTimeout [c] + (.complete c)))) + + ;; 4 minutes is google default + (.setTimeout continuation (get options :response-timeout (* 4 60 1000))) + (reactor emit) + )))))) + +(defn- create-server + "Construct a Jetty Server instance." + [options] + (let [connector (doto (SelectChannelConnector.) + (.setPort (options :port 80)) + (.setHost (options :host))) + server (doto (Server.) + (.addConnector connector) + (.setSendDateHeader true))] + server)) + +(defn ^Server run-jetty-async + "Serve the given handler according to the options. + Options: + :configurator - A function called with the Server instance. + :port + :host + :join? - Block the caller: defaults to true. + :response-timeout - Timeout after which the server will close the connection" + [handler options] + (let [^Server s (create-server (dissoc options :configurator))] + (when-let [configurator (:configurator options)] + (configurator s)) + (doto s + (.setHandler (proxy-handler handler options)) + (.start)) + (when (:join? options true) + (.join s)) + s))