mirror of
https://github.com/metosin/reitit.git
synced 2025-12-18 08:51:12 +00:00
Add create-file-handler, fixes #395
This commit is contained in:
parent
711c1fea8f
commit
fd0d7cc46c
3 changed files with 200 additions and 169 deletions
|
|
@ -30,6 +30,7 @@ is called the first time, so that `rfe/push-state` and such can be called
|
||||||
### `reitit-ring`
|
### `reitit-ring`
|
||||||
|
|
||||||
* `reitit.ring/routes` strips away `nil` routes, fixes [#394](https://github.com/metosin/reitit/issues/394)
|
* `reitit.ring/routes` strips away `nil` routes, fixes [#394](https://github.com/metosin/reitit/issues/394)
|
||||||
|
* `reitit.ring/create-file-handler` to serve files from classpah, fixes [#395](https://github.com/metosin/reitit/issues/395)
|
||||||
|
|
||||||
## 0.4.2 (2020-01-17)
|
## 0.4.2 (2020-01-17)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,48 @@
|
||||||
;; TODO: ring.middleware.not-modified/wrap-not-modified
|
;; TODO: ring.middleware.not-modified/wrap-not-modified
|
||||||
;; TODO: ring.middleware.head/wrap-head
|
;; TODO: ring.middleware.head/wrap-head
|
||||||
;; TODO: handle etags
|
;; TODO: handle etags
|
||||||
|
(defn -create-file-or-resource-handler
|
||||||
|
[response-fn {:keys [parameter root path loader allow-symlinks? index-files paths not-found-handler]
|
||||||
|
:or {parameter (keyword "")
|
||||||
|
root "public"
|
||||||
|
index-files ["index.html"]
|
||||||
|
paths (constantly nil)
|
||||||
|
not-found-handler (constantly {:status 404, :body "", :headers {}})}}]
|
||||||
|
(let [options {:root root
|
||||||
|
:loader loader
|
||||||
|
:index-files? false
|
||||||
|
:allow-symlinks? allow-symlinks?}
|
||||||
|
path-size (count path)
|
||||||
|
create (fn [handler]
|
||||||
|
(fn
|
||||||
|
([request] (handler request))
|
||||||
|
([request respond _] (respond (handler request)))))
|
||||||
|
join-paths (fn [& paths]
|
||||||
|
(str/replace (str/replace (str/join "/" paths) #"([/]+)" "/") #"/$" ""))
|
||||||
|
response (fn [path]
|
||||||
|
(if-let [response (or (paths (join-paths "/" path))
|
||||||
|
(response-fn path options))]
|
||||||
|
(response/content-type response (mime-type/ext-mime-type path))))
|
||||||
|
path-or-index-response (fn [path uri]
|
||||||
|
(or (response path)
|
||||||
|
(loop [[file & files] index-files]
|
||||||
|
(if file
|
||||||
|
(if (response (join-paths path file))
|
||||||
|
(response/redirect (join-paths uri file))
|
||||||
|
(recur files))))))
|
||||||
|
handler (if path
|
||||||
|
(fn [request]
|
||||||
|
(let [uri (:uri request)]
|
||||||
|
(if-let [path (if (>= (count uri) path-size) (subs uri path-size))]
|
||||||
|
(path-or-index-response path uri))))
|
||||||
|
(fn [request]
|
||||||
|
(let [uri (:uri request)
|
||||||
|
path (-> request :path-params parameter)]
|
||||||
|
(or (path-or-index-response path uri)
|
||||||
|
(not-found-handler request)))))]
|
||||||
|
(create handler))))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
(defn create-resource-handler
|
(defn create-resource-handler
|
||||||
"A ring handler for serving classpath resources, configured via options:
|
"A ring handler for serving classpath resources, configured via options:
|
||||||
|
|
||||||
|
|
@ -202,42 +244,25 @@
|
||||||
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)"
|
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)"
|
||||||
([]
|
([]
|
||||||
(create-resource-handler nil))
|
(create-resource-handler nil))
|
||||||
([{:keys [parameter root path loader allow-symlinks? index-files paths not-found-handler]
|
([opts]
|
||||||
:or {parameter (keyword "")
|
(-create-file-or-resource-handler response/resource-response opts))))
|
||||||
root "public"
|
|
||||||
index-files ["index.html"]
|
#?(:clj
|
||||||
paths (constantly nil)
|
(defn create-file-handler
|
||||||
not-found-handler (constantly {:status 404, :body "", :headers {}})}}]
|
"A ring handler for serving file resources, configured via options:
|
||||||
(let [options {:root root, :loader loader, :allow-symlinks? allow-symlinks?}
|
|
||||||
path-size (count path)
|
| key | description |
|
||||||
create (fn [handler]
|
| -------------------|-------------|
|
||||||
(fn
|
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
|
||||||
([request] (handler request))
|
| :root | optional resource root, defaults to `\"public\"`
|
||||||
([request respond _] (respond (handler request)))))
|
| :path | optional path to mount the handler to. Works only if mounted outside of a router.
|
||||||
join-paths (fn [& paths]
|
| :loader | optional class loader to resolve the resources
|
||||||
(str/replace (str/replace (str/join "/" paths) #"([/]+)" "/") #"/$" ""))
|
| :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]`
|
||||||
resource-response (fn [path]
|
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)"
|
||||||
(if-let [response (or (paths (join-paths "/" path))
|
([]
|
||||||
(response/resource-response path options))]
|
(create-file-handler nil))
|
||||||
(response/content-type response (mime-type/ext-mime-type path))))
|
([opts]
|
||||||
path-or-index-response (fn [path uri]
|
(-create-file-or-resource-handler response/file-response opts))))
|
||||||
(or (resource-response path)
|
|
||||||
(loop [[file & files] index-files]
|
|
||||||
(if file
|
|
||||||
(if (resource-response (join-paths path file))
|
|
||||||
(response/redirect (join-paths uri file))
|
|
||||||
(recur files))))))
|
|
||||||
handler (if path
|
|
||||||
(fn [request]
|
|
||||||
(let [uri (:uri request)]
|
|
||||||
(if-let [path (if (>= (count uri) path-size) (subs uri path-size))]
|
|
||||||
(path-or-index-response path uri))))
|
|
||||||
(fn [request]
|
|
||||||
(let [uri (:uri request)
|
|
||||||
path (-> request :path-params parameter)]
|
|
||||||
(or (path-or-index-response path uri)
|
|
||||||
(not-found-handler request)))))]
|
|
||||||
(create handler)))))
|
|
||||||
|
|
||||||
(defn create-enrich-request [inject-match? inject-router?]
|
(defn create-enrich-request [inject-match? inject-router?]
|
||||||
(cond
|
(cond
|
||||||
|
|
|
||||||
|
|
@ -454,158 +454,163 @@
|
||||||
(app request)))))))
|
(app request)))))))
|
||||||
|
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(deftest resource-handler-test
|
(deftest file-resource-handler-test
|
||||||
(let [redirect (fn [uri] {:status 302, :body "", :headers {"Location" uri}})
|
(let [redirect (fn [uri] {:status 302, :body "", :headers {"Location" uri}})
|
||||||
request (fn [uri] {:uri uri, :request-method :get})]
|
request (fn [uri] {:uri uri, :request-method :get})]
|
||||||
(testing "inside a router"
|
|
||||||
|
|
||||||
(testing "from root"
|
(doseq [[name create] [["resource-handler" ring/create-resource-handler]
|
||||||
(let [app (ring/ring-handler
|
["file-handler" #(ring/create-file-handler (assoc % :root "dev-resources/public"))]]]
|
||||||
(ring/router
|
|
||||||
["/*" (ring/create-resource-handler)])
|
|
||||||
(ring/create-default-handler))]
|
|
||||||
(testing test
|
|
||||||
(testing "different file-types"
|
|
||||||
(let [response (app (request "/hello.json"))]
|
|
||||||
(is (= "application/json" (get-in response [:headers "Content-Type"])))
|
|
||||||
(is (get-in response [:headers "Last-Modified"]))
|
|
||||||
(is (= "{\"hello\": \"file\"}" (slurp (:body response)))))
|
|
||||||
(let [response (app (request "/hello.xml"))]
|
|
||||||
(is (= "text/xml" (get-in response [:headers "Content-Type"])))
|
|
||||||
(is (get-in response [:headers "Last-Modified"]))
|
|
||||||
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body response))))))
|
|
||||||
|
|
||||||
(testing "index-files"
|
(testing (str "for " name)
|
||||||
(let [response (app (request "/docs"))]
|
(testing "inside a router"
|
||||||
(is (= (redirect "/docs/index.html") response)))
|
|
||||||
(let [response (app (request "/docs/"))]
|
|
||||||
(is (= (redirect "/docs/index.html") response))))
|
|
||||||
|
|
||||||
(testing "not found"
|
(testing "from root"
|
||||||
(let [response (app (request "/not-found"))]
|
(let [app (ring/ring-handler
|
||||||
(is (= 404 (:status response)))))
|
(ring/router
|
||||||
|
["/*" (create nil)])
|
||||||
|
(ring/create-default-handler))]
|
||||||
|
(testing test
|
||||||
|
(testing "different file-types"
|
||||||
|
(let [response (app (request "/hello.json"))]
|
||||||
|
(is (= "application/json" (get-in response [:headers "Content-Type"])))
|
||||||
|
(is (get-in response [:headers "Last-Modified"]))
|
||||||
|
(is (= "{\"hello\": \"file\"}" (slurp (:body response)))))
|
||||||
|
(let [response (app (request "/hello.xml"))]
|
||||||
|
(is (= "text/xml" (get-in response [:headers "Content-Type"])))
|
||||||
|
(is (get-in response [:headers "Last-Modified"]))
|
||||||
|
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body response))))))
|
||||||
|
|
||||||
(testing "3-arity"
|
(testing "index-files"
|
||||||
(let [result (atom nil)
|
(let [response (app (request "/docs"))]
|
||||||
respond (partial reset! result)
|
(is (= (redirect "/docs/index.html") response)))
|
||||||
raise ::not-called]
|
(let [response (app (request "/docs/"))]
|
||||||
(app (request "/hello.xml") respond raise)
|
(is (= (redirect "/docs/index.html") response))))
|
||||||
(is (= "text/xml" (get-in @result [:headers "Content-Type"])))
|
|
||||||
(is (get-in @result [:headers "Last-Modified"]))
|
|
||||||
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body @result)))))))))
|
|
||||||
|
|
||||||
(testing "from path"
|
(testing "not found"
|
||||||
(let [app (ring/ring-handler
|
(let [response (app (request "/not-found"))]
|
||||||
(ring/router
|
(is (= 404 (:status response)))))
|
||||||
["/files/*" (ring/create-resource-handler)])
|
|
||||||
(ring/create-default-handler))
|
|
||||||
request #(request (str "/files" %))
|
|
||||||
redirect #(redirect (str "/files" %))]
|
|
||||||
(testing test
|
|
||||||
(testing "different file-types"
|
|
||||||
(let [response (app (request "/hello.json"))]
|
|
||||||
(is (= "application/json" (get-in response [:headers "Content-Type"])))
|
|
||||||
(is (get-in response [:headers "Last-Modified"]))
|
|
||||||
(is (= "{\"hello\": \"file\"}" (slurp (:body response)))))
|
|
||||||
(let [response (app (request "/hello.xml"))]
|
|
||||||
(is (= "text/xml" (get-in response [:headers "Content-Type"])))
|
|
||||||
(is (get-in response [:headers "Last-Modified"]))
|
|
||||||
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body response))))))
|
|
||||||
|
|
||||||
(testing "index-files"
|
(testing "3-arity"
|
||||||
(let [response (app (request "/docs"))]
|
(let [result (atom nil)
|
||||||
(is (= (redirect "/docs/index.html") response)))
|
respond (partial reset! result)
|
||||||
(let [response (app (request "/docs/"))]
|
raise ::not-called]
|
||||||
(is (= (redirect "/docs/index.html") response))))
|
(app (request "/hello.xml") respond raise)
|
||||||
|
(is (= "text/xml" (get-in @result [:headers "Content-Type"])))
|
||||||
|
(is (get-in @result [:headers "Last-Modified"]))
|
||||||
|
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body @result)))))))))
|
||||||
|
|
||||||
(testing "not found"
|
(testing "from path"
|
||||||
(let [response (app (request "/not-found"))]
|
(let [app (ring/ring-handler
|
||||||
(is (= 404 (:status response)))))
|
(ring/router
|
||||||
|
["/files/*" (create nil)])
|
||||||
|
(ring/create-default-handler))
|
||||||
|
request #(request (str "/files" %))
|
||||||
|
redirect #(redirect (str "/files" %))]
|
||||||
|
(testing test
|
||||||
|
(testing "different file-types"
|
||||||
|
(let [response (app (request "/hello.json"))]
|
||||||
|
(is (= "application/json" (get-in response [:headers "Content-Type"])))
|
||||||
|
(is (get-in response [:headers "Last-Modified"]))
|
||||||
|
(is (= "{\"hello\": \"file\"}" (slurp (:body response)))))
|
||||||
|
(let [response (app (request "/hello.xml"))]
|
||||||
|
(is (= "text/xml" (get-in response [:headers "Content-Type"])))
|
||||||
|
(is (get-in response [:headers "Last-Modified"]))
|
||||||
|
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body response))))))
|
||||||
|
|
||||||
(testing "3-arity"
|
(testing "index-files"
|
||||||
(let [result (atom nil)
|
(let [response (app (request "/docs"))]
|
||||||
respond (partial reset! result)
|
(is (= (redirect "/docs/index.html") response)))
|
||||||
raise ::not-called]
|
(let [response (app (request "/docs/"))]
|
||||||
(app (request "/hello.xml") respond raise)
|
(is (= (redirect "/docs/index.html") response))))
|
||||||
(is (= "text/xml" (get-in @result [:headers "Content-Type"])))
|
|
||||||
(is (get-in @result [:headers "Last-Modified"]))
|
|
||||||
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body @result))))))))))
|
|
||||||
|
|
||||||
(testing "outside a router"
|
(testing "not found"
|
||||||
|
(let [response (app (request "/not-found"))]
|
||||||
|
(is (= 404 (:status response)))))
|
||||||
|
|
||||||
(testing "from root"
|
(testing "3-arity"
|
||||||
(let [app (ring/ring-handler
|
(let [result (atom nil)
|
||||||
(ring/router [])
|
respond (partial reset! result)
|
||||||
(ring/routes
|
raise ::not-called]
|
||||||
(ring/create-resource-handler {:path "/"})
|
(app (request "/hello.xml") respond raise)
|
||||||
(ring/create-default-handler)))]
|
(is (= "text/xml" (get-in @result [:headers "Content-Type"])))
|
||||||
(testing test
|
(is (get-in @result [:headers "Last-Modified"]))
|
||||||
(testing "different file-types"
|
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body @result))))))))))
|
||||||
(let [response (app (request "/hello.json"))]
|
|
||||||
(is (= "application/json" (get-in response [:headers "Content-Type"])))
|
|
||||||
(is (get-in response [:headers "Last-Modified"]))
|
|
||||||
(is (= "{\"hello\": \"file\"}" (slurp (:body response)))))
|
|
||||||
(let [response (app (request "/hello.xml"))]
|
|
||||||
(is (= "text/xml" (get-in response [:headers "Content-Type"])))
|
|
||||||
(is (get-in response [:headers "Last-Modified"]))
|
|
||||||
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body response))))))
|
|
||||||
|
|
||||||
(testing "index-files"
|
(testing "outside a router"
|
||||||
(let [response (app (request "/docs"))]
|
|
||||||
(is (= (redirect "/docs/index.html") response)))
|
|
||||||
(let [response (app (request "/docs/"))]
|
|
||||||
(is (= (redirect "/docs/index.html") response))))
|
|
||||||
|
|
||||||
(testing "not found"
|
(testing "from root"
|
||||||
(let [response (app (request "/not-found"))]
|
(let [app (ring/ring-handler
|
||||||
(is (= 404 (:status response)))))
|
(ring/router [])
|
||||||
|
(ring/routes
|
||||||
|
(create {:path "/"})
|
||||||
|
(ring/create-default-handler)))]
|
||||||
|
(testing test
|
||||||
|
(testing "different file-types"
|
||||||
|
(let [response (app (request "/hello.json"))]
|
||||||
|
(is (= "application/json" (get-in response [:headers "Content-Type"])))
|
||||||
|
(is (get-in response [:headers "Last-Modified"]))
|
||||||
|
(is (= "{\"hello\": \"file\"}" (slurp (:body response)))))
|
||||||
|
(let [response (app (request "/hello.xml"))]
|
||||||
|
(is (= "text/xml" (get-in response [:headers "Content-Type"])))
|
||||||
|
(is (get-in response [:headers "Last-Modified"]))
|
||||||
|
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body response))))))
|
||||||
|
|
||||||
(testing "3-arity"
|
(testing "index-files"
|
||||||
(let [result (atom nil)
|
(let [response (app (request "/docs"))]
|
||||||
respond (partial reset! result)
|
(is (= (redirect "/docs/index.html") response)))
|
||||||
raise ::not-called]
|
(let [response (app (request "/docs/"))]
|
||||||
(app (request "/hello.xml") respond raise)
|
(is (= (redirect "/docs/index.html") response))))
|
||||||
(is (= "text/xml" (get-in @result [:headers "Content-Type"])))
|
|
||||||
(is (get-in @result [:headers "Last-Modified"]))
|
|
||||||
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body @result)))))))))
|
|
||||||
|
|
||||||
(testing "from path"
|
(testing "not found"
|
||||||
(let [app (ring/ring-handler
|
(let [response (app (request "/not-found"))]
|
||||||
(ring/router [])
|
(is (= 404 (:status response)))))
|
||||||
(ring/routes
|
|
||||||
(ring/create-resource-handler {:path "/files"})
|
|
||||||
(ring/create-default-handler)))
|
|
||||||
request #(request (str "/files" %))
|
|
||||||
redirect #(redirect (str "/files" %))]
|
|
||||||
(testing test
|
|
||||||
(testing "different file-types"
|
|
||||||
(let [response (app (request "/hello.json"))]
|
|
||||||
(is (= "application/json" (get-in response [:headers "Content-Type"])))
|
|
||||||
(is (get-in response [:headers "Last-Modified"]))
|
|
||||||
(is (= "{\"hello\": \"file\"}" (slurp (:body response)))))
|
|
||||||
(let [response (app (request "/hello.xml"))]
|
|
||||||
(is (= "text/xml" (get-in response [:headers "Content-Type"])))
|
|
||||||
(is (get-in response [:headers "Last-Modified"]))
|
|
||||||
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body response))))))
|
|
||||||
|
|
||||||
(testing "index-files"
|
(testing "3-arity"
|
||||||
(let [response (app (request "/docs"))]
|
(let [result (atom nil)
|
||||||
(is (= (redirect "/docs/index.html") response)))
|
respond (partial reset! result)
|
||||||
(let [response (app (request "/docs/"))]
|
raise ::not-called]
|
||||||
(is (= (redirect "/docs/index.html") response))))
|
(app (request "/hello.xml") respond raise)
|
||||||
|
(is (= "text/xml" (get-in @result [:headers "Content-Type"])))
|
||||||
|
(is (get-in @result [:headers "Last-Modified"]))
|
||||||
|
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body @result)))))))))
|
||||||
|
|
||||||
(testing "not found"
|
(testing "from path"
|
||||||
(let [response (app (request "/not-found"))]
|
(let [app (ring/ring-handler
|
||||||
(is (= 404 (:status response)))))
|
(ring/router [])
|
||||||
|
(ring/routes
|
||||||
|
(create {:path "/files"})
|
||||||
|
(ring/create-default-handler)))
|
||||||
|
request #(request (str "/files" %))
|
||||||
|
redirect #(redirect (str "/files" %))]
|
||||||
|
(testing test
|
||||||
|
(testing "different file-types"
|
||||||
|
(let [response (app (request "/hello.json"))]
|
||||||
|
(is (= "application/json" (get-in response [:headers "Content-Type"])))
|
||||||
|
(is (get-in response [:headers "Last-Modified"]))
|
||||||
|
(is (= "{\"hello\": \"file\"}" (slurp (:body response)))))
|
||||||
|
(let [response (app (request "/hello.xml"))]
|
||||||
|
(is (= "text/xml" (get-in response [:headers "Content-Type"])))
|
||||||
|
(is (get-in response [:headers "Last-Modified"]))
|
||||||
|
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body response))))))
|
||||||
|
|
||||||
(testing "3-arity"
|
(testing "index-files"
|
||||||
(let [result (atom nil)
|
(let [response (app (request "/docs"))]
|
||||||
respond (partial reset! result)
|
(is (= (redirect "/docs/index.html") response)))
|
||||||
raise ::not-called]
|
(let [response (app (request "/docs/"))]
|
||||||
(app (request "/hello.xml") respond raise)
|
(is (= (redirect "/docs/index.html") response))))
|
||||||
(is (= "text/xml" (get-in @result [:headers "Content-Type"])))
|
|
||||||
(is (get-in @result [:headers "Last-Modified"]))
|
(testing "not found"
|
||||||
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body @result)))))))))))))
|
(let [response (app (request "/not-found"))]
|
||||||
|
(is (= 404 (:status response)))))
|
||||||
|
|
||||||
|
(testing "3-arity"
|
||||||
|
(let [result (atom nil)
|
||||||
|
respond (partial reset! result)
|
||||||
|
raise ::not-called]
|
||||||
|
(app (request "/hello.xml") respond raise)
|
||||||
|
(is (= "text/xml" (get-in @result [:headers "Content-Type"])))
|
||||||
|
(is (get-in @result [:headers "Last-Modified"]))
|
||||||
|
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body @result)))))))))))))))
|
||||||
|
|
||||||
(deftest router-available-in-default-branch
|
(deftest router-available-in-default-branch
|
||||||
(testing "1-arity"
|
(testing "1-arity"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue