Permalink
Browse files

For ring-jetty-adapter to ring-jetty-async-adapter, adding support fo…

…r async HTTP and WebSockets.
  • Loading branch information...
1 parent 160b8b0 commit 96232b747752e8620aa588273331a80902aebd92 @mmcgrana committed Nov 1, 2010
View
8 ring-jetty-async-adapter/project.clj
@@ -0,0 +1,8 @@
+(defproject ring/ring-jetty-async-adapter "0.3.3-SNAPSHOT"
+ :description "Ring Jetty adapter, with async HTTP and WebSockets support."
+ :url "http://github.com/mmcgrana/ring"
+ :dependencies [[ring/ring-core "0.3.3" :exclusions [javax.servlet/servlet-api]]
+ [ring/ring-servlet "0.3.3" :exclusions [javax.servlet/servlet-api]]
+ [org.eclipse.jetty/jetty-server "8.0.0.M1"]
+ [org.eclipse.jetty/jetty-websocket "8.0.0.M1"]]
+ :dev-dependencies [[clj-http "0.1.1"]])
View
129 ring-jetty-async-adapter/src/ring/adapter/jetty_async.clj
@@ -0,0 +1,129 @@
+(ns ring.adapter.jetty-async
+ "Adapter for the Jetty webserver, with async HTTP and WebSockets support."
+ (:import (org.eclipse.jetty.websocket WebSocketFactory WebSocket WebSocket$Outbound)
+ (org.eclipse.jetty.server.handler AbstractHandler)
+ (org.eclipse.jetty.server Server Request Response)
+ (org.eclipse.jetty.server.bio SocketConnector)
+ (org.eclipse.jetty.server.ssl SslSocketConnector)
+ (javax.servlet.http HttpServletRequest))
+ (:require [ring.util.servlet :as servlet]))
+
+(defn- proxy-websocket
+ "Create a Jetty WebSocket implementation for the given Ring channel handler."
+ [channel-handler]
+ (let [outbound (atom nil)
+ reactor
+ (fn [{:keys [type data]}]
+ (case type
+ :disconnect
+ (if-let [^WebSocket$Outbound out @outbound]
+ (.disconnect out))
+ :message
+ (let [^WebSocket$Outbound out @outbound]
+ (if out
+ (.sendMessage out (byte 0) ^String data)
+ (throw (Exception. "no connection"))))))
+ channel (channel-handler reactor)]
+ (proxy [WebSocket] []
+ (onConnect [out]
+ (swap! outbound (constantly out))
+ (channel {:type :connect}))
+ (onDisconnect []
+ (swap! outbound (constantly nil))
+ (channel {:type :disconnect}))
+ (onMessage
+ ([frame data]
+ (channel {:type :message :data data}))
+ ([frame bdata offset length]
+ (throw (Exception. "not yet implemented")))))))
+
+(defn- proxy-handler
+ "Returns an Jetty Handler implementation for the given Ring handler."
+ [handler]
+ (let [buffer-size 8192
+ max-idle -1
+ factory (WebSocketFactory. buffer-size)]
+ (.setMaxIdleTime factory -1)
+ (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)
+ ac (.startAsync request)
+ send (fn [{:keys [type data]}]
+ (case type
+ :status
+ (servlet/set-status (.getResponse ac) data)
+ :headers
+ (servlet/set-headers (.getResponse ac) data)
+ :chunk
+ (let [writer (.getWriter (.getResponse ac))]
+ (.println writer data)
+ (.flush writer))
+ :close
+ (.complete ac)))]
+ (reactor send))
+ :websocket
+ (let [reactor (:reactor response-map)
+ websocket (proxy-websocket reactor)
+ origin (or (get-in request-map [:headers "origin"])
+ (get-in request-map [:headers "host"]))
+ protocol (get-in request-map [:headers "websocket-protocol"])]
+ (.upgrade factory request response websocket origin protocol))))))))
+
+(defn- add-ssl-connector!
+ "Add an SslSocketConnector to a Jetty Server instance."
+ [^Server server options]
+ (let [ssl-connector (SslSocketConnector.)]
+ (doto ssl-connector
+ (.setPort (options :ssl-port 443))
+ (.setKeystore (options :keystore))
+ (.setKeyPassword (options :key-password)))
+ (when (options :truststore)
+ (.setTruststore ssl-connector (options :truststore)))
+ (when (options :trust-password)
+ (.setTrustPassword ssl-connector (options :trust-password)))
+ (.addConnector server ssl-connector)))
+
+(defn- create-server
+ "Construct a Jetty Server instance."
+ [options]
+ (let [connector (doto (SocketConnector.)
+ (.setPort (options :port 80))
+ (.setHost (options :host)))
+ server (doto (Server.)
+ (.addConnector connector)
+ (.setSendDateHeader true))]
+ (when (or (options :ssl?) (options :ssl-port))
+ (add-ssl-connector! server options))
+ 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.
+ :ssl? - Use SSL.
+ :ssl-port - SSL port: defaults to 443, implies :ssl?
+ :keystore
+ :key-password
+ :truststore
+ :trust-password"
+ [handler options]
+ (let [^Server s (create-server (dissoc options :configurator))]
+ (when-let [configurator (:configurator options)]
+ (configurator s))
+ (doto s
+ (.setHandler (proxy-handler handler))
+ (.start))
+ (when (:join? options true)
+ (.join s))
+ s))
View
20 ring-jetty-async-adapter/test/ring/adapter/jetty_async_test.clj
@@ -0,0 +1,20 @@
+(ns ring.adapter.jetty-async-test
+ (:use clojure.test
+ ring.adapter.jetty-async)
+ (:require [clj-http.client :as http]))
+
+(defn- hello-world [request]
+ {:status 200
+ :headers {"Content-Type" "text/plain"}
+ :body "Hello World"})
+
+(deftest jetty-async-test
+ (let [server (run-jetty-async hello-world {:port 4347, :join? false})]
+ (try
+ (Thread/sleep 2000)
+ (let [response (http/get "http://localhost:4347")]
+ (is (= (:status response) 200))
+ (is (.startsWith (get-in response [:headers "content-type"])
+ "text/plain"))
+ (is (= (:body response) "Hello World\n")))
+ (finally (.stop server)))))

0 comments on commit 96232b7

Please sign in to comment.