ataraxy
ataraxy copied to clipboard
How to set headers, especially in middleware
I'm very new to this, so sorry if this makes no sense.
If I have a handler which returns a vector like [::response/ok "Hello"] then I can't see how to set any custom headers on the response. It seems like you need to return a map instead, but then you have to include the content type explicitly as well which is a bit inconvenient.
This seems to be even more of an issue in middleware. If my handler returns a map then I can set the headers in middleware as in the example in the readme (although I think there is a typo - it should be :headers not :header). However, if the handler returns a vector then this throws an error (obviously, but it took me some time to work this out since I assumed that I could just copy and paste stuff from the readme!). I don't know how to write middleware that can handle both scenarios.
If there's any easy way around these issues, it would be great if it could be documented.
It looks like mddleware currently doesn't work with handlers that return vectors; but it should do. Consider this a bug; I'll try to get it fixed either this week or next.
Regarding adding headers in, the first way to solve it is to just use maps. The ring-http-response library may help if you take this route.
The other way is to create your own result handler. Handlers are resolved recursively until a map is returned, so a result with [::response/ok "Hello"] will be passed to the handler associated with ::response/ok, or, if none exist, it will be resolved by ataraxy.handler/default.
So suppose you want to add in a custom "ok" response that you want to be globally available. You might write something like:
(defmethod handler/sync-default ::ok [{result :ataraxy/result}]
(-> (handler/sync-default result) (resp/header "X-Example" "Foo")))
Thanks for the response - that's very helpful.
On a related note, is it possible to combine middleware? I've been trying to add authentication/authorization, splitting it into an authentication middleware applied to the whole routes table and an authorization one applied to a specific route, but I could only get 1 middleware being run.
Yes, multiple middleware functions are composed together. Can you produce an example where this doesn't work?
Sorry for the delayed reply. Yes, actually multiple middleware functions are composed, but I came across a couple of issues. The first is I'm not sure of the syntax needed to apply middleware to the whole routing table. For example
:duct.module/ataraxy ^:test/testa {
[:get "/example"] [:example/fetch]
}
Does nothing - I had to use
:duct.module/ataraxy {
"" ^:test/testa
{
[:get "/example"] [:example/fetch]
}
}
instead.
Secondly, this might be due to my poor grasp of Clojure, but I don't know how to specify the order in which middleware run. For example, if I write
:duct.module/ataraxy {
"" ^:test/testa
{
[:get "/example"] ^:test/testb [:example/fetch]
}
}
then in runs testb first followed by testa, whereas I'd like it to be the other way around (also switching testa and testb in the above table has no effect). Similarly I'd like a way of specifying the order when writing something like
:duct.module/ataraxy {
[:get "/example"] ^:test/testa ^:test/testb [:example/fetch]
}
Thanks.
You can add middleware to all the routes just by adding it to the handler that Ataraxy produces. In Duct, you can do this by adding references to the :middleware option in :duct.core/handler:
:duct.core/handler
{:middleware [#ig/ref :your.middleware/key]}
In terms of the ordering of nested middleware in Ataraxy, that needs some work. Metadata is stored as a map, so it has no inherent order. To make it deterministic, Ataraxy sorts the keys alphanumerically. I have some ideas on how to solve this more effectively, but they're yet to be implemented.
You can also apply middleware directly to the handlers, outside of Ataraxy, thus maintaining an explicit order. This does mean the middleware isn't applied as data, but it could be a solution.
Just run into this issue too:
Here's a minimal case:
((ataraxy.core/handler {
:routes '{[:get "/hello"] ^:af [:hello]}
:middleware {:af ring.middleware.anti-forgery/wrap-anti-forgery}
:handlers {:hello (fn [req] [:atraxy.response/ok "hello"])}})
{:uri "/hello" :request-method :get})
Also whilst looking at this I ran into the reported issue reported by @benstevens48 of not being able to explicitly select an ordering of middlewares via the metadata syntax. It would be nice if there was a more explicit method that didn't rely on sorting the keys. Should I break this out in a separate issue?