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

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
cd ring

Then download the needed dependencies:

cd ring
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

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:

java -Djava.ext.dirs=deps clojure.main src/ring/examples/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/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 backtrace as the HTML response.
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/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

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

* @ring.adapter.jetty@: Adapter for the Jetty webserver.
* @ring.adapter.httpcore@: Adapter for the Apache HttpCore webserver.
* @ring.middleware.file@: Middleware that serves static files out of a public
directory.
* @ring.middleware.file-info@: Middleware that augments response headers with
info about File responses.
* @ring.middleware.static@: Middleware that serves static files with specified
prefixes out of a public directory.
* @ring.middleware.show-exceptions@: Middleware that catches exceptions and
displays readable stacktraces for debugging.
* @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

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:

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
* @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/
* @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

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.
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
the details of the protocol.

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

Once initialized, adapters receive HTTP requests, parse them to construct 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
(:import (org.apache.http HttpRequest Header HttpEntity HttpEntityEnclosingRequest HttpResponse
(ns ring.adapter.httpcore
(:import (org.apache.http HttpRequest Header HttpEntityHttpEntityEnclosingRequest HttpResponse
ConnectionClosedException HttpException HttpServerConnection)
(org.apache.http.entity AbstractHttpEntity StringEntity EntityTemplate InputStreamEntity
FileEntity ContentProducer)
(org.apache.http.message BasicHeader BasicHeaderElement)
(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
HttpContext)
(org.apache.http.impl DefaultConnectionReuseStrategy DefaultHttpResponseFactory
(org.apache.http.impl DefaultConnectionReuseStrategy DefaultHttpResponseFactory
DefaultHttpServerConnection)
(java.io File FileInputStream InputStream OutputStream OutputStreamWriter IOException
(java.io File FileInputStream InputStream OutputStream OutputStreamWriter IOException
InterruptedIOException)
(java.net URI ServerSocket)
(java.util.concurrent Executors Executor ThreadFactory))
Expand All @@ -20,9 +20,9 @@
([form] form)
([form next-form & forms]
`(when-let [x# ~form] (-?> (-> x# ~next-form) ~@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]
(-?> content-type-header .getElements #^BasicHeaderElement first (.getParameterByName "charset") .getValue))
Expand All @@ -31,20 +31,20 @@
(.toLowerCase s java.util.Locale/ENGLISH))

(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."
[#^HttpRequest request request-prototype]
(let [request-line (.getRequestLine request)
(let [request-line (.getRequestLine request)
headers (reduce
(fn [header-map #^Header header]
(assoc header-map
(-> header .getName lower-case)
(.getValue header)))
{} (seq (.getAllHeaders request)))
host (or (headers "host")
host (or (headers "host")
(str (request-prototype :server-name) ":" (request-prototype :server-port 80)))
uri (URI. (str "http://" host (.getUri request-line)))]
(into (or request-prototype {})
(into (or request-prototype {})
{:server-port (.getPort uri)
:server-name (.getHost uri)
:uri (.getRawPath uri)
Expand All @@ -53,7 +53,7 @@
:headers headers
:content-type (headers "content-type")
: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)
:body (instance?-> HttpEntityEnclosingRequest request .getEntity .getContent)})))

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

(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\"."
[app]
(proxy [HttpRequestHandler] []
Expand All @@ -110,7 +110,7 @@
;; Simple HTTP Server

(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]
(let [context (doto (BasicHttpContext. nil)
(.setAttribute "ring.request-prototype" request-prototype))]
Expand All @@ -135,31 +135,31 @@
(.addInterceptor (ResponseConnControl.)))
registry (doto (HttpRequestHandlerRegistry.)
(.register "*" (ring-handler app)))]
(doto (HttpService. httpproc
(doto (HttpService. httpproc
(DefaultConnectionReuseStrategy.)
(DefaultHttpResponseFactory.))
(.setParams params)
(.setHandlerResolver registry))))

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

(defn run
(defn run-httpcore
"Serve the given app according to the options.
Options:
:port, an Integer,
: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,
:execute, a function (whose sig is [f & args]) that applies f to args -- usually in another thread."
[{:keys [port server-name server-port execute]} app]
(let [execute (or execute (partial executor-execute
:port, an Integer.
: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).
:execute, a function with signature [f & args] that applies f to args, usually in another thread."
[app {:keys [port server-name server-port execute]}]
(let [execute (or execute (partial executor-execute
(Executors/newCachedThreadPool
(proxy [ThreadFactory] []
(proxy [ThreadFactory] []
(newThread [r]
(doto (Thread. #^Runnable r)
(.setDaemon true)))))))
(.setDaemon true)))))))
params (doto (BasicHttpParams.)
(.setIntParameter CoreConnectionPNames/SO_TIMEOUT 5000)
(.setIntParameter CoreConnectionPNames/SOCKET_BUFFER_SIZE (* 8 1024))
Expand All @@ -172,6 +172,5 @@
(let [socket (.accept serversocket)
conn (doto (DefaultHttpServerConnection.)
(.bind socket params))]
(execute handle-request httpservice conn
(execute handle-request httpservice conn
(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)
(org.mortbay.jetty.handler AbstractHandler)
(org.mortbay.jetty Server)
Expand Down Expand Up @@ -81,11 +81,11 @@
(apply-resp-map response resp)
(.setHandled request true)))))

(defn run
(defn run-jetty
"Serve the given app according to the options.
Options:
:port, an Integer."
[options app]
[app options]
(let [port (or (:port options) (throwf ":port missing from options"))
server (doto (Server. port) (.setSendDateHeader true))
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.

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

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