From f8a43f0996e4983e69be9b9f4c7a02d1179aa1ba Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Wed, 25 Apr 2018 08:32:01 +0300 Subject: [PATCH] use ring for serving resources. --- CHANGELOG.md | 6 ++ doc/ring/static.md | 16 +-- modules/reitit-ring/project.clj | 3 +- modules/reitit-ring/src/reitit/ring.cljc | 58 +++++------ modules/reitit-ring/src/reitit/ring/mime.cljc | 99 ------------------- perf-test/clj/reitit/static_perf_test.clj | 65 ++++++++++-- project.clj | 2 + 7 files changed, 104 insertions(+), 145 deletions(-) delete mode 100644 modules/reitit-ring/src/reitit/ring/mime.cljc diff --git a/CHANGELOG.md b/CHANGELOG.md index 266de275..546b974d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,12 @@ * new helper `reitit.ring/router` to compose routes outside of a router. * `reitit.ring/create-resource-handler` function to serve static routes. See (docs)[https://metosin.github.io/reitit/ring/static.html]. +* new dependencies: + +```clj +[ring/ring-core "1.6.3"] +``` + ### `reitit-swagger` * New module to produce swagger-docs from routing tree, including `Coercion` definitions. Works with both middleware & interceptors and Schema & Spec. See [docs](https://metosin.github.io/reitit/swagger.html). diff --git a/doc/ring/static.md b/doc/ring/static.md index a63427ff..64beb482 100644 --- a/doc/ring/static.md +++ b/doc/ring/static.md @@ -8,7 +8,6 @@ There are two options to serve the files. This is good option if static files can be from non-conflicting paths, e.g. `"/assets/*"`. - ```clj (require '[reitit.ring :as ring]) @@ -50,13 +49,16 @@ To serve files from conflicting paths, e.g. `"/*"`, one option is to mount them `reitit.ring/create-resource-handler` takes optionally an options map to configure how the files are being served. -| key | description | -| -------------|-------------| -| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:` -| :root | optional resource root, defaults to `"public"` -| :mime-types | optional extension->mime-type mapping, defaults to `reitit.ring.mime/default-types` -| :path | optional path to mount the handler to. Works only if mounted outside of a router. +| key | description | +| -----------------|-------------| +| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:` +| :root | optional resource root, defaults to `"public"` +| :path | optional path to mount the handler to. Works only if mounted outside of a router. +| :loader | optional class loader to resolve the resources +| :allow-symlinks? | allow symlinks that lead to paths outside the root classpath directories, defaults to `false` ### TODO * support for things like `:cache`, `:last-modified?`, `:index-files` and `:gzip` +* support for ClojureScript +* serve from file-system diff --git a/modules/reitit-ring/project.clj b/modules/reitit-ring/project.clj index 45fb455b..059df996 100644 --- a/modules/reitit-ring/project.clj +++ b/modules/reitit-ring/project.clj @@ -6,4 +6,5 @@ :plugins [[lein-parent "0.3.2"]] :parent-project {:path "../../project.clj" :inherit [:deploy-repositories :managed-dependencies]} - :dependencies [[metosin/reitit-core]]) + :dependencies [[metosin/reitit-core] + [ring/ring-core]]) diff --git a/modules/reitit-ring/src/reitit/ring.cljc b/modules/reitit-ring/src/reitit/ring.cljc index 6d20d42b..62cde8b4 100644 --- a/modules/reitit-ring/src/reitit/ring.cljc +++ b/modules/reitit-ring/src/reitit/ring.cljc @@ -1,11 +1,11 @@ (ns reitit.ring (:require [meta-merge.core :refer [meta-merge]] [reitit.middleware :as middleware] - [reitit.ring.mime :as mime] [reitit.core :as r] [reitit.impl :as impl] - #?(:clj - [clojure.java.io :as io]))) + #?@(:clj [ + [ring.util.mime-type :as mime-type] + [ring.util.response :as response]]))) (def http-methods #{:get :head :post :put :delete :connect :options :trace :patch}) (defrecord Methods [get head post put delete connect options trace patch]) @@ -71,42 +71,36 @@ (defn create-resource-handler "A ring handler for serving classpath resources, configured via options: - | key | description | + | key | description | | -----------------|-------------| - | :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:` - | :root | optional resource root, defaults to `\"public\"` - | :mime-types | optional extension->mime-type mapping, defaults to `reitit.ring.mime/default-types` + | :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:` + | :root | optional resource root, defaults to `\"public\"` | :path | optional path to mount the handler to. Works only if mounted outside of a router. | :loader | optional class loader to resolve the resources | :allow-symlinks? | allow symlinks that lead to paths outside the root classpath directories, defaults to `false`" ([] (create-resource-handler nil)) - ([{:keys [parameter root mime-types path loader allow-symlinks?] + ([{:keys [parameter root path loader allow-symlinks?] :or {parameter (keyword "") - root "public" - mime-types mime/default-mime-types}}] - (let [response (fn [file] - {:status 200 - :body file - :headers {"Content-Type" (mime/ext-mime-type (.getName file) mime-types)}})] - (if path - (let [path-size (count path) - serve (fn [req] - (let [uri (:uri req)] - (if (and (>= (count uri) path-size)) - (some->> (str root (subs uri path-size)) io/resource io/file response))))] - (fn - ([req] - (serve req)) - ([req respond _] - (respond (serve req))))) - (let [serve (fn [req] - (or (some->> req :path-params parameter (str root "/") io/resource io/file response) - {:status 404}))] - (fn ([req] - (serve req)) - ([req respond _] - (respond (serve req)))))))))) + root "public"}}] + (let [options {:root root, :loader loader, :allow-symlinks? allow-symlinks?} + path-size (count path) + create (fn [handler] + (fn + ([request] (handler request)) + ([request respond _] (respond (handler request))))) + response (fn [path] + (if-let [response (response/resource-response path options)] + (response/content-type response (mime-type/ext-mime-type path)))) + handler (if path + (fn [request] + (let [uri (:uri request)] + (if (>= (count uri) path-size) + (response (subs uri path-size))))) + (fn [request] + (let [path (-> request :path-params parameter)] + (or (response path) {:status 404}))))] + (create handler))))) (defn ring-handler "Creates a ring-handler out of a ring-router. diff --git a/modules/reitit-ring/src/reitit/ring/mime.cljc b/modules/reitit-ring/src/reitit/ring/mime.cljc deleted file mode 100644 index 3e7b6de3..00000000 --- a/modules/reitit-ring/src/reitit/ring/mime.cljc +++ /dev/null @@ -1,99 +0,0 @@ -(ns reitit.ring.mime - (:require [clojure.string :as str])) - -(def default-mime-types - "A map of file extensions to mime-types." - {"7z" "application/x-7z-compressed" - "aac" "audio/aac" - "ai" "application/postscript" - "appcache" "text/cache-manifest" - "asc" "text/plain" - "atom" "application/atom+xml" - "avi" "video/x-msvideo" - "bin" "application/octet-stream" - "bmp" "image/bmp" - "bz2" "application/x-bzip" - "class" "application/octet-stream" - "cer" "application/pkix-cert" - "crl" "application/pkix-crl" - "crt" "application/x-x509-ca-cert" - "css" "text/css" - "csv" "text/csv" - "deb" "application/x-deb" - "dart" "application/dart" - "dll" "application/octet-stream" - "dmg" "application/octet-stream" - "dms" "application/octet-stream" - "doc" "application/msword" - "dvi" "application/x-dvi" - "edn" "application/edn" - "eot" "application/vnd.ms-fontobject" - "eps" "application/postscript" - "etx" "text/x-setext" - "exe" "application/octet-stream" - "flv" "video/x-flv" - "flac" "audio/flac" - "gif" "image/gif" - "gz" "application/gzip" - "htm" "text/html" - "html" "text/html" - "ico" "image/x-icon" - "iso" "application/x-iso9660-image" - "jar" "application/java-archive" - "jpe" "image/jpeg" - "jpeg" "image/jpeg" - "jpg" "image/jpeg" - "js" "text/javascript" - "json" "application/json" - "lha" "application/octet-stream" - "lzh" "application/octet-stream" - "mov" "video/quicktime" - "m4v" "video/mp4" - "mp3" "audio/mpeg" - "mp4" "video/mp4" - "mpe" "video/mpeg" - "mpeg" "video/mpeg" - "mpg" "video/mpeg" - "oga" "audio/ogg" - "ogg" "audio/ogg" - "ogv" "video/ogg" - "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" - "rar" "application/x-rar-compressed" - "ras" "image/x-cmu-raster" - "rb" "text/plain" - "rd" "text/plain" - "rss" "application/rss+xml" - "rtf" "application/rtf" - "sgm" "text/sgml" - "sgml" "text/sgml" - "svg" "image/svg+xml" - "swf" "application/x-shockwave-flash" - "tar" "application/x-tar" - "tif" "image/tiff" - "tiff" "image/tiff" - "ttf" "application/x-font-ttf" - "txt" "text/plain" - "webm" "video/webm" - "wmv" "video/x-ms-wmv" - "woff" "application/font-woff" - "xbm" "image/x-xbitmap" - "xls" "application/vnd.ms-excel" - "xml" "text/xml" - "xpm" "image/x-xpixmap" - "xwd" "image/x-xwindowdump" - "zip" "application/zip"}) - -(defn file-ext [name] - (if-let [i (str/last-index-of name ".")] - (subs name (inc i)))) - -(defn ext-mime-type [name mime-types] - (-> name file-ext mime-types)) diff --git a/perf-test/clj/reitit/static_perf_test.clj b/perf-test/clj/reitit/static_perf_test.clj index bce93d0f..90cba0b1 100644 --- a/perf-test/clj/reitit/static_perf_test.clj +++ b/perf-test/clj/reitit/static_perf_test.clj @@ -4,7 +4,10 @@ [reitit.ring :as ring] [clojure.java.io :as io] [criterium.core :as cc] - [ring.util.mime-type :as mime])) + [ring.util.response] + [ring.middleware.defaults] + [ring.middleware.resource] + [ring.util.mime-type])) ;; ;; start repl with `lein perf repl` @@ -21,14 +24,14 @@ ;; Memory: 16 GB ;; -(def app +(def app1 (ring/ring-handler (ring/router [["/ping" (constantly {:status 200, :body "pong"})] ["/files/*" (ring/create-resource-handler)]]) (ring/create-default-handler))) -(def app +(def app2 (ring/ring-handler (ring/router ["/ping" (constantly {:status 200, :body "pong"})]) @@ -36,19 +39,69 @@ (ring/create-resource-handler {:path "/files"}) (ring/create-default-handler)))) +(def wrap-resource + (-> (constantly {:status 200, :body "pong"}) + (ring.middleware.resource/wrap-resource "public"))) + +(def wrap-defaults + (-> (constantly {:status 200, :body "pong"}) + (ring.middleware.defaults/wrap-defaults ring.middleware.defaults/site-defaults))) + (comment (def server (web/run #'app {:port 3000, :dispatch? false, :server {:always-set-keep-alive false}})) (routing-test)) +(defn bench-resources [] + + ;; 134µs + (cc/quick-bench + (ring.util.response/resource-response "hello.json" {:root "public"})) + + ;; 144µs + (cc/quick-bench + (app1 {:request-method :get, :uri "/files/hello.json"})) + + ;; 144µs + (cc/quick-bench + (app2 {:request-method :get, :uri "/files/hello.json"})) + + ;; 143µs + (cc/quick-bench + (wrap-resource {:request-method :get, :uri "/hello.json"})) + + ;; 163µs + (cc/quick-bench + (wrap-defaults {:request-method :get, :uri "/hello.json"}))) + +(defn bench-handler [] + + ;; 140ns + (cc/quick-bench + (app1 {:request-method :get, :uri "/ping"})) + + ;; 134ns + (cc/quick-bench + (app2 {:request-method :get, :uri "/ping"})) + + ;; 108µs + (cc/quick-bench + (wrap-resource {:request-method :get, :uri "/ping"})) + + ;; 146µs + (cc/quick-bench + (wrap-defaults {:request-method :get, :uri "/ping"}))) + (comment + (bench-resources) + (bench-handler) + (let [file (-> "logback.xml" io/resource io/file) name (.getName file)] ;; 639ns (cc/quick-bench - (mime/ext-mime-type name)) - + (ring.util.mime-type/ext-mime-type name)) ;; 106ns (cc/quick-bench - (ext-mime-type name)))) + (reitit.ring.mime/ext-mime-type name reitit.ring.mime/default-mime-types)))) diff --git a/project.clj b/project.clj index 8927ed63..257c8415 100644 --- a/project.clj +++ b/project.clj @@ -17,6 +17,7 @@ [metosin/reitit-swagger "0.1.1-SNAPSHOT"] [meta-merge "1.0.0"] + [ring/ring-core "1.6.3"] [metosin/spec-tools "0.6.2-SNAPSHOT"] [metosin/schema-tools "0.10.2-SNAPSHOT"]] @@ -60,6 +61,7 @@ "-Dclojure.compiler.direct-linking=true"] :test-paths ["perf-test/clj"] :dependencies [[compojure "1.6.1"] + [ring/ring-defaults "0.3.1"] [ikitommi/immutant-web "3.0.0-alpha1"] [io.pedestal/pedestal.route "0.5.3"] [org.clojure/core.async "0.4.474"]