cljs-ajax
cljs-ajax copied to clipboard
DELETE without `:params` sends request body `null`
The following call
(DELETE "http://localhost:3000"
{:format (json-request-format)})
makes an http DELETE request with the request-body null
and Content-Length
4 even if no :params
are specified.
This is problematic at least on Google App Engine as the web server won't allow a body in DELETE
requests
I'm caught between a rock and a hard place on this one. On the other hand, you've got a genuine problem there, on the other the HTTP spec allows bodies in DELETE. Furthermore, null
is the correct JSON encoding for no value at all.
I think the easiest way to work around this is to use :format :raw
.
I'll try to think of a way to support this without breaking people who actually need DELETE with a body.
I did come up with a way of handling this, but the more I think about it it isn't actually superior to just saying "don't use json on DELETE
in google app engine". (The same holds true of TRACE
in all environments.)
I think I've come up with a way of handling this well, so I'm re-opening the issue.
Take a look at https://github.com/JulianBirch/cljs-ajax/blob/interceptors/interceptors.md and see what you think.
Hey, can you write this solution somewhere more accessible ? What i'm talking about is -> :format :raw for GAE
This is a long time gone, but I'm curious @JulianBirch if you could point me to a RFC (or something) where this is declared:
Furthermore, null is the correct JSON encoding for no value at all
Just stumbled upon this as well.
:format :raw
cannot be used since the keywords are not allowed as the format values in the current version. And there's an interesting comment in the source code: "There's no raw-request-format because it's handled by the DirectSubmission code".
But the issue is that DirectSubmission
works only if the :body
is not nil. The workaround is to specify :body ""
. That way, it's not nil, DirectSubmission
works, and there is still nothing to send since the body is empty.
I guess we could make DirectSubmission treat a nil as empty string. Need to make sure it didn't interfere with normal operation, though.
Incidentally, for future reference, the correct behaviour for nil
is specified in the MDN schema.
The example in https://github.com/JulianBirch/cljs-ajax/blob/master/docs/interceptors.md doesn't work in Clojure where body is an InputStream
:
(if (ajax.protocols/-body response)
(I'm not even sure it works in ClojureScript, as I had to compare body with ""
instead of nil
. But I use transit+json
format, not pure json
.)
I check Content-Length to make the interceptor work in both Clojure and ClojureScript:
[ajax.core :as ajax]
[ajax.protocols :as ajax-protocols]
...
(def -treat-empty-response-as-nil
(ajax/to-interceptor
{:name "JSON special case nil"
:response (fn -empty-means-nil
[response]
; The "Content-Length" response header is required.
; It's checked in order to avoid consuming the response body input stream in Clojure
; and works in both Clojure and CLJS.
(if (= (ajax-protocols/-get-response-header response "Content-Length") "0")
(reduced [(ajax/success? (ajax-protocols/-status response)) nil])
response))}))
My previous comment is incorrect because not all responses have Content-Length
. So instead of interceptor I settled on a custom parsing function:
(ns foo
(:require [ajax.interceptors :as ajax-interceptors]
[ajax.protocols :as ajax-protocols]
[cognitect.transit :as transit])
#?(:clj (:import (org.apache.http.nio.entity ContentInputStream))))
; HACK: an alternative to ajax.transit/transit-read-fn which does not choke on empty responses (e.g. on DELETE).
; https://github.com/JulianBirch/cljs-ajax/issues/65.
(defn -read-transit-json
[response]
(let [body (ajax-protocols/-body response)
opts {:handlers ....}]
#?(:clj
(if (zero? (.available ^ContentInputStream body))
nil
(transit/read (transit/reader body :json opts)))
:cljs
(if (empty? body)
nil
(transit/read (transit/reader :json opts) body)))))
; HACK: custom response format instead of ajax/transit-response-format
; which always asks server for transit+msgpack.
; https://github.com/JulianBirch/cljs-ajax/issues/240.
(def -response-format (ajax-interceptors/map->ResponseFormat
{:content-type ["application/transit+json"]
:description "Transit"
:read -read-transit-json}))
Hmm. I'm coming round to the view we should support this (via a flag) in the base library. And you've done the hard work of figuring out how to do it. I need to figure out how to integrate it best. Think the protocol probably needs a :body-empty
method that can then be used by json and transit to default to null.