Permalink
Please sign in to comment.
Browse files
Initial import from http://github.com/mmcgrana/clj-garden
- Loading branch information...
Showing
with
1,073 additions
and 0 deletions.
- +1 −0 .gitignore
- +22 −0 LICENSE
- +60 −0 README.textile
- +124 −0 SPEC
- +17 −0 build.xml
- BIN jars/clj-backtrace.jar
- BIN jars/clj-html-helpers.jar
- BIN jars/clj-html.jar
- BIN jars/clj-unit.jar
- BIN jars/clojure-contrib.jar
- BIN jars/clojure.jar
- BIN jars/commons-io-1.4.jar
- BIN jars/jetty-6.1.14.jar
- BIN jars/jetty-util-6.1.14.jar
- BIN jars/ring.jar
- BIN jars/servlet-api-2.5-6.1.14.jar
- +8 −0 src/ring/builder.clj
- +81 −0 src/ring/dump.clj
- +14 −0 src/ring/examples/basic_stack.clj
- +16 −0 src/ring/examples/hello_world.clj
- +18 −0 src/ring/examples/linted.clj
- BIN src/ring/examples/public/clojure.png
- +49 −0 src/ring/file.clj
- +79 −0 src/ring/file_info.clj
- +87 −0 src/ring/jetty.clj
- +108 −0 src/ring/lint.clj
- +10 −0 src/ring/reloading.clj
- +93 −0 src/ring/show_exceptions.clj
- +25 −0 src/ring/utils.clj
- +1 −0 test/ring/assets/foo.html
- +1 −0 test/ring/assets/index.html
- +1 −0 test/ring/assets/plain.txt
- +1 −0 test/ring/assets/random.xyz
- +10 −0 test/ring/builder_test.clj
- +18 −0 test/ring/dump_test.clj
- +26 −0 test/ring/file_info_test.clj
- +42 −0 test/ring/file_test.clj
- +129 −0 test/ring/lint_test.clj
- +8 −0 test/ring/reloading_test.clj
- +15 −0 test/ring/show_exceptions_test.clj
- +9 −0 test/run.clj
1
.gitignore
| @@ -0,0 +1 @@ | ||
| +bundle |
22
LICENSE
| @@ -0,0 +1,22 @@ | ||
| +Copyright (c) 2009 Mark McGranaghan | ||
| + | ||
| +Permission is hereby granted, free of charge, to any person | ||
| +obtaining a copy of this software and associated documentation | ||
| +files (the "Software"), to deal in the Software without | ||
| +restriction, including without limitation the rights to use, | ||
| +copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| +copies of the Software, and to permit persons to whom the | ||
| +Software is furnished to do so, subject to the following | ||
| +conditions: | ||
| + | ||
| +The above copyright notice and this permission notice shall be | ||
| +included in all copies or substantial portions of the Software. | ||
| + | ||
| +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
| +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | ||
| +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | ||
| +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
| +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
| +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | ||
| +OTHER DEALINGS IN THE SOFTWARE. |
| @@ -0,0 +1,60 @@ | ||
| +h1. Ring | ||
| + | ||
| +Ring is a library inspired by Python's WSGI and Ruby's Rack that | ||
| +enables modular and concise Clojure web applications. Ring abstracts the details | ||
| +of HTTP and defines a simple API by which web application components can | ||
| +cleanly interact. | ||
| + | ||
| +h2. Quick Setup | ||
| + | ||
| +All jars needed to load any of the @ring.*@ libs in this distribution are | ||
| +included in the jars/ directory. To see a live Ring app, run: | ||
| + | ||
| + java -Djava.ext.dirs=jars clojure.main src/ring/examples/hello_world.clj | ||
| + curl localhost:8080/ | ||
| + | ||
| +You can find additional example Ring apps in the @src/examples@ directory of this distribution, including a "basic stack" example that shows a typical Ring setup with wrapping middleware. | ||
| + | ||
| +h2. Spec | ||
| + | ||
| +Please consult the @SPEC@ file at the root of this distribution for a complete | ||
| +description of the Ring interface. | ||
| + | ||
| +h2. Available Libs | ||
| + | ||
| + * @ring.jetty@: Handler for the Jetty 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.dump@: Endpoint that dumps the Ring requests as HTML responses for debugging. | ||
| + * @ring.show-exceptions@: Middleware that catches exceptions and displays readable stack traces for debugging. | ||
| + * @ring.reloading@: Middleware to automatically reload selected libs before each requests, minimizing server restarts. | ||
| + * @ring.builder@: Helpers for combining Ring endpoints and middleware into Ring apps. | ||
| + * @ring.lint@: Linter for the Ring interface, ensures compliance with the @SPEC@. | ||
| + * @ring.examples.*@: Various example Ring apps. | ||
| + | ||
| +h2. Development | ||
| + | ||
| +Ring is being actively developed; you can track its progress and contribute at: "github.com/mmcgrana/ring":http://github.com/mmcgrana/ring. | ||
| + | ||
| +To run all of the @ring.*@ unit tests: | ||
| + | ||
| + java -Djava.ext.dirs=jars clojure.main test/ring/run.clj | ||
| + | ||
| +You can learn more about Ring's dependencies at the following sites: | ||
| + | ||
| + * @clojure.jar@, @clojure-contrib.jar@: "clojure.org":http://clojure.org | ||
| + * @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 | ||
| + * @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 | ||
| + | ||
| +--- | ||
| + | ||
| +<pre><code> | ||
| + +--->HTTP Request--->[Handler]--->Req1---->[Middleware]--->Req2----+ | ||
| + | | | ||
| + [Client] [Endpoint] | ||
| + | | | ||
| + +----HTTP Response<--[Handler]<---Resp2<---[Middleware]<---Resp1<--+ | ||
| +</code></pre> | ||
| + | ||
| +Copyright (c) 2009 Mark McGranaghan and released under an MIT license. |
124
SPEC
| @@ -0,0 +1,124 @@ | ||
| +=== Ring Spec (Draft Version) | ||
| +Ring is defined in terms of Components, Handlers, Requests, and Responses, | ||
| +each of which are described below. | ||
| + | ||
| + | ||
| +== Components | ||
| +Ring components constitute the logic of the web application and are abstracted | ||
| +form the details of the HTTP protocol. They are implemented as Clojure | ||
| +functions that process a given request to generate and return a response. | ||
| + | ||
| +A component is either an 'endpoint' or 'middleware'. An endpoint component is | ||
| +used at the leaf of a component tree, i.e. it does not call any other components | ||
| +to generate it's response. A 'middleware' component in contrast may invoke | ||
| +other components to generate its response. We can combine 1 or more endpoints | ||
| +with 0 or more middleware components to build a complete Ring 'app'. Such an | ||
| +app can then be run by a Ring handler. | ||
| + | ||
| +== Handlers | ||
| +A Ring handler is a server implementation than can 'run' apps. Handlers are | ||
| +responsible for implementing the HTTP protocol and abstracting the apps that | ||
| +they run from the details of the protocol. | ||
| + | ||
| +Handlers are implemented as functions of two arguments, an options map and a | ||
| +Ring app. The options map provides any needed configuration to the handler, such | ||
| +as the port on which to run. | ||
| + | ||
| +Once initialized, handlers receive HTTP requests, parse them to construct an | ||
| +Ring request, and then invoke their Ring app with this request as an | ||
| +argument. Once the app returns a Ring response, the handler uses it to construct | ||
| +and send an HTTP response to the client. | ||
| + | ||
| + | ||
| +== Ring Requests | ||
| +A Ring request is a Clojure map containing at least the following keys and | ||
| +corresponding values: | ||
| + | ||
| +:server-port | ||
| + (Required, Integer) | ||
| + The port on which the request is being handled. | ||
| + | ||
| +:server-name | ||
| + (Required, String) | ||
| + The resolved server name, or the server IP address. | ||
| + | ||
| +:remote-addr | ||
| + (Required, String) | ||
| + The IP address of the client or the last proxy that sent the request. | ||
| + | ||
| +:uri | ||
| + (Required, String) | ||
| + The request URI. Must starts with "/". | ||
| + | ||
| +:query-string | ||
| + (Optional, String) | ||
| + The query string, if present. | ||
| + | ||
| +:scheme | ||
| + (Required, Keyword) | ||
| + The transport protocol, must be one of :http or :https. | ||
| + | ||
| +:request-method | ||
| + (Required, Keyword) | ||
| + The HTTP request method, must be one of :get, :head, :options, :put, :post, or | ||
| + :delete. | ||
| + | ||
| +:content-type | ||
| + (Optional, String) | ||
| + The MIME type of the request body, if known. | ||
| + | ||
| +:content-length | ||
| + (Optional, Integer) | ||
| + The number of bytes in the request body, if known. | ||
| + | ||
| +:character-encoding | ||
| + (Optional, String) | ||
| + The name of the character encoding used in the request body, if known. | ||
| + | ||
| +:headers | ||
| + (Required, IPersistentMap) | ||
| + A Clojure map of downcased header name Strings to corresponding header value | ||
| + Strings. | ||
| + | ||
| +:body | ||
| + (Optional, InputStream) | ||
| + An InputStream for the request body, if present. | ||
| + | ||
| +If a component invokes another component with an environment containing | ||
| +additional keys, these keys must be namespaced using the Clojure | ||
| +:name.space/key-name convention. The ring.* namespaces are reserved. | ||
| + | ||
| + | ||
| +== Ring Responses | ||
| +A Ring response is a Clojure map containing at least the following keys and corresponding values: | ||
| + | ||
| +:status | ||
| + (Required, Integer) | ||
| + The HTTP status code, must be greater than or equal to 100. | ||
| + | ||
| +:headers | ||
| + (Required, IPersistentMap) | ||
| + A Clojure map of HTTP header names to header values. These values may be | ||
| + either Strings, in which case one name/value header will be sent in the | ||
| + HTTP response, or a seq of Strings, in which case a name/value header will be | ||
| + sent for each such String value. | ||
| + | ||
| +:body | ||
| + (Optional, {String, File, InputStream}) | ||
| + A representation of the response body, if a response body is appropriate for | ||
| + the response's status code. The respond body is handled according to its type: | ||
| + String: | ||
| + Contents are sent to the client as-is. | ||
| + File: | ||
| + Contents at the specified location are sent to the client. The server may | ||
| + use an optimized method to send the file if such a method is available. | ||
| + InputStream: | ||
| + Contents are consumed from the stream and sent to the client. When the | ||
| + stream is exhausted, it is .close'd. | ||
| + | ||
| +As with environments, keys other than those listed above should be appropriately | ||
| +namespaced. | ||
| + | ||
| + | ||
| +== Credit | ||
| +This project borrows heavily from Ruby's Rack and Python's WSGI, and I thank the communities developing and supporting those projects. |
17
build.xml
| @@ -0,0 +1,17 @@ | ||
| +<project name="ring" default="jar"> | ||
| + <description>Pack all sources into a JAR.</description> | ||
| + | ||
| + <property name="jarfile" location="ring.jar"/> | ||
| + | ||
| + <target name="clean" description="Remove generated files and directories."> | ||
| + <delete file="${jarfile}"/> | ||
| + </target> | ||
| + | ||
| + <target name="jar" description="Create jar file."> | ||
| + <jar jarfile="${jarfile}"> | ||
| + <path location="LICENSE"/> | ||
| + <fileset dir="./src" includes="ring/**/*.clj"/> | ||
| + <fileset dir="./test" includes="ring/**/*.clj"/> | ||
| + </jar> | ||
| + </target> | ||
| +</project> |
BIN
jars/clj-backtrace.jar
Binary file not shown.
BIN
jars/clj-html-helpers.jar
Binary file not shown.
BIN
jars/clj-html.jar
Binary file not shown.
BIN
jars/clj-unit.jar
Binary file not shown.
BIN
jars/clojure-contrib.jar
Binary file not shown.
BIN
jars/clojure.jar
Binary file not shown.
BIN
jars/commons-io-1.4.jar
Binary file not shown.
BIN
jars/jetty-6.1.14.jar
Binary file not shown.
BIN
jars/jetty-util-6.1.14.jar
Binary file not shown.
BIN
jars/ring.jar
Binary file not shown.
BIN
jars/servlet-api-2.5-6.1.14.jar
Binary file not shown.
8
src/ring/builder.clj
| @@ -0,0 +1,8 @@ | ||
| +(ns ring.builder) | ||
| + | ||
| +(defn wrap-if | ||
| + "If test is logically true, returns the result of invoking the wrapper on the | ||
| + core app, i.e. a wrapped app; if test is logically false, returns the core | ||
| + app." | ||
| + [test wrapper-fn core-app] | ||
| + (if test (wrapper-fn core-app) core-app)) |
81
src/ring/dump.clj
| @@ -0,0 +1,81 @@ | ||
| +(ns ring.dump | ||
| + (:use (clj-html core utils helpers) | ||
| + clojure.contrib.def | ||
| + clojure.set) | ||
| + (:import (org.apache.commons.io IOUtils))) | ||
| + | ||
| +(def css " | ||
| +/* | ||
| +Copyright (c) 2008, Yahoo! Inc. All rights reserved. | ||
| +Code licensed under the BSD License: | ||
| +http://developer.yahoo.net/yui/license.txt | ||
| +version: 2.6.0 | ||
| +*/ | ||
| +html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:text-top;}sub{vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}del,ins{text-decoration:none;} | ||
| + | ||
| + | ||
| +h3.info { | ||
| + font-size: 1.5em; | ||
| + margin-left: 1em; | ||
| + padding-top: .5em; | ||
| + padding-bottom: .5em; | ||
| +} | ||
| + | ||
| +table.trace { | ||
| + margin-left: 1em; | ||
| + background: lightgrey; | ||
| +} | ||
| + | ||
| +table.trace tr { | ||
| + line-height: 1.4em; | ||
| +} | ||
| + | ||
| +table.trace td.method { | ||
| + padding-left: .5em; | ||
| + text-aligh: left; | ||
| +} | ||
| + | ||
| +table.trace td.source { | ||
| + text-align: left; | ||
| +}") | ||
| + | ||
| +(def ring-keys | ||
| + '(:server-port :server-name :remote-addr :uri :query-string :scheme | ||
| + :request-method :content-type :content-length :character-encoding | ||
| + :headers :body)) | ||
| + | ||
| +(defhtml req-pair | ||
| + [key req] | ||
| + [:tr [:td.source (h (str key))] | ||
| + [:td.method (h (pr-str (key req)))]]) | ||
| + | ||
| +(defhtml template | ||
| + [req] | ||
| + (doctype :xhtml-transitional) | ||
| + [:html {:xmlns "http://www.w3.org/1999/xhtml"} | ||
| + [:head | ||
| + [:meta {:http-equiv "Content-Type" :content "text/html"}] | ||
| + [:title "Ring: Environment Dump"]] | ||
| + [:style {:type "text/css"} css] | ||
| + [:body | ||
| + [:div#content | ||
| + [:h3.info "Ring Env. Values"] | ||
| + [:table.trace | ||
| + [:tbody | ||
| + (domap-str [key ring-keys] | ||
| + (req-pair key req))]] | ||
| + (if-let [user-keys (difference (set (keys req)) (set ring-keys))] | ||
| + (html | ||
| + [:br] | ||
| + [:table.trace | ||
| + [:tbody [:tr | ||
| + (domap-str [key (sort user-keys)] | ||
| + (req-pair key req))]]]))]]]) | ||
| + | ||
| +(defn app | ||
| + "Returns a response tuple corresponding to an HTML dump of the request | ||
| + req as it was recieved by this app." | ||
| + [req] | ||
| + {:status 200 | ||
| + :headers {"Content-Type" "text/html"} | ||
| + :body (template req)}) |
14
src/ring/examples/basic_stack.clj
| @@ -0,0 +1,14 @@ | ||
| +; A failry typical middleware configuration wrapping the dump endpoint. | ||
| + | ||
| +(ns ring.examples.basic-stack | ||
| + (:require (ring show-exceptions file-info file reloading dump jetty)) | ||
| + (:import (java.io File))) | ||
| + | ||
| +(def app | ||
| + (ring.show-exceptions/wrap | ||
| + (ring.file-info/wrap | ||
| + (ring.file/wrap (File. "src/ring/examples/public") | ||
| + (ring.reloading/wrap '(ring.dump) | ||
| + ring.dump/app))))) | ||
| + | ||
| +(ring.jetty/run {:port 8080} app) |
16
src/ring/examples/hello_world.clj
| @@ -0,0 +1,16 @@ | ||
| +; A very simple Ring application. | ||
| + | ||
| +(ns ring.examples.hello_world | ||
| + (:require ring.jetty) | ||
| + (:import java.util.Date (java.text DateFormat SimpleDateFormat))) | ||
| + | ||
| +(def formatter (SimpleDateFormat. "HH:mm:ss")); | ||
| + | ||
| +(defn app | ||
| + [req] | ||
| + {:status 200 | ||
| + :headers {"Content-Type" "text/html"} | ||
| + :body (str "<h3>Hello World from Ring</h3>" | ||
| + "<p>The current time is " (.format formatter (Date.)) ".</p>")}) | ||
| + | ||
| +(ring.jetty/run {:port 8080} app) |
18
src/ring/examples/linted.clj
| @@ -0,0 +1,18 @@ | ||
| +; Like basic_stack.clj, but with the linter inserted between each component. | ||
| + | ||
| +(ns ring.examples.linted | ||
| + (:require (ring show-exceptions file file-info reloading lint dump jetty)) | ||
| + (:import (java.io File))) | ||
| + | ||
| +(def app | ||
| + (ring.lint/wrap | ||
| + (ring.show-exceptions/wrap | ||
| + (ring.lint/wrap | ||
| + (ring.file-info/wrap | ||
| + (ring.lint/wrap | ||
| + (ring.file/wrap (File. "src/ring/examples/public") | ||
| + (ring.lint/wrap | ||
| + (ring.reloading/wrap '(ring.dump) | ||
| + ring.dump/app))))))))) | ||
| + | ||
| +(ring.jetty/run {:port 8080} app) |
BIN
src/ring/examples/public/clojure.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49
src/ring/file.clj
| @@ -0,0 +1,49 @@ | ||
| +(ns ring.file | ||
| + (:use clojure.contrib.except | ||
| + ring.utils) | ||
| + (:import (java.io File))) | ||
| + | ||
| +(defn- ensure-dir | ||
| + "Ensures that the given directory exists." | ||
| + [dir] | ||
| + (throw-if-not (.exists dir) | ||
| + "Directory does not exist: %s" dir)) | ||
| + | ||
| +(defn- forbidden | ||
| + [] | ||
| + {:status 403 | ||
| + :headers {"Content-Type" "text/html"} | ||
| + :body "<html><body><h1>403 Forbidden</h1></body></html>"}) | ||
| + | ||
| +(defn- success | ||
| + [file] | ||
| + {:status 200 :headers {} :body file}) | ||
| + | ||
| +(defn- maybe-file | ||
| + "Returns the File corresponding to the given relative path within the given | ||
| + dir if it exists, or nil if no such file exists." | ||
| + [dir path] | ||
| + (let [file (File. dir path)] | ||
| + (and (.exists file) (.canRead file) file))) | ||
| + | ||
| +(defn wrap | ||
| + "Wrap an app such that a given directory is checked for a static file | ||
| + with which to respond to the request, proxying the request to the | ||
| + wrapped app if such a file does not exist." | ||
| + ([dir app] | ||
| + (ensure-dir dir) | ||
| + (fn [req] | ||
| + (if (#{:get :head} (:request-method req)) | ||
| + (let [uri (url-decode (:uri req))] | ||
| + (if (str-includes? ".." uri) | ||
| + (forbidden) | ||
| + (let [path (cond | ||
| + (.endsWith "/" uri) (str uri "index.html") | ||
| + (re-match? #"\.[a-z]+$" uri) uri | ||
| + :else (str uri ".html"))] | ||
| + (if-let [file (maybe-file dir path)] | ||
| + (success file) | ||
| + (app req))))) | ||
| + (app req)))) | ||
| + ([dir] | ||
| + (partial wrap dir))) |
79
src/ring/file_info.clj
| @@ -0,0 +1,79 @@ | ||
| +(ns ring.file-info | ||
| + (:use clojure.contrib.def) | ||
| + (:import org.apache.commons.io.FilenameUtils | ||
| + java.io.File)) | ||
| + | ||
| +(defvar- mime-type-map | ||
| + {"ai" "application/postscript" | ||
| + "asc" "text/plain" | ||
| + "avi" "video/x-msvideo" | ||
| + "bin" "application/octet-stream" | ||
| + "bmp" "image/bmp" | ||
| + "class" "application/octet-stream" | ||
| + "cer" "application/pkix-cert" | ||
| + "crl" "application/pkix-crl" | ||
| + "crt" "application/x-x509-ca-cert" | ||
| + "css" "text/css" | ||
| + "dms" "application/octet-stream" | ||
| + "doc" "application/msword" | ||
| + "dvi" "application/x-dvi" | ||
| + "eps" "application/postscript" | ||
| + "etx" "text/x-setext" | ||
| + "exe" "application/octet-stream" | ||
| + "gif" "image/gif" | ||
| + "htm" "text/html" | ||
| + "html" "text/html" | ||
| + "jpe" "image/jpeg" | ||
| + "jpeg" "image/jpeg" | ||
| + "jpg" "image/jpeg" | ||
| + "js" "text/javascript" | ||
| + "lha" "application/octet-stream" | ||
| + "lzh" "application/octet-stream" | ||
| + "mov" "video/quicktime" | ||
| + "mpe" "video/mpeg" | ||
| + "mpeg" "video/mpeg" | ||
| + "mpg" "video/mpeg" | ||
| + "pbm" "image/x-portable-bitmap" | ||
| + "pdf" "application/pdf" | ||
| + "pgm" "image/x-portable-graymap" | ||
| + "png" "image/png" | ||
| + "pnm" "image/x-portable-anymap" | ||
| + "ppm" "image/x-portable-pixmap" | ||
| + "ppt" "application/vnd.ms-powerpoint" | ||
| + "ps" "application/postscript" | ||
| + "qt" "video/quicktime" | ||
| + "ras" "image/x-cmu-raster" | ||
| + "rb" "text/plain" | ||
| + "rd" "text/plain" | ||
| + "rtf" "application/rtf" | ||
| + "sgm" "text/sgml" | ||
| + "sgml" "text/sgml" | ||
| + "tif" "image/tiff" | ||
| + "tiff" "image/tiff" | ||
| + "txt" "text/plain" | ||
| + "xbm" "image/x-xbitmap" | ||
| + "xls" "application/vnd.ms-excel" | ||
| + "xml" "text/xml" | ||
| + "xpm" "image/x-xpixmap" | ||
| + "xwd" "image/x-xwindowdump" | ||
| + "zip" "application/zip"}) | ||
| + | ||
| +(defn- guess-mime-type | ||
| + "Returns a String corresponding to the guessed mime type for the given file, | ||
| + or application/octet-stream if a type cannot be guessed." | ||
| + [#^File file] | ||
| + (get mime-type-map (FilenameUtils/getExtension (.getPath file)) | ||
| + "application/octet-stream")) | ||
| + | ||
| +(defn wrap | ||
| + "Wrap an app such that responses with a file a body will have | ||
| + corresponding Content-Type and Content-Length headers added if they are not | ||
| + allready present and can be determined from the file." | ||
| + [app] | ||
| + (fn [req] | ||
| + (let [{:keys [headers body] :as response} (app req)] | ||
| + (if (instance? File body) | ||
| + (assoc response :headers | ||
| + (assoc headers "Content-Length" (str (.length body)) | ||
| + "Content-Type" (guess-mime-type body))) | ||
| + response)))) |
87
src/ring/jetty.clj
| @@ -0,0 +1,87 @@ | ||
| +(ns ring.jetty | ||
| + (:import (javax.servlet.http HttpServletRequest HttpServletResponse) | ||
| + (org.mortbay.jetty.handler AbstractHandler) | ||
| + (org.mortbay.jetty Server) | ||
| + (java.io File FileInputStream InputStream OutputStream) | ||
| + (org.apache.commons.io IOUtils)) | ||
| + (:use (clojure.contrib fcase except))) | ||
| + | ||
| +(defn- req-map | ||
| + "Returns a map representing the given Servlet request, to be passed as the | ||
| + Ring request to an app." | ||
| + [#^HttpServletRequest request] | ||
| + {:server-port (.getServerPort request) | ||
| + :server-name (.getServerName request) | ||
| + :remote-addr (.getRemoteAddr request) | ||
| + :uri (.getRequestURI request) | ||
| + :query-string (.getQueryString request) | ||
| + :scheme (keyword (.getScheme request)) | ||
| + :request-method (keyword (.toLowerCase (.getMethod request))) | ||
| + :headers (reduce | ||
| + (fn [header-map #^String header-name] | ||
| + (assoc header-map | ||
| + (.toLowerCase header-name) | ||
| + (.getHeader request header-name))) | ||
| + {} | ||
| + (enumeration-seq (.getHeaderNames request))) | ||
| + :content-type (.getContentType request) | ||
| + :content-length (let [len (.getContentLength request)] | ||
| + (if (>= len 0) len)) | ||
| + :character-encoding (.getCharacterEncoding request) | ||
| + :body (.getInputStream request)}) | ||
| + | ||
| +(defn- apply-response-map | ||
| + "Apply the given response map to the servlet response, therby completing | ||
| + the HTTP response." | ||
| + [#^HttpServletResponse response {:keys [status headers body]}] | ||
| + ; Apply the status. | ||
| + (.setStatus response status) | ||
| + ; Apply the headers. | ||
| + (doseq [[key val-or-vals] headers] | ||
| + (if (string? val-or-vals) | ||
| + (.setHeader response key val-or-vals) | ||
| + (doseq [val val-or-vals] | ||
| + (.addHeader response key val)))) | ||
| + ; Apply the body - the method depends on the given body type. | ||
| + (cond | ||
| + (string? body) | ||
| + (with-open [writer (.getWriter response)] | ||
| + (.println writer body)) | ||
| + (instance? InputStream body) | ||
| + (let [#^InputStream in body] | ||
| + (with-open [out (.getOutputStream response)] | ||
| + (IOUtils/copy in out) | ||
| + (.close in) | ||
| + (.flush out))) | ||
| + (instance? File body) | ||
| + (let [#^File f body] | ||
| + (with-open [fin (FileInputStream. f)] | ||
| + (with-open [out (.getOutputStream response)] | ||
| + (IOUtils/copy fin out) | ||
| + (.flush out)))) | ||
| + (nil? body) | ||
| + nil | ||
| + :else | ||
| + (throwf "Unreceognized body: %s" body))) | ||
| + | ||
| +(defn- proxy-handler | ||
| + "Returns an Handler implementation for the given app." | ||
| + [app] | ||
| + (proxy [AbstractHandler] [] | ||
| + (handle [target request response dispatch] | ||
| + (let [req (req-map request) | ||
| + tuple (app req)] | ||
| + (apply-response-map response tuple) | ||
| + (.setHandled request true))))) | ||
| + | ||
| +(defn run | ||
| + "Serve the given app according to the options. | ||
| + Options: | ||
| + :port, an Integer." | ||
| + [options app] | ||
| + (let [port (or (:port options) (throwf ":port missing from options")) | ||
| + server (doto (Server. port) (.setSendDateHeader true)) | ||
| + handler (proxy-handler app)] | ||
| + (.setHandler server handler) | ||
| + (.start server) | ||
| + (.join server))) |
108
src/ring/lint.clj
| @@ -0,0 +1,108 @@ | ||
| +(ns ring.lint | ||
| + (:use clojure.set clojure.contrib.except) | ||
| + (:import (java.io File InputStream))) | ||
| + | ||
| +(defn lint | ||
| + "Asserts that spec applied to val returns logical truth, otherwise raises | ||
| + an exception with a message produced by applying format to the message-pattern | ||
| + argument and a printing of an invalid val." | ||
| + [val spec message] | ||
| + (try | ||
| + (if-not (spec val) | ||
| + (throwf "Ring lint error: specified %s, but %s was not" message (pr-str val))) | ||
| + (catch Exception e | ||
| + (if-not (re-find #"^Ring lint error: " (.getMessage e)) | ||
| + (throwf | ||
| + "Ring lint error: exception occured when checking that %s on %s: %s" | ||
| + message (pr-str val) (.getMessage e)) | ||
| + (throw e))))) | ||
| + | ||
| +(defn lint-namespacing | ||
| + "Asserts that all keys are namespaces other than those included in a | ||
| + specified set of permitted unnamspaced keys" | ||
| + [map map-name no-namespace-needed] | ||
| + (let [must-namespace (difference (set (keys map)) no-namespace-needed)] | ||
| + (doseq [k must-namespace] | ||
| + (lint k namespace | ||
| + (format "user keys in the %s map must be namespaced" map-name))))) | ||
| + | ||
| +(defn check-req | ||
| + "Validates the request, throwing an exception on violations of the spec" | ||
| + [req] | ||
| + (lint req map? | ||
| + "Ring request must a Clojure map") | ||
| + | ||
| + (lint (:server-port req) integer? | ||
| + ":server-port must be an Integer") | ||
| + (lint (:server-name req) string? | ||
| + ":server-name must be a String") | ||
| + (lint (:remote-addr req) string? | ||
| + ":remote-addr must be a String") | ||
| + (lint (:uri req) #(and (string? %) (.startsWith % "/")) | ||
| + ":uri must be a String starting with \"/\"") | ||
| + (lint (:query-string req) #(or (nil? %) (string? %)) | ||
| + ":query-string must be nil or a non-blank String") | ||
| + (lint (:scheme req) #{:http :https} | ||
| + ":scheme must be one of :http or :https") | ||
| + (lint (:request-method req) #{:get :head :options :put :post :delete} | ||
| + ":request-method must be one of :get, :head, :options, :put, :post, or :delete") | ||
| + (lint (:content-type req) #(or (nil? %) (string? %)) | ||
| + ":content-type must be nil or a String") | ||
| + (lint (:content-length req) #(or (nil? %) (integer? %)) | ||
| + ":content-length must be nil or an Integer") | ||
| + (lint (:character-encoding req) #(or (nil? %) (string? %)) | ||
| + ":character-encoding must be nil or a String") | ||
| + | ||
| + (let [headers (:headers req)] | ||
| + (lint headers map? | ||
| + ":headers must be a Clojure map") | ||
| + (doseq [[hname hval] headers] | ||
| + (lint hname string? | ||
| + "header names must be Strings") | ||
| + (lint hname #(= % (.toLowerCase %)) | ||
| + "header names must be in lower case") | ||
| + (lint hval string? | ||
| + "header values must be strings"))) | ||
| + | ||
| + (lint (:body req) #(or (nil? %) (instance? InputStream %)) | ||
| + ":body must be nil or an InputStream") | ||
| + | ||
| + (lint-namespacing req "request" | ||
| + #{:server-port :server-name :remote-addr :uri :query-string :scheme | ||
| + :request-method :content-type :content-length :character-encoding | ||
| + :headers :body})) | ||
| + | ||
| +(defn check-resp | ||
| + "Validates the response, throwing an exception on violations of the spec" | ||
| + [resp] | ||
| + (lint resp map? | ||
| + "Ring response must be a Clojure map") | ||
| + | ||
| + (lint (:status resp) #(and (integer? %) (>= % 100)) | ||
| + ":status must be an Intger greater than or equal to 100") | ||
| + | ||
| + (let [headers (:headers resp)] | ||
| + (lint headers map? | ||
| + ":headers must be a Clojure map") | ||
| + (doseq [[hname hval] headers] | ||
| + (lint hname string? | ||
| + "header names must Strings") | ||
| + (lint hval #(or (string? %) (every? string? %)) | ||
| + "header values must be Strings or colls of Strings"))) | ||
| + | ||
| + (lint (:body resp) #(or (nil? %) (string? %) (instance? File %) | ||
| + (instance? InputStream %)) | ||
| + ":body must a String, File, or InputStream") | ||
| + | ||
| + (lint-namespacing resp "response" | ||
| + #{:status :headers :body})) | ||
| + | ||
| +(defn wrap | ||
| + "Wrap an app to validate incoming requests and outgoing responses | ||
| + according to the Ring spec." | ||
| + [app] | ||
| + (fn [req] | ||
| + (check-req req) | ||
| + (let [resp (app req)] | ||
| + (check-resp resp) | ||
| + resp))) |
10
src/ring/reloading.clj
| @@ -0,0 +1,10 @@ | ||
| +(ns ring.reloading) | ||
| + | ||
| +(defn wrap | ||
| + "Wrap an app such that before a request is passed to the app, each namespace | ||
| + identified by syms in reloadables is reloaded." | ||
| + [reloadables app] | ||
| + (fn [req] | ||
| + (doseq [ns-sym reloadables] | ||
| + (require ns-sym :reload)) | ||
| + (app req))) |
93
src/ring/show_exceptions.clj
| @@ -0,0 +1,93 @@ | ||
| +(ns ring.show-exceptions | ||
| + (:use (clj-html core utils helpers) | ||
| + clojure.contrib.str-utils | ||
| + (clj-backtrace core repl) | ||
| + ring.utils)) | ||
| + | ||
| +(def css " | ||
| +/* | ||
| +Copyright (c) 2008, Yahoo! Inc. All rights reserved. | ||
| +Code licensed under the BSD License: | ||
| +http://developer.yahoo.net/yui/license.txt | ||
| +version: 2.6.0 | ||
| +*/ | ||
| +html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:text-top;}sub{vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}del,ins{text-decoration:none;} | ||
| + | ||
| + | ||
| +h3.info { | ||
| + font-size: 1.5em; | ||
| + margin-left: 1em; | ||
| + padding-top: .5em; | ||
| + padding-bottom: .5em; | ||
| +} | ||
| + | ||
| +table.trace { | ||
| + margin-left: 1em; | ||
| + background: lightgrey; | ||
| +} | ||
| + | ||
| +table.trace tr { | ||
| + line-height: 1.4em; | ||
| +} | ||
| + | ||
| +table.trace td.method { | ||
| + padding-left: .5em; | ||
| + text-aligh: left; | ||
| +} | ||
| + | ||
| +table.trace td.source { | ||
| + text-align: right; | ||
| +}") | ||
| + | ||
| +(defn- js-response [req e] | ||
| + {:status 500 | ||
| + :headers {"Content-Type" "text/javascript"} | ||
| + :body (pst-str e)}) | ||
| + | ||
| +(defn- html-reponse [req e] | ||
| + (let [excp (parse-exception e)] | ||
| + {:status 500 | ||
| + :headers {"Content-Type" "text/html"} | ||
| + :body | ||
| + (html | ||
| + (doctype :xhtml-transitional) | ||
| + [:html {:xmlns "http://www.w3.org/1999/xhtml"} | ||
| + [:head | ||
| + [:meta {:http-equiv "Content-Type" :content "text/html;charset=utf-8"}] | ||
| + [:title "Ring: Show Exceptions"] | ||
| + [:style {:type "text/css"} css] | ||
| + [:body | ||
| + [:div#content | ||
| + [:h3.info (h (str e))] | ||
| + [:table.trace | ||
| + (domap-str [parsed (:trace-elems excp)] | ||
| + (html | ||
| + [:tbody | ||
| + (if (:clojure parsed) | ||
| + (html | ||
| + [:tr | ||
| + [:td.source (h (source-str parsed))] | ||
| + [:td.method (h (clojure-method-str parsed))]]) | ||
| + (html | ||
| + [:tr | ||
| + [:td.source (h (source-str parsed))] | ||
| + [:td.method (h (java-method-str parsed))]]))]))]]]]])})) | ||
| + | ||
| +(defn- response | ||
| + "Returns a response showing debugging information about the exception. | ||
| + Currently supports delegation to either js or html exception views." | ||
| + [req e] | ||
| + (let [accept (get-in req [:headers "accept"])] | ||
| + (if (and accept (re-match? #"^text/javascript" accept)) | ||
| + (js-response req e) | ||
| + (html-reponse req e)))) | ||
| + | ||
| +(defn wrap | ||
| + "Wrap an app such that exceptions thrown within the wrapped app are caught | ||
| + and a helpful debugging response is returned." | ||
| + [app] | ||
| + (fn [req] | ||
| + (try | ||
| + (app req) | ||
| + (catch Exception e | ||
| + (response req e))))) |
25
src/ring/utils.clj
| @@ -0,0 +1,25 @@ | ||
| +(ns ring.utils | ||
| + (:use clojure.contrib.str-utils)) | ||
| + | ||
| +(defn url-decode | ||
| + "Returns the form-url-decoded version of the given string." | ||
| + [encoded] | ||
| + (java.net.URLDecoder/decode encoded "UTF-8")) | ||
| + | ||
| +(defn str-includes? | ||
| + "Returns logical truth iff the given target appears in the given string" | ||
| + [target string] | ||
| + (<= 0 (.indexOf string target))) | ||
| + | ||
| +(defn re-match? | ||
| + "Returns true iff the given string contains a match for the given pattern." | ||
| + [#^java.util.regex.Pattern pattern string] | ||
| + (.find (.matcher pattern string))) | ||
| + | ||
| +(defn re-get | ||
| + "Returns the nth captured group resulting from matching the given pattern | ||
| + against the given string, or nil if no match is found." | ||
| + [re n s] | ||
| + (let [m (re-matcher re s)] | ||
| + (if (.find m) | ||
| + (.group m n)))) |
1
test/ring/assets/foo.html
| @@ -0,0 +1 @@ | ||
| +foo |
1
test/ring/assets/index.html
| @@ -0,0 +1 @@ | ||
| +index |
1
test/ring/assets/plain.txt
| @@ -0,0 +1 @@ | ||
| +plain |
1
test/ring/assets/random.xyz
| @@ -0,0 +1 @@ | ||
| +random |
10
test/ring/builder_test.clj
| @@ -0,0 +1,10 @@ | ||
| +(ns ring.builder-test | ||
| + (:use clj-unit.core ring.builder)) | ||
| + | ||
| +(deftest "wrap-if" | ||
| + (let [core-app inc | ||
| + wrapper-fn (fn [app] (fn [req] (+ 2 (app req)))) | ||
| + unwrapped (wrap-if false wrapper-fn core-app) | ||
| + wrapped (wrap-if true wrapper-fn core-app)] | ||
| + (assert= 1 (unwrapped 0)) | ||
| + (assert= 3 (wrapped 0)))) |
18
test/ring/dump_test.clj
| @@ -0,0 +1,18 @@ | ||
| +(ns ring.dump-test | ||
| + (:use clj-unit.core ring.dump) | ||
| + (:import java.io.ByteArrayInputStream)) | ||
| + | ||
| +(def post-req | ||
| + {:uri "/foo/bar" | ||
| + :request-method :post | ||
| + :body (ByteArrayInputStream. (.getBytes "post body"))}) | ||
| + | ||
| +(def get-req | ||
| + {:uri "/foo/bar" | ||
| + :request-method :get}) | ||
| + | ||
| +(deftest "app" | ||
| + (let [{:keys [status]} (app post-req)] | ||
| + (assert= 200 status)) | ||
| + (let [{:keys [status]} (app get-req)] | ||
| + (assert= 200 status))) |
26
test/ring/file_info_test.clj
| @@ -0,0 +1,26 @@ | ||
| +(ns ring.file-info-test | ||
| + (:use clj-unit.core ring.file-info) | ||
| + (:import java.io.File)) | ||
| + | ||
| +(def non-file-app (wrap (constantly {:headers {} :body "body"}))) | ||
| + | ||
| +(def known-file (File. "test/ring/assets/plain.txt")) | ||
| +(def known-file-app (wrap (constantly {:headers {} :body known-file}))) | ||
| + | ||
| +(def unknown-file (File. "test/ring/assets/random.xyz")) | ||
| +(def unknown-file-app (wrap (constantly {:headers {} :body unknown-file}))) | ||
| + | ||
| +(deftest "wrap: non-file response" | ||
| + (assert= {:headers {} :body "body"} (non-file-app {}))) | ||
| + | ||
| +(deftest "wrap: known file response" | ||
| + (assert= | ||
| + {:headers {"Content-Type" "text/plain" "Content-Length" "6"} | ||
| + :body known-file} | ||
| + (known-file-app {}))) | ||
| + | ||
| +(deftest "wrap: unknown file resonse" | ||
| + (assert= | ||
| + {:headers {"Content-Type" "application/octet-stream" "Content-Length" "7"} | ||
| + :body unknown-file} | ||
| + (unknown-file-app {}))) |
42
test/ring/file_test.clj
| @@ -0,0 +1,42 @@ | ||
| +(ns ring.file-test | ||
| + (:use clj-unit.core ring.file) | ||
| + (:import java.io.File)) | ||
| + | ||
| +(deftest "wrap: no directory" | ||
| + (assert-throws #"Directory does not exist" | ||
| + (wrap (File. "not_here") (constantly :response)))) | ||
| + | ||
| +(def public-dir (File. "test/ring/assets")) | ||
| +(def index-html (File. public-dir "index.html")) | ||
| +(def foo-html (File. public-dir "foo.html")) | ||
| + | ||
| +(def app (wrap public-dir (constantly :response))) | ||
| + | ||
| +(deftest "wrap: unsafe method" | ||
| + (assert= :response (app {:request-method :post :uri "/foo"}))) | ||
| + | ||
| +(deftest "wrap: forbidden url" | ||
| + (let [{:keys [status body]} (app {:request-method :get :uri "/../foo"})] | ||
| + (assert= 403 status) | ||
| + (assert-match #"Forbidden" body))) | ||
| + | ||
| +(deftest "wrap: directory" | ||
| + (let [{:keys [status headers body]} (app {:request-method :get :uri "/"})] | ||
| + (assert= 200 status) | ||
| + (assert= {} headers) | ||
| + (assert= index-html body))) | ||
| + | ||
| +(deftest "wrap: file without extension" | ||
| + (let [{:keys [status headers body]} (app {:request-method :get :uri "/foo"})] | ||
| + (assert= 200 status) | ||
| + (assert= {} headers) | ||
| + (assert= foo-html body))) | ||
| + | ||
| +(deftest "wrap: file with extension" | ||
| + (let [{:keys [status headers body]} (app {:request-method :get :uri "/foo.html"})] | ||
| + (assert= 200 status) | ||
| + (assert= {} headers) | ||
| + (assert= foo-html body))) | ||
| + | ||
| +(deftest "wrap: no file" | ||
| + (assert= :response (app {:request-method :get :uri "/dynamic"}))) |
129
test/ring/lint_test.clj
| @@ -0,0 +1,129 @@ | ||
| +(ns ring.lint-test | ||
| + (:use clj-unit.core ring.lint) | ||
| + (:import (java.io File InputStream ByteArrayInputStream))) | ||
| + | ||
| +(defn str-input-stream | ||
| + "Returns a ByteArrayInputStream for the given String." | ||
| + [string] | ||
| + (ByteArrayInputStream. (.getBytes string))) | ||
| + | ||
| +(def valid-request | ||
| + {:server-port 80 | ||
| + :server-name "localhost" | ||
| + :remote-addr "192.0.2.235" | ||
| + :uri "/" | ||
| + :query-string nil | ||
| + :scheme :http | ||
| + :request-method :get | ||
| + :headers {} | ||
| + :content-type nil | ||
| + :content-length nil | ||
| + :character-encoding nil | ||
| + :body nil}) | ||
| + | ||
| +(def valid-response | ||
| + {:status 200 :headers {} :body "valid"}) | ||
| + | ||
| +(def valid-response-app | ||
| + (wrap (fn [req] valid-response))) | ||
| + | ||
| +(defn constant-app | ||
| + [constant-response] | ||
| + (wrap (fn [req] constant-response))) | ||
| + | ||
| + | ||
| +(defmacro lints-req | ||
| + [key goods bads] | ||
| + (let [good-name (str "request " key " valid values") | ||
| + bad-name (str "request " key " invalid values")] | ||
| + `(do | ||
| + (deftest ~good-name | ||
| + (doseq [good# ~goods] | ||
| + (assert-truth (= valid-response | ||
| + (valid-response-app (assoc valid-request ~key good#))) | ||
| + (format "%s is a valid value for request key %s" | ||
| + (pr-str good#) ~key)))) | ||
| + (deftest ~bad-name | ||
| + (doseq [bad# ~bads] | ||
| + (assert-throws #"Ring lint error:" | ||
| + (valid-response-app (assoc valid-request ~key bad#)))))))) | ||
| + | ||
| +(defmacro lints-resp | ||
| + [key goods bads] | ||
| + (let [good-name (str "response " key " valid values") | ||
| + bad-name (str "response " key " invalid values")] | ||
| + `(do | ||
| + (deftest ~good-name | ||
| + (doseq [good# ~goods] | ||
| + (let [response# (assoc valid-response ~key good#) | ||
| + app# (constant-app response#)] | ||
| + (assert-truth (= response# (app# valid-request)) | ||
| + (format "%s is a valid value for response key %s" | ||
| + (pr-str good#) ~key))))) | ||
| + (deftest ~bad-name | ||
| + (doseq [bad# ~bads] | ||
| + (let [response# (assoc valid-response ~key bad#) | ||
| + app# (constant-app response#)] | ||
| + (assert-throws #"Ring lint error:" | ||
| + (app# valid-request)))))))) | ||
| + | ||
| +(lints-req :server-port | ||
| + [80 8080] | ||
| + [nil "80"]) | ||
| + | ||
| +(lints-req :server-name | ||
| + ["localhost" "www.amazon.com" "192.0.2.235"] | ||
| + [nil 123]) | ||
| + | ||
| +(lints-req :remote-addr | ||
| + ["192.0.2.235" "0:0:0:0:0:0:0:1%0"] | ||
| + [nil 123]) | ||
| + | ||
| +(lints-req :uri | ||
| + ["/" "/foo" "/foo/bar"] | ||
| + [nil ""]) | ||
| + | ||
| +(lints-req :query-string | ||
| + [nil "" "foo=bar" "foo=bar&biz=bat"] | ||
| + [:foo]) | ||
| + | ||
| +(lints-req :scheme | ||
| + [:http :https] | ||
| + [nil "http"]) | ||
| + | ||
| +(lints-req :request-method | ||
| + [:get :head :options :put :post :delete] | ||
| + [nil :foobar "get"]) | ||
| + | ||
| +(lints-req :content-type | ||
| + [nil "text/html"] | ||
| + [:text/html]) | ||
| + | ||
| +(lints-req :content-length | ||
| + [nil 123] | ||
| + ["123"]) | ||
| + | ||
| +(lints-req :character-encoding | ||
| + [nil "UTF-8"] | ||
| + [:utf-8]) | ||
| + | ||
| +(lints-req :headers | ||
| + [{"foo" "bar"} {"bat" "Biz"} {"whiz-bang" "high-low"}] | ||
| + [nil {:foo "bar"} {"bar" :foo} {"Bar" "foo"}]) | ||
| + | ||
| +(lints-req :body | ||
| + [nil (str-input-stream "thebody")] | ||
| + ["thebody" :thebody]) | ||
| + | ||
| + | ||
| +(lints-resp :status | ||
| + [100 301 500] | ||
| + [nil "100" 99]) | ||
| + | ||
| +(lints-resp :headers | ||
| + [{} {"foo" "bar"} {"Biz" "Bat"} {"vert" ["high" "low"]} {"horz" #{"left right"}}] | ||
| + [nil {:foo "bar"} {"foo" :bar} {"dir" 123}]) | ||
| + | ||
| +(lints-resp :body | ||
| + [nil "thebody" (str-input-stream "thebody") (File. "test/ring/assets/foo.html")] | ||
| + [123 :thebody]) |
8
test/ring/reloading_test.clj
| @@ -0,0 +1,8 @@ | ||
| +(ns ring.reloading-test | ||
| + (:use clj-unit.core ring.reloading)) | ||
| + | ||
| +(def app (constantly :response)) | ||
| + | ||
| +(deftest "wrap" | ||
| + (let [wrapped (wrap '(ring.reloading) app)] | ||
| + (assert= :response (wrapped :request)))) |
15
test/ring/show_exceptions_test.clj
| @@ -0,0 +1,15 @@ | ||
| +(ns ring.show-exceptions-test | ||
| + (:use clj-unit.core ring.show-exceptions)) | ||
| + | ||
| +(def app (wrap #(throw (Exception. "fail")))) | ||
| + | ||
| +(def html-req {}) | ||
| +(def js-req {:headers {"accept" "text/javascript"}}) | ||
| + | ||
| +(deftest "wrap" | ||
| + (let [{:keys [status headers] :as response} (app html-req)] | ||
| + (assert= 500 status) | ||
| + (assert= {"Content-Type" "text/html"} headers)) | ||
| + (let [{:keys [status headers]} (app js-req)] | ||
| + (assert= 500 status) | ||
| + (assert= {"Content-Type" "text/javascript"} headers))) |
9
test/run.clj
| @@ -0,0 +1,9 @@ | ||
| +(use 'clj-unit.core) | ||
| +(require-and-run-tests | ||
| + 'ring.builder-test | ||
| + 'ring.dump-test | ||
| + 'ring.lint-test | ||
| + 'ring.file-test | ||
| + 'ring.file-info-test | ||
| + 'ring.reloading-test | ||
| + 'ring.show-exceptions-test) |
0 comments on commit
68e08fb