I've added an introduction page to https://duct-framework.org/. The docs are now at https://duct-framework.org/docs and of course there's a big button on the new index page linking to them.
I was following the latest version of the documentation and I can reproduce an error consistently. Under the 3. Web Applications section, the third example of middleware configuration fails. My understanding is that the code (update response :headers merge headers) attempts to update the :headers key and it doesn't exist in response. It seems that when the middleware is configured at the route level, the other middlewares are not applied (you don't get a ring response back from the (handler ,,,) call).
java.lang.IllegalArgumentException: Key must be integer
APersistentVector.java:410 clojure.lang.APersistentVector.assoc
APersistentVector.java:19 clojure.lang.APersistentVector.assoc
RT.java:847 clojure.lang.RT.assoc
core.clj:193 clojure.core/assoc
core.clj:6261 clojure.core/update
core.clj:6251 clojure.core/update
middleware.clj:8 todo.middleware/wrap-headers[fn]
web.clj:91 duct.middleware.web/wrap-hiccup[fn]
[...]The call to (handler ,,,) with :middleware key defined at route level returns:
[:html {:lang "en"} [:head [:title "Hello world"]] [:body [:h1 "Hello world"]]]
Moving the :middleware to the :duct.module/web config
{:status 200, :headers {"Content-Type" "text/html;charset=UTF-8", "Set-Cookie" ("ring-session=259915dc-4275-4b2a-bae2-952f51ea10d6; Path=/; HttpOnly; SameSite=Strict"), "Content-Length" "111", "X-Frame-Options" "SAMEORIGIN", "X-Content-Type-Options" "nosniff"}, :body "\nHello world Hello world
"}Thanks for the report. Let me look into it.
This is for the :route-middleware example, right? Looks like the module puts the Hiccup middleware after the user middleware, when it should go before.
No, the 3rd example, defining the middleware at next to the :get definition ...
{:system
{:duct.module/logging {}
:duct.module/web
{:features #{:site :hiccup}
:routes [["/" {:get :todo.routes/index
:middleware [#ig/ref :todo.middleware/wrap-headers]}]]}
:todo.middleware/wrap-headers {"Index-Only" "True"}}}The :route-middleware works fine
Ahh, right. So the ordering was correct for :route-middleware , but of course if we add middleware to the routes directly that comes before the global route middleware... and that sounds like the correct thing to do.
Let me give this a little thought.
I've been going back and forth on what the best option for the Hiccup middleware is. Returning a raw vector is convenient, but perhaps introduces more problems.
Perhaps it's just a documentation update, stating that when you define the middleware in the route, it becomes the first in the chain? (or something along those lines) ... and the documentation must specify that the middleware code also needs to be updated, from (update response :headers merge headers) to {:headers headers :body response}
I think the issue with the documentation is indicative of a more fundamental problem with the Hiccup middleware. It might be that it's overall more predictable if it expects a handler to return {:body [:html ...]}.
So I've thought about this a little, and I think the solution is to remove the feature that allows you to return a standalone Hiccup vector. Instead, the {:body [:html ...]} syntax will be favoured.
The reason for this is that returning a vector breaks any middleware that expects a response map. Now, the {:body [:html ...]} form is a incomplete response, in that it lacks keys for :status and :headers, but middleware can typically deal with missing keys in a map far better than they can deal with an entirely different type.
Ultimately I think we need something more flexible than Muuntaja for content negotiation. Ideally I want a way of returning a bunch of content in different formats, and then have the content negotiation pick the correct one. The problem with Muuntaja is that it's based around encoding a data structure in different formats - but it's entirely possible to return HTML and JSON from the same HTTP resource.
However, that's a future project, I think.