ok, after some digging I manage to fix it with following middleware configuration. However it now returns octet-stream for all JSON responses, is there a way to fix it? I also would be grateful for some kind of explanation of what's going on here (I realize it's because wrap-content middleware but I don't get why Ring strip content-type header from static content if I omit it - so how can I have both?)
(def app
(api
(assoc api-config
:middleware
[[wrap-cors :access-control-allow-origin [#"\\*"] :access-control-allow-methods [:get :post :put :delete :patch]]
wrap-reload
[wrap-defaults (-> site-defaults
(assoc-in [:responses :not-modified-responses] true)
(assoc-in [:static :resources] resources-root)
(assoc-in [:security :anti-forgery] false)
(assoc-in [:security :frame-options] :deny)
(assoc-in [:security :ssl-redirect] (not (:dev env)))
(assoc-in [:security :hsts] (not (:dev env))))]])
@petr.mensik What are you using that expects middleware in that format? I would expect wrap-cors
, wrap-reload
, and wrap-defaults
all to be function calls threaded together around a handler...
The "simple" answer is that wrap-content-type
doesn't do what many people expect it to: it sets the content type based on the requested :uri
rather than anything intrinsic to the data you are returning.
You need wrap-json-response
on the inside, which will set Content-Type: application/json; charset=utf-8
if it gets a response containing :body
that satisfies coll?
If Content-Type
is already set before wrap-content-type
(in ring-defaults
) gets control, it'll just pass that through.
We tend to have
(-> handler
(wrap-json-response)
(wrap-defaults config)
(wrap-json-params)
(cors))
so that CORS goes first on requests, then json params, then defaults, then your handler (as wrap-json-response does nothing on the way in). When your handler responds, json response goes first, then defaults, then CORS (since wrap-json-params does nothing on the way out).Ring middleware is often very order-sensitive.
It looks like you have two questions in there (based on the above code and your SO question):
1. why is my JSON response not getting application/json
content type? A: you don't have wrap-json-response
in your code above.
2. why is resource-response
producing application/octet-stream
? A: because it doesn't set a content type at all, and then wrap-content-type
tries to deduce it from the incoming URI (which has no "extension").
Hope that helps @petr.mensik?
@seancorfield I am using Compojure-API which wraps middleware inside it's app configuration. Your response is definitely helpful, thanks a lot. However Compojure-API uses Muuntaja for content negotiation (which replaces ring-json
I guess) and the content type is not recognized even if I use muuntaja.middleware/wrap-format
as a first middleware (which is suppose to handle application/json response)
@petr.mensik hi, I can take a look at that.
@ikitommi thanks a lot, this issue causes me a serious headache right now 😄
@petr.mensik so, what was the actual problem you had?
By default, Muuntaja returns Streams, you can slurp
the :body
to get the actual JSON. Or you can configure to use the streaming JSON, in which case you get a lazy muuntaja.protocols.StreamableResponse
.
1 All the endpoints returns something like (ok {:thing "another"})
2. Application returns index.html
on GET / which is a SPA downloading and linking other resources (this works well)
3. Routes are configured as in app
above.
So the problem here is that my responses are not explicitly converted to JSON and don't contain Content Type header like in your example? Is there a way how to automate that with Muuntaja?
oh, I see. missed the content-type. It seems that ring-defaults
does something odd here. If you run it outside of api
, it seems do do better.
does that look better?
My guess is that ring-defaults sets the content-type and Muuntaja is smart/dumb enough to respect the user defined content-type and thus, does not override it.
https://github.com/metosin/muuntaja/blob/master/src/clj/muuntaja/core.clj#L408
Yes, that's the result I want - but that's kinda the problem, if I say (assoc-in [:responses :content-type] false)
then the adding content-type for static resources stops working. However it adds application/json for the API
I would serve the static resources as a route in Compojure.
something like https://github.com/metosin/compojure-api/wiki/Serving-static-resources#embedded-routes
that should work.
thats what I have right now, have I? https://pastebin.com/rjgKZsFz
Oh, but you have also the static resources defined via ring-defaults?
Yes, I have to because resources-root differens between dev and production
I wouldn't mess with that otherwise
actually I used Noir before so I hoped it can work basically the same way with Compojure API https://github.com/noir-clojure/lib-noir/blob/master/src/noir/util/middleware.clj#L140
Not 100% sure, but I would guess that the ring-defaults mw is now serving your static resources and the compojure resource route in the end IS never called.
So what do you think is the best solution which should work?
so, my 17:04 snippet doesn't work?
in it, the muuntaja & ring-defaults are in different order: muuntaja sets the json etc & the defaults should guess the content-type from the resources.
I am gonna try it again, thx
btw, ring-defaults seems to add quite a lot of overhead to the request processing.