cljs-ajax icon indicating copy to clipboard operation
cljs-ajax copied to clipboard

DELETE without `:params` sends request body `null`

Open jonase opened this issue 10 years ago • 11 comments

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

jonase avatar Dec 06 '14 10:12 jonase

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.

JulianBirch avatar Dec 06 '14 18:12 JulianBirch

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.)

JulianBirch avatar Jan 11 '15 16:01 JulianBirch

I think I've come up with a way of handling this well, so I'm re-opening the issue.

JulianBirch avatar Aug 09 '15 07:08 JulianBirch

Take a look at https://github.com/JulianBirch/cljs-ajax/blob/interceptors/interceptors.md and see what you think.

JulianBirch avatar Aug 09 '15 20:08 JulianBirch

Hey, can you write this solution somewhere more accessible ? What i'm talking about is -> :format :raw for GAE

rzvavram avatar Feb 08 '16 13:02 rzvavram

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

ghost avatar Dec 21 '16 16:12 ghost

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.

p-himik avatar Jul 06 '19 13:07 p-himik

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.

JulianBirch avatar Jul 08 '19 17:07 JulianBirch

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))}))

metametadata avatar Oct 24 '19 00:10 metametadata

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}))

metametadata avatar Nov 25 '19 08:11 metametadata

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.

JulianBirch avatar Nov 27 '19 11:11 JulianBirch