mirror of
https://github.com/metosin/reitit.git
synced 2025-12-16 16:01:11 +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/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)
|
||||
|
||||
|
|
|
|||
|
|
@ -189,6 +189,48 @@
|
|||
;; TODO: ring.middleware.not-modified/wrap-not-modified
|
||||
;; TODO: ring.middleware.head/wrap-head
|
||||
;; 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
|
||||
"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)"
|
||||
([]
|
||||
(create-resource-handler nil))
|
||||
([{: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, :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) #"([/]+)" "/") #"/$" ""))
|
||||
resource-response (fn [path]
|
||||
(if-let [response (or (paths (join-paths "/" path))
|
||||
(response/resource-response path options))]
|
||||
(response/content-type response (mime-type/ext-mime-type path))))
|
||||
path-or-index-response (fn [path uri]
|
||||
(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)))))
|
||||
([opts]
|
||||
(-create-file-or-resource-handler response/resource-response opts))))
|
||||
|
||||
#?(:clj
|
||||
(defn create-file-handler
|
||||
"A ring handler for serving file resources, configured via options:
|
||||
|
||||
| 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
|
||||
| :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]`
|
||||
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)"
|
||||
([]
|
||||
(create-file-handler nil))
|
||||
([opts]
|
||||
(-create-file-or-resource-handler response/file-response opts))))
|
||||
|
||||
(defn create-enrich-request [inject-match? inject-router?]
|
||||
(cond
|
||||
|
|
|
|||
|
|
@ -454,158 +454,163 @@
|
|||
(app request)))))))
|
||||
|
||||
#?(:clj
|
||||
(deftest resource-handler-test
|
||||
(deftest file-resource-handler-test
|
||||
(let [redirect (fn [uri] {:status 302, :body "", :headers {"Location" uri}})
|
||||
request (fn [uri] {:uri uri, :request-method :get})]
|
||||
(testing "inside a router"
|
||||
|
||||
(testing "from root"
|
||||
(let [app (ring/ring-handler
|
||||
(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))))))
|
||||
(doseq [[name create] [["resource-handler" ring/create-resource-handler]
|
||||
["file-handler" #(ring/create-file-handler (assoc % :root "dev-resources/public"))]]]
|
||||
|
||||
(testing "index-files"
|
||||
(let [response (app (request "/docs"))]
|
||||
(is (= (redirect "/docs/index.html") response)))
|
||||
(let [response (app (request "/docs/"))]
|
||||
(is (= (redirect "/docs/index.html") response))))
|
||||
(testing (str "for " name)
|
||||
(testing "inside a router"
|
||||
|
||||
(testing "not found"
|
||||
(let [response (app (request "/not-found"))]
|
||||
(is (= 404 (:status response)))))
|
||||
(testing "from root"
|
||||
(let [app (ring/ring-handler
|
||||
(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"
|
||||
(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)))))))))
|
||||
(testing "index-files"
|
||||
(let [response (app (request "/docs"))]
|
||||
(is (= (redirect "/docs/index.html") response)))
|
||||
(let [response (app (request "/docs/"))]
|
||||
(is (= (redirect "/docs/index.html") response))))
|
||||
|
||||
(testing "from path"
|
||||
(let [app (ring/ring-handler
|
||||
(ring/router
|
||||
["/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 "not found"
|
||||
(let [response (app (request "/not-found"))]
|
||||
(is (= 404 (:status response)))))
|
||||
|
||||
(testing "index-files"
|
||||
(let [response (app (request "/docs"))]
|
||||
(is (= (redirect "/docs/index.html") response)))
|
||||
(let [response (app (request "/docs/"))]
|
||||
(is (= (redirect "/docs/index.html") 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)))))))))
|
||||
|
||||
(testing "not found"
|
||||
(let [response (app (request "/not-found"))]
|
||||
(is (= 404 (:status response)))))
|
||||
(testing "from path"
|
||||
(let [app (ring/ring-handler
|
||||
(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"
|
||||
(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))))))))))
|
||||
(testing "index-files"
|
||||
(let [response (app (request "/docs"))]
|
||||
(is (= (redirect "/docs/index.html") response)))
|
||||
(let [response (app (request "/docs/"))]
|
||||
(is (= (redirect "/docs/index.html") response))))
|
||||
|
||||
(testing "outside a router"
|
||||
(testing "not found"
|
||||
(let [response (app (request "/not-found"))]
|
||||
(is (= 404 (:status response)))))
|
||||
|
||||
(testing "from root"
|
||||
(let [app (ring/ring-handler
|
||||
(ring/router [])
|
||||
(ring/routes
|
||||
(ring/create-resource-handler {: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"
|
||||
(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))))))))))
|
||||
|
||||
(testing "index-files"
|
||||
(let [response (app (request "/docs"))]
|
||||
(is (= (redirect "/docs/index.html") response)))
|
||||
(let [response (app (request "/docs/"))]
|
||||
(is (= (redirect "/docs/index.html") response))))
|
||||
(testing "outside a router"
|
||||
|
||||
(testing "not found"
|
||||
(let [response (app (request "/not-found"))]
|
||||
(is (= 404 (:status response)))))
|
||||
(testing "from root"
|
||||
(let [app (ring/ring-handler
|
||||
(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"
|
||||
(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)))))))))
|
||||
(testing "index-files"
|
||||
(let [response (app (request "/docs"))]
|
||||
(is (= (redirect "/docs/index.html") response)))
|
||||
(let [response (app (request "/docs/"))]
|
||||
(is (= (redirect "/docs/index.html") response))))
|
||||
|
||||
(testing "from path"
|
||||
(let [app (ring/ring-handler
|
||||
(ring/router [])
|
||||
(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 "not found"
|
||||
(let [response (app (request "/not-found"))]
|
||||
(is (= 404 (:status response)))))
|
||||
|
||||
(testing "index-files"
|
||||
(let [response (app (request "/docs"))]
|
||||
(is (= (redirect "/docs/index.html") response)))
|
||||
(let [response (app (request "/docs/"))]
|
||||
(is (= (redirect "/docs/index.html") 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)))))))))
|
||||
|
||||
(testing "not found"
|
||||
(let [response (app (request "/not-found"))]
|
||||
(is (= 404 (:status response)))))
|
||||
(testing "from path"
|
||||
(let [app (ring/ring-handler
|
||||
(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"
|
||||
(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)))))))))))))
|
||||
(testing "index-files"
|
||||
(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"
|
||||
(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
|
||||
(testing "1-arity"
|
||||
|
|
|
|||
Loading…
Reference in a new issue