Skip to content

Commit

Permalink
Initial refactor towards 0.1.
Browse files Browse the repository at this point in the history
  • Loading branch information
mmcgrana committed Jul 11, 2009
1 parent 24fe773 commit cbeebac
Show file tree
Hide file tree
Showing 33 changed files with 330 additions and 340 deletions.
2 changes: 1 addition & 1 deletion .gitignore
@@ -1 +1 @@
bundle bundle
85 changes: 47 additions & 38 deletions README.textile
@@ -1,51 +1,68 @@
h1. Ring h1. Ring


Ring is a Clojure web applications library inspired by Python's WSGI and Ruby's Rack. By abstracting the details of HTTP into a simple, unified API, Ring allows web applications to be constructed of modular components that can be shared among a variety of applications, web servers, and web frameworks. Ring is a Clojure web applications library inspired by Python's WSGI and Ruby's
Rack. By abstracting the details of HTTP into a simple, unified API, Ring allows
web applications to be constructed of modular components that can be shared among
a variety of applications, web servers, and web frameworks.


The @SPEC@ file at the root of this distribution for provides a complete description of the Ring interface. The @SPEC@ file at the root of this distribution for provides a complete
description of the Ring interface.


h2. Quick Setup h2. Quick Start


First clone the Ring source: First clone the Ring source and download Ring's dependencies:


git clone git://github.com/mmcgrana/ring.git git clone git://github.com/mmcgrana/ring.git
cd ring cd ring

Then download the needed dependencies:

ant deps ant deps


The @deps@ ant target downloads all Ring dependencies and unpacks them to @deps/@. To see a live "Hello World" Ring app, run: To see a live "Hello World" Ring app, run:


java -Djava.ext.dirs=deps clojure.main src/ring/examples/hello_world.clj java -Djava.ext.dirs=deps clojure.main src/ring/examples/hello_world.clj


then visit @http://localhost:8080/@ in your browser; the Ring app will respond to your request with a simple HTML page indicating the time of day. Now visit @http://localhost:8080/@ in your browser; the Ring app will respond
to your request with a simple HTML page indicating the time of day.


To see a more sophisticated Ring app, run: To see a more sophisticated Ring app, run:


java -Djava.ext.dirs=deps clojure.main src/ring/examples/wrapping.clj java -Djava.ext.dirs=deps clojure.main src/ring/example/wrapping.clj


* If you request @http://localhost:8080/@ in your browser the @ring.dump@ handler will respond with an HTML page representing the request map that it received (see the @SPEC@ for details on the request map). * If you request @http://localhost:8080/@ in your browser the @ring.dump@
* If you request @http://localhost:8080/clojure.png@, the @ring.file@ middleware will detect that there is a @clojure.png@ file in the app's @public@ directory and return that image as a response. handler will respond with an HTML page representing the request map that it
* If you request @http://localhost:8080/error@, the app will produce an error that will be caught by the @ring.show-exceptions@ middleware, which will in turn return a readable backtrace as the HTML response. received (see the @SPEC@ for details on the request map).
* If you request @http://localhost:8080/clojure.png@, the @ring.file@
middleware will detect that there is a @clojure.png@ file in the app's
@public@ directory and return that image as a response.
* If you request @http://localhost:8080/error@, the app will produce an error
that will be caught by the @ring.show-exceptions@ middleware, which will in
turn return a readable stacktrace as the HTML response.


h2. All Available Libs h2. Included Libs


* @ring.jetty@: Adapter for the Jetty webserver. * @ring.adapter.jetty@: Adapter for the Jetty webserver.
* @ring.httpcore@: Adapter for the Apache HttpCore webserver. * @ring.adapter.httpcore@: Adapter for the Apache HttpCore webserver.
* @ring.file@: Middleware that serves static files out of a public directory. * @ring.middleware.file@: Middleware that serves static files out of a public
* @ring.file-info@: Middleware that augments response headers with info about File responses. directory.
* @ring.static@: Middleware that serves static files with specified prefixes out of a public directory. * @ring.middleware.file-info@: Middleware that augments response headers with
* @ring.dump@: Handler that dumps request maps as HTML responses for debugging. info about File responses.
* @ring.show-exceptions@: Middleware that catches exceptions and displays readable backtraces for debugging. * @ring.middleware.static@: Middleware that serves static files with specified
* @ring.reload@: Middleware to automatically reload selected libs before each requests, minimizing server restarts. prefixes out of a public directory.
* @ring.builder@: Helpers for composing Ring apps from multiple handlers and middleware. * @ring.middleware.show-exceptions@: Middleware that catches exceptions and
* @ring.lint@: Linter for the Ring interface, ensures compliance with the Ring spec. displays readable stacktraces for debugging.
* @ring.examples.*@: Various example Ring apps. * @ring.middleware.reload@: Middleware to automatically reload selected libs
before each requests, minimizing server restarts.
* @ring.handler.dump@: Handler that dumps request maps as HTML responses for
debugging.
* @ring.util.builder@: Helpers for composing Ring apps from multiple handlers
and middleware.
* @ring.util.lint@: Linter for the Ring interface, ensures compliance with the
Ring spec.
* @ring.example.*@: Various example Ring apps.


h2. Development h2. Development


Ring is being actively developed; you can track its progress and contribute at the project's "GitHub"::http://github.com/mmcgrana/ring page. Ring is being actively developed; you can track its progress and contribute at
the project's "GitHub"::http://github.com/mmcgrana/ring page.


To run all the Ring unit tests: To run all the Ring unit tests:


Expand All @@ -57,21 +74,13 @@ You can learn more about Ring's dependencies at the following sites:
* @jetty-6.1.14.jar@, @jetty-util-6.1.14.jar@, @servlet-api-2.5-6.1.14.jar@: "mortbay.org/jetty":http://www.mortbay.org/jetty * @jetty-6.1.14.jar@, @jetty-util-6.1.14.jar@, @servlet-api-2.5-6.1.14.jar@: "mortbay.org/jetty":http://www.mortbay.org/jetty
* @httpcore-4.0.jar@, @httpcore-nio-4.0.jar@: "hc.apache.org":http://hc.apache.org/httpcomponents-core/index.html * @httpcore-4.0.jar@, @httpcore-nio-4.0.jar@: "hc.apache.org":http://hc.apache.org/httpcomponents-core/index.html
* @commons-io-1.4.jar@: "commons.apache.org/io":http://commons.apache.org/io/ * @commons-io-1.4.jar@: "commons.apache.org/io":http://commons.apache.org/io/
* @clj-html.jar@, @clj-html-helpers.jar@, @clj-backtrace.jar@, @clj-unit@ (testing): "github.com/mmcgrana":http://github.com/mmcgrana * @clj-html.jar@, @clj-html-helpers.jar@, @clj-stacktrace.jar@, @clj-unit@: "github.com/mmcgrana":http://github.com/mmcgrana


h2. Thanks h2. Thanks


This project borrows heavily from Ruby's Rack and Python's WSGI, and I thank the communities developing and supporting those projects. This project borrows heavily from Ruby's Rack and Python's WSGI, and I thank the communities developing and supporting those projects.


--- ---


<pre><code>
+--->HTTP Request--->[Adapter]--->Req1---->[Middleware]--->Req2----+
| |
[Client] [Handler]
| |
+----HTTP Response<--[Adapter]<---Resp2<---[Middleware]<---Resp1<--+
</code></pre>

Copyright (c) 2009 Mark McGranaghan and released under an MIT license. Copyright (c) 2009 Mark McGranaghan and released under an MIT license.
Clojure logo by Tom Hickey. Clojure logo by Tom Hickey.
6 changes: 3 additions & 3 deletions SPEC
Expand Up @@ -22,9 +22,9 @@ Handlers are run via Ring adapters, which are in turn responsible for
implementing the HTTP protocol and abstracting the handlers that they run from implementing the HTTP protocol and abstracting the handlers that they run from
the details of the protocol. the details of the protocol.


Adapters are implemented as functions of two arguments: an options map and a Adapters are implemented as functions of two arguments: a handler and an options
handler. The options map provides any needed configuration to the adapter, such map. The options map provides any needed configuration to the adapter, such as
as the port on which to run. the port on which to run.


