This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-02-16
Channels
- # ai (2)
- # announcements (9)
- # asami (6)
- # babashka (6)
- # beginners (49)
- # clojure (33)
- # clojure-europe (31)
- # clojure-nl (2)
- # clojure-norway (29)
- # clojure-uk (12)
- # clr (3)
- # cursive (3)
- # data-science (27)
- # datomic (13)
- # emacs (1)
- # graalvm (12)
- # gratitude (1)
- # hyperfiddle (39)
- # integrant (14)
- # leiningen (1)
- # matrix (3)
- # music (1)
- # off-topic (39)
- # other-languages (3)
- # pathom (7)
- # pedestal (19)
- # polylith (5)
- # portal (9)
- # releases (4)
- # shadow-cljs (8)
- # spacemacs (11)
- # squint (7)
- # testing (11)
Hello! I've tried replacing the resource
interceptor with fast-resource
interceptor to use the :index?
functionality like this:
(ns com.example.demoapp.service
(:require
[io.pedestal.http :as http]
[io.pedestal.http.route :refer [routes-from]]
[io.pedestal.http.ring-middlewares :as middlewares]
[io.pedestal.environment :refer [dev-mode?]]
[com.example.demoapp.routes :as routes]))
(defn static-content-interceptors [service-map]
(let [resource-path (::http/resource-path service-map)
fast-resource (middlewares/fast-resource resource-path)]
(-> service-map
(update
::http/interceptors
#(->> %
(mapcat
(fn [item]
(if (= (:name item) ::middlewares/resource)
[fast-resource]
[item])))
(into []))))))
(defn service-map [opts]
(let [{:keys [dev-mode]
:or {dev-mode dev-mode?}} opts]
(-> {::http/port 8080
::http/type :jetty
::http/routes (routes-from (routes/routes))
::http/resource-path "public"
::http/secure-headers {:content-security-policy-settings
{:script-src "'self' 'unsafe-inline' 'unsafe-eval' https: http:"
:object-src "'none'"}}
::http/join? false}
http/default-interceptors
static-content-interceptors
(cond-> dev-mode http/dev-interceptors))))
(comment
(-> (service-map nil)
::http/interceptors))
While it fetches static content from the ::http/resource-path
correctly, it responds with
Content-Type: application/octet-stream
instead of Content-Type: text/html
(or the respective content type of the fetched item)
Is this a problem with fast-resource
interceptor or content-type
interceptor? Is there a better way to achieve the :index?
functionality that the fast-resource
interceptor uses?To answer my own question, it seems that even though fast-resource
interceptor supports the :index?
functionality it has some major drawbacks:
• For uri s that end in "/", even when it picks a matching index file it does not update the request map and the content-type
interceptor checks and assigns a content type according to the request :uri
property
• Due to hardcoded File
usage does not work from within an uberjar
So in the end I wrote this simple dir-index
interceptor to match my needs:
(ns com.example.demoapp.service
(:require
[clojure.string :as str]
[io.pedestal.http :as http]
[io.pedestal.http.route :refer [routes-from]]
[io.pedestal.http.ring-middlewares :as middlewares]
[io.pedestal.interceptor :refer [interceptor]]
[io.pedestal.environment :refer [dev-mode?]]
[ring.middleware.resource :as resource]
[ring.util.request :as ring-request]
[com.example.demoapp.routes :as routes]))
(defn dir-index [root-path]
(interceptor
{:name ::dir-index
:enter (fn [context]
(let [{:keys [request]} context
path (ring-request/path-info request)]
(if (str/ends-with? path "/")
(let [index-path (str path "index.html")
index-request (-> request
(assoc :path-info index-path)
(assoc :uri index-path))
response (resource/resource-request index-request root-path)]
(if response
(-> context
(assoc :request index-request)
(assoc :response response))
context))
context)))}))
(defn static-content-interceptors [service-map]
(let [resource-path (::http/resource-path service-map)
dir-index (dir-index resource-path)]
(-> service-map
(update
::http/interceptors
#(->> %
(mapcat
(fn [item]
(if (= (:name item) ::middlewares/resource)
[item dir-index]
[item])))
(into []))))))
(defn service-map [opts]
(let [{:keys [dev-mode]
:or {dev-mode dev-mode?}} opts]
(-> {::http/port 8080
::http/type :jetty
::http/routes (routes-from (routes/routes))
::http/resource-path "public"
::http/secure-headers {:content-security-policy-settings
{:script-src "'self' 'unsafe-inline' 'unsafe-eval' https: http:"
:object-src "'none'"}}
::http/join? false}
http/default-interceptors
static-content-interceptors
(cond-> dev-mode http/dev-interceptors))))
(comment
(-> (service-map nil)
::http/interceptors))
The interceptor couples 2 things that you could ply apart for greater flexibility. thing 1) translates xyz/ to xyz/index.html, thing 2) responds as if someone asked for xyz/index.html. However, your app can probably already respond (or at least it ought to be able) to the xyz/index.html request. So this interceptor could limit itself to tweaking the request uri, and let downstream interceptors handle the resulting tweaked uri.
The interceptor is inserted right after the ::middlewares/resource
interceptor as a fallback for it
Concretely, the job of knowing xyz/ === xyz/index.html is distinct from the job of knowing that index.html will definitely be a resource. Pedestal's scheme of interceptors is ideal for this scenario, because infer-index-html-interceptor can have only an Enter half (which adjusts the uri) and can precede the resource interceptor that knows how to respond to xyz/index.html
So first the ::middlewares/resource
interceptor tries to fetch the requested url from ::http/resource-path
and if it fails, dir-index
checks if the path is a directory (ends with /
) and if so it tries in a similar way to fetch an index.html
from the ::http/resource-path
. It also updates the :request
in context
along with the :response
so when the content-type
leave interceptor runs it assigns the proper Content-Type
header
Yeah I suppose I could also make an interceptor that just checks and modifies the uri
instead of actually loading the resource as I do
Then it would work equally well for resource-served index.html and (theoretically) index.html generated by other means by other interceptors
Yeah I think the problem with that is that we cannot know beforehand that an uri ending in /
has to be translated into an index.html
Because we want uris like /api/products/
to still work and not be mapped to /api/products/index.html
The way resource interceptor works is that it just tries to find the :uri
in the resource-path first, and if it fails the interceptor chain continues to the router interceptor
The problem with that, is that we actually have to do the same type of file/resource lookup that the resource
interceptor does in our interceptor (thus duplicating it, only the lookup part)
Hmm after a little bit of digging it shouldn't be that much work: https://github.com/pedestal/pedestal/blob/bf00bc66a9d9ba229d92bf609492bbb4a2e883ab/service/src/io/pedestal/http/ring_middlewares.clj#L144 https://github.com/ring-clojure/ring/blob/a7b0b508c8ca7614a2deffb3142ea14b522654f7/ring-core/src/ring/middleware/resource.clj#L18 https://github.com/ring-clojure/ring/blob/a7b0b508c8ca7614a2deffb3142ea14b522654f7/ring-core/src/ring/util/response.clj#L341
It also seems that the current resource/resource-request
does not actually load the data, it just constructs a ring response with a resource path to them? So my interceptor kinda does what you describe?
The only real difference would be now to just assign the new :request
to the context, do not assign a :response
and add the interceptor before the resource
interceptor
Yeah I updated my interceptor to follow the discussed concept, here it is for future reference:
(ns com.example.demoapp.service
(:require
[clojure.string :as str]
[io.pedestal.http :as http]
[io.pedestal.http.route :refer [routes-from]]
[io.pedestal.http.ring-middlewares :as middlewares]
[io.pedestal.interceptor :refer [interceptor]]
[io.pedestal.environment :refer [dev-mode?]]
[ring.middleware.resource :as resource]
[ring.util.request :as ring-request]
[com.example.demoapp.routes :as routes]))
(defn dir-index [root-path]
(interceptor
{:name ::dir-index
:enter (fn [context]
(let [{:keys [request]} context
path (ring-request/path-info request)]
(if (str/ends-with? path "/")
(let [index-path (str path "index.html")
index-request (-> request
(assoc :path-info index-path)
(assoc :uri index-path))
response (resource/resource-request index-request root-path)]
(if response
(assoc context :request index-request)
context))
context)))}))
(defn static-content-interceptors [service-map]
(let [resource-path (::http/resource-path service-map)
dir-index (dir-index resource-path)]
(-> service-map
(update
::http/interceptors
#(->> %
(mapcat
(fn [item]
(if (= (:name item) ::middlewares/resource)
[dir-index item]
[item])))
(into []))))))
(defn service-map [opts]
(let [{:keys [dev-mode]
:or {dev-mode dev-mode?}} opts]
(-> {::http/port 8080
::http/type :jetty
::http/routes (routes-from (routes/routes))
::http/resource-path "public"
::http/secure-headers {:content-security-policy-settings
{:script-src "'self' 'unsafe-inline' 'unsafe-eval' https: http:"
:object-src "'none'"}}
::http/join? false}
http/default-interceptors
static-content-interceptors
(cond-> dev-mode http/dev-interceptors))))