Once initialized, adapters receive HTTP requests, parse them to construct an Once initialized, adapters receive HTTP requests, parse them to construct an
request map, and then invoke their handler with this request map as an request map, and then invoke their handler with this request map as an
Expand Down
59 changes: 29 additions & 30 deletions src/ring/httpcore.clj → src/ring/adapter/httpcore.clj
@@ -1,16 +1,16 @@
(ns ring.httpcore (ns ring.adapter.httpcore
(:import (org.apache.http HttpRequest Header HttpEntity HttpEntityEnclosingRequest HttpResponse (:import (org.apache.http HttpRequest Header HttpEntityHttpEntityEnclosingRequest HttpResponse
ConnectionClosedException HttpException HttpServerConnection) ConnectionClosedException HttpException HttpServerConnection)
(org.apache.http.entity AbstractHttpEntity StringEntity EntityTemplate InputStreamEntity (org.apache.http.entity AbstractHttpEntity StringEntity EntityTemplate InputStreamEntity
FileEntity ContentProducer) FileEntity ContentProducer)
(org.apache.http.message BasicHeader BasicHeaderElement) (org.apache.http.message BasicHeader BasicHeaderElement)
(org.apache.http.params BasicHttpParams CoreConnectionPNames CoreProtocolPNames) (org.apache.http.params BasicHttpParams CoreConnectionPNames CoreProtocolPNames)
(org.apache.http.protocol HttpRequestHandler BasicHttpContext HttpService BasicHttpProcessor (org.apache.http.protocol HttpRequestHandler BasicHttpContext HttpService BasicHttpProcessor
ResponseDate ResponseServer ResponseContent ResponseConnControl HttpRequestHandlerRegistry ResponseDate ResponseServer ResponseContent ResponseConnControl HttpRequestHandlerRegistry
HttpContext) HttpContext)
(org.apache.http.impl DefaultConnectionReuseStrategy DefaultHttpResponseFactory (org.apache.http.impl DefaultConnectionReuseStrategy DefaultHttpResponseFactory
DefaultHttpServerConnection) DefaultHttpServerConnection)
(java.io File FileInputStream InputStream OutputStream OutputStreamWriter IOException (java.io File FileInputStream InputStream OutputStream OutputStreamWriter IOException
InterruptedIOException) InterruptedIOException)
(java.net URI ServerSocket) (java.net URI ServerSocket)
(java.util.concurrent Executors Executor ThreadFactory)) (java.util.concurrent Executors Executor ThreadFactory))
Expand All @@ -20,9 +20,9 @@
([form] form) ([form] form)
([form next-form & forms] ([form next-form & forms]
`(when-let [x# ~form] (-?> (-> x# ~next-form) ~@forms)))) `(when-let [x# ~form] (-?> (-> x# ~next-form) ~@forms))))

(defmacro #^{:private true} instance?-> [type x & forms] (defmacro #^{:private true} instance?-> [type x & forms]
`(when (instance? ~type ~x) (-> ~(vary-meta x assoc :tag type) ~@forms))) `(when (instance? ~type ~x) (-> ~(vary-meta x assoc :tag type) ~@forms)))


(defn- charset [#^BasicHeader content-type-header] (defn- charset [#^BasicHeader content-type-header]
(-?> content-type-header .getElements #^BasicHeaderElement first (.getParameterByName "charset") .getValue)) (-?> content-type-header .getElements #^BasicHeaderElement first (.getParameterByName "charset") .getValue))
Expand All @@ -31,20 +31,20 @@
(.toLowerCase s java.util.Locale/ENGLISH)) (.toLowerCase s java.util.Locale/ENGLISH))


(defn- build-req-map (defn- build-req-map
"Augments the given request-prototype (a map) to represent the given HTTP "Augments the given request-prototype (a map) to represent the given HTTP
request, to be passed as the Ring request to an app." request, to be passed as the Ring request to an app."
[#^HttpRequest request request-prototype] [#^HttpRequest request request-prototype]
(let [request-line (.getRequestLine request) (let [request-line (.getRequestLine request)
headers (reduce headers (reduce
(fn [header-map #^Header header] (fn [header-map #^Header header]
(assoc header-map (assoc header-map
(-> header .getName lower-case) (-> header .getName lower-case)
(.getValue header))) (.getValue header)))
{} (seq (.getAllHeaders request))) {} (seq (.getAllHeaders request)))
host (or (headers "host") host (or (headers "host")
(str (request-prototype :server-name) ":" (request-prototype :server-port 80))) (str (request-prototype :server-name) ":" (request-prototype :server-port 80)))
uri (URI. (str "http://" host (.getUri request-line)))] uri (URI. (str "http://" host (.getUri request-line)))]
(into (or request-prototype {}) (into (or request-prototype {})
{:server-port (.getPort uri) {:server-port (.getPort uri)
:server-name (.getHost uri) :server-name (.getHost uri)
:uri (.getRawPath uri) :uri (.getRawPath uri)
Expand All @@ -53,7 +53,7 @@
:headers headers :headers headers
:content-type (headers "content-type") :content-type (headers "content-type")
:content-length (when-let [len (instance?-> HttpEntityEnclosingRequest request .getEntity .getContentLength)] :content-length (when-let [len (instance?-> HttpEntityEnclosingRequest request .getEntity .getContentLength)]
(when (>= len 0) len)) (when (>= len 0) len))
:character-encoding (instance?-> HttpEntityEnclosingRequest request .getEntity .getContentEncoding charset) :character-encoding (instance?-> HttpEntityEnclosingRequest request .getEntity .getContentEncoding charset)
:body (instance?-> HttpEntityEnclosingRequest request .getEntity .getContent)}))) :body (instance?-> HttpEntityEnclosingRequest request .getEntity .getContent)})))


Expand All @@ -73,13 +73,13 @@
(when body (when body
(let [content-type (headers "Content-Type") (let [content-type (headers "Content-Type")
charset (when content-type (charset (BasicHeader. "Content-Type" content-type))) charset (when content-type (charset (BasicHeader. "Content-Type" content-type)))
content-length (headers "Content-Length") content-length (headers "Content-Length")
entity entity
(cond (cond
(string? body) (string? body)
(StringEntity. body) (StringEntity. body)
(seq? body) (seq? body)
(EntityTemplate. (EntityTemplate.
(proxy [ContentProducer] [] (proxy [ContentProducer] []
(writeTo [#^OutputStream s] (writeTo [#^OutputStream s]
(let [w (if charset (OutputStreamWriter. s #^String charset) (OutputStreamWriter. s))] (let [w (if charset (OutputStreamWriter. s #^String charset) (OutputStreamWriter. s))]
Expand All @@ -97,7 +97,7 @@
(.setEntity response entity)))) (.setEntity response entity))))


(defn- ring-handler (defn- ring-handler
"Returns an Handler implementation for the given app. "Returns an Handler implementation for the given app.
The HttpContext must contains a map associated to \"ring.request-prototype\"." The HttpContext must contains a map associated to \"ring.request-prototype\"."
[app] [app]
(proxy [HttpRequestHandler] [] (proxy [HttpRequestHandler] []
Expand All @@ -110,7 +110,7 @@
;; Simple HTTP Server ;; Simple HTTP Server


(defn- handle-request (defn- handle-request
"Handle the request, usually called from a worker thread." "Handle the request, usually called from a worker thread."
[#^HttpService httpservice #^HttpServerConnection conn request-prototype] [#^HttpService httpservice #^HttpServerConnection conn request-prototype]
(let [context (doto (BasicHttpContext. nil) (let [context (doto (BasicHttpContext. nil)
(.setAttribute "ring.request-prototype" request-prototype))] (.setAttribute "ring.request-prototype" request-prototype))]
Expand All @@ -135,31 +135,31 @@
(.addInterceptor (ResponseConnControl.))) (.addInterceptor (ResponseConnControl.)))
registry (doto (HttpRequestHandlerRegistry.) registry (doto (HttpRequestHandlerRegistry.)
(.register "*" (ring-handler app)))] (.register "*" (ring-handler app)))]
(doto (HttpService. httpproc (doto (HttpService. httpproc
(DefaultConnectionReuseStrategy.) (DefaultConnectionReuseStrategy.)
(DefaultHttpResponseFactory.)) (DefaultHttpResponseFactory.))
(.setParams params) (.setParams params)
(.setHandlerResolver registry)))) (.setHandlerResolver registry))))


(defn executor-execute (defn executor-execute
"Executes (apply f args) using the specified Executor." "Executes (apply f args) using the specified Executor."
[#^java.util.concurrent.Executor executor f & args] [#^java.util.concurrent.Executor executor f & args]
(.execute executor #(apply f args))) (.execute executor #(apply f args)))


(defn run (defn run-httpcore
"Serve the given app according to the options. "Serve the given app according to the options.
Options: Options:
:port, an Integer, :port, an Integer.
:server-name, a String -- for old HTTP/1.0 clients, :server-name, a String (for old HTTP/1.0 clients).
:server-port, an Integer -- for old HTTP/1.0 clients, when public facing port is different from :port, :server-port, an Integer (for old HTTP/1.0 clients, when public facing port is different from :port).
:execute, a function (whose sig is [f & args]) that applies f to args -- usually in another thread." :execute, a function with signature [f & args] that applies f to args, usually in another thread."
[{:keys [port server-name server-port execute]} app] [app {:keys [port server-name server-port execute]}]
(let [execute (or execute (partial executor-execute (let [execute (or execute (partial executor-execute
(Executors/newCachedThreadPool (Executors/newCachedThreadPool
(proxy [ThreadFactory] [] (proxy [ThreadFactory] []
(newThread [r] (newThread [r]
(doto (Thread. #^Runnable r) (doto (Thread. #^Runnable r)
(.setDaemon true))))))) (.setDaemon true)))))))
params (doto (BasicHttpParams.) params (doto (BasicHttpParams.)
(.setIntParameter CoreConnectionPNames/SO_TIMEOUT 5000) (.setIntParameter CoreConnectionPNames/SO_TIMEOUT 5000)
(.setIntParameter CoreConnectionPNames/SOCKET_BUFFER_SIZE (* 8 1024)) (.setIntParameter CoreConnectionPNames/SOCKET_BUFFER_SIZE (* 8 1024))
Expand All @@ -172,6 +172,5 @@
(let [socket (.accept serversocket) (let [socket (.accept serversocket)
conn (doto (DefaultHttpServerConnection.) conn (doto (DefaultHttpServerConnection.)
(.bind socket params))] (.bind socket params))]
(execute handle-request httpservice conn (execute handle-request httpservice conn
(assoc request-prototype :remote-addr (-> socket .getInetAddress .getHostAddress)))))))) (assoc request-prototype :remote-addr (-> socket .getInetAddress .getHostAddress))))))))

6 changes: 3 additions & 3 deletions src/ring/jetty.clj → src/ring/adapter/jetty.clj
@@ -1,4 +1,4 @@
(ns ring.jetty (ns ring.adapter.jetty
(:import (javax.servlet.http HttpServletRequest HttpServletResponse) (:import (javax.servlet.http HttpServletRequest HttpServletResponse)
(org.mortbay.jetty.handler AbstractHandler) (org.mortbay.jetty.handler AbstractHandler)
(org.mortbay.jetty Server) (org.mortbay.jetty Server)
Expand Down Expand Up @@ -81,11 +81,11 @@
(apply-resp-map response resp) (apply-resp-map response resp)
(.setHandled request true))))) (.setHandled request true)))))


(defn run (defn run-jetty
"Serve the given app according to the options. "Serve the given app according to the options.
Options: Options:
:port, an Integer." :port, an Integer."
[options app] [app options]
(let [port (or (:port options) (throwf ":port missing from options")) (let [port (or (:port options) (throwf ":port missing from options"))
server (doto (Server. port) (.setSendDateHeader true)) server (doto (Server. port) (.setSendDateHeader true))
handler (proxy-handler app)] handler (proxy-handler app)]
Expand Down
8 changes: 0 additions & 8 deletions src/ring/builder.clj

This file was deleted.

@@ -1,7 +1,7 @@
; A very simple Ring application. ; A very simple Ring application.


(ns ring.examples.hello-world (ns ring.example.hello-world
(:require ring.jetty) (:use ring.adapter.jetty)
(:import java.util.Date java.text.SimpleDateFormat)) (:import java.util.Date java.text.SimpleDateFormat))


(def formatter (SimpleDateFormat. "HH:mm:ss")); (def formatter (SimpleDateFormat. "HH:mm:ss"));
Expand All @@ -11,7 +11,7 @@
{:status 200 {:status 200
:headers {"Content-Type" "text/html"} :headers {"Content-Type" "text/html"}
:body (str "<h3>Hello World from Ring</h3>" :body (str "<h3>Hello World from Ring</h3>"
"<p>The current time is " "<p>The current time is "
(.format formatter (Date.)) ".</p>")}) (.format formatter (Date.)) ".</p>")})


(ring.jetty/run {:port 8080} app) (run-jetty app {:port 8080})
21 changes: 21 additions & 0 deletions src/ring/example/linted.clj
@@ -0,0 +1,21 @@
; An example of inserting the linter between each component to ensure
; compliance to the Ring spec.

(ns ring.examples.linted
(:use (ring.handler dump)
(ring.middleware stacktrace file file-info reload lint)
(ring.adapter jetty)))

(def app
(-> handle-dump
wrap-lint
wrap-stacktrace
wrap-lint
wrap-file-info
wrap-lint
(wrap-file "src/ring/examples/public")
wrap-lint
(wrap-reload '(ring.dump)
wrap-lint)))

(run-jetty app {:port 8080})
File renamed without changes
22 changes: 22 additions & 0 deletions src/ring/example/wrapping.clj
@@ -0,0 +1,22 @@
; A example of modular construction of Ring apps.

(ns ring.example.wrapping
(:use (ring.handler dump)
(ring.middleware stacktrace file-info file)
(ring.adapter jetty)
(clojure.contrib fcase)))

(defn wrap-error [app]
(fn [req]
(if (= "/error" (:uri req))
(throwf "Demonstrating ring.middleware.stacktrace")
(app req))))

(def app
(-> handle-dump
wrap-stacktrace
wrap-file-info
(wrap-file "src/ring/examples/public")
wrap-error))

(run-jetty app {:port 8080})

0 comments on commit cbeebac

Please sign in to comment.