johnbakers
johnbakers

Reputation: 24750

Unable to parse out EDN in this server request

I think I'm doing things right but I can't get my EDN out of the :body input stream. The Ring and Compojure handler is this:

dependencies:

 [ring.middleware.params :as params]
 [ring.middleware.edn :as edn]; https://github.com/tailrecursion/ring-edn

Handler:

(defroutes ajax-example
  (PUT "/ajax-example" r
       (println r)
       {:status 200
        :headers {"Content-Type" "application/edn"}
        :body "yo"}))

I wrap it like:

  (def ajax-wrapped
   (-> ajax-example
      edn/wrap-edn-params
      params/wrap-params))

The println'd response correctly reveals that it is EDN and the content length is correct based on the simple test map I send in, but the map itself is nowhere to be found, it is forever trapped in the input stream of the :body... how to get at it?

Here is the response println:

{:ssl-client-cert nil, :remote-addr 0:0:0:0:0:0:0:1, :params {}, :route-params {}, :headers {origin http://localhost:6542, host localhost:6542, user-agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36, content-type application/edn, cookie _ga=GA1.1.1354518981.1429622648; content-length 20, referer http://localhost:6542/, connection keep-alive, accept /, accept-language en-US,en;q=0.8,sq;q=0.6, accept-encoding gzip, deflate, sdch, cache-control max-age=0}, :server-port 6542, :content-length 20, :form-params {}, :query-params {}, :content-type application/edn, :character-encoding nil, :uri /ajax-example, :server-name localhost, :query-string nil, :body #, :edn-params nil, :scheme :http, :request-method :put}

The :body is not pasting correctly above, it looks like this:

 [open corner bracket] HttpInput org.eclipse.jetty.server.HttpInput@5d969109 [end corner bracket]

The client-side code sent from the browser using cljs-ajax lib is:

(defn ajax-send
  []
  (let [push {:adder1 2 :add2 3}]
    (PUT "/ajax-example" 
         {:format :edn 
          :params push
          :handler ajax-result
          :error-handler error-handler})))

Here is the output of a test suggested by one of the answers:

hf.examples> ((-> ajax-example  params/wrap-params edn/wrap-edn-params) (-> (mock/request :put "/ajax-example")
                                                             (mock/content-type "application/edn")
                                                             (mock/body "{:hello \"there\"}"))) 
{:status 200,
 :headers {"Content-Type" "application/edn"},
 :body
 "{:remote-addr \"localhost\", :params {:hello \"there\"}, :route-params {}, :headers {\"content-length\" \"16\", \"content-type\" \"application/edn\", \"host\" \"localhost\"}, :server-port 80, :content-length 16, :form-params {}, :query-params {}, :content-type \"application/edn\", :uri \"/ajax-example\", :server-name \"localhost\", :query-string nil, :edn-params {:hello \"there\"}, :scheme :http, :request-method :put}"}
hf.examples> 

I also tried this:

(defroutes ajax-example
  (PUT "/ajax-example" r
       {:status 200
        :headers {"Content-Type" "application/edn"}
        :body (pr-str (dissoc r :body))}))

Curl result independent of the front end:

curl -X PUT -H "Content-Type: application/edn" -d '{:name :barnabas}' http://localhost:6542/ajax-example
{:ssl-client-cert nil, :remote-addr "0:0:0:0:0:0:0:1", :params {}, :route-params {}, :headers {"host" "localhost:6542", "content-length" "17", "content-type" "application/edn", "user-agent" "curl/7.37.1", "accept" "*/*"}, :server-port 6542, :content-length 17, :content-type "application/edn", :character-encoding nil, :uri "/ajax-example", :server-name "localhost", :query-string nil, :edn-params nil, :scheme :http, :request-method

The content-length of 17 matches the number of characters in the map passed via Curl. But the edn-params is nil! Where is the content?

Upvotes: 3

Views: 1398

Answers (2)

Symfrog
Symfrog

Reputation: 3418

EDIT: As an answer to the updated question, the wrap-edn-params function consumes the body of the request by fully reading the :body InputStream. Compojure routes passes the request to each parameter handler until a non nil value is returned. In this case, whichever handler is passed to routes as the first handler will consume :body and there will be no :body value for the 2nd handler to consume, resulting in a nil body value read by wrap-edn-params.

The request that is being passed to the ring handler probably does not have its content-type set to edn. The wrap-edn-params function will only parse the edn if the request content-type is set to edn.

In addition the parsed edn parameters will only be placed in the :params and :edn-params keys of the request map by the wrap-edn-params function, and therefore :body should not be used to access the parsed edn.

(require '[ring.mock.request :as mock])
(require '[ring.middleware.edn :as edn]) 

((-> ajax-example  params/wrap-params edn/wrap-edn-params) (-> (mock/request :put "/ajax-example")
                                                             (mock/content-type "application/edn")
                                                             (mock/body "{:hello \"there\"}"))) 

{:remote-addr "localhost",
 :params {:hello "there"},
 :route-params {},
 :headers
 {"content-length" "16",
  "content-type" "application/edn",
  "host" "localhost"},
 :server-port 80,
 :content-length 16,
 :form-params {},
 :query-params {},
 :content-type "application/edn",
 :uri "/ajax-example",
 :server-name "localhost",
 :query-string nil,
 :body #<ByteArrayInputStream java.io.ByteArrayInputStream@171788d8>,
 :edn-params {:hello "there"},
 :scheme :http,
 :request-method :put}

Upvotes: 3

johnbakers
johnbakers

Reputation: 24750

I have fixed this but I do not know why the problem exists. I had another independent route that also wrapped EDN middle ware. Here is the reproducible example:

(defroutes example-routes2
  (PUT "/test-edn" r
       (println r)
       {:status 200
        :headers {"Content-Type" "application/edn"}
        :body (pr-str (str "request is: " r))}))

(defroutes ajax-example
  (PUT "/ajax-example" r
       (println r)
       {:status 200
        :headers {"Content-Type" "application/edn"}
        :body (pr-str (str "request is: " r))}))

(def edn-routes 
  (-> example-routes2
      edn/wrap-edn-params))

(def ajax-wrapped
  (-> ajax-example
      edn/wrap-edn-params))

;;combining all routes on this page into a single handler
(def example-routes (routes example-routes1  ajax-wrapped edn-routes))

Note that these two routes are essentially identical and are being wrapped identically. The one that fails to parse EDN into edn-params is the one listed second in the example-routes def!

curl -X PUT -H "Content-Type: application/edn" -d '{:name :barnabasss}' http://localhost:6542/test-edn

returns:

"request is: {:ssl-client-cert nil, :remote-addr \"0:0:0:0:0:0:0:1\", :params {}, :route-params {}, :headers {\"host\" \"localhost:6542\", \"content-length\" \"19\", \"content-type\" \"application/edn\", \"user-agent\" \"curl/7.37.1\", \"accept\" \"/\"}, :server-port 6542, :content-length 19, :content-type \"application/edn\", :character-encoding nil, :uri \"/test-edn\", :server-name \"localhost\", :query-string nil, :body #, :edn-params nil, :scheme :http, :request-method :put}"

curl -X PUT -H "Content-Type: application/edn" -d '{:name :barnabasss}' http://localhost:6542/ajax-example

returns:

"request is: {:ssl-client-cert nil, :remote-addr \"0:0:0:0:0:0:0:1\", :params {:name :barnabasss}, :route-params {}, :headers {\"host\" \"localhost:6542\", \"content-length\" \"19\", \"content-type\" \"application/edn\", \"user-agent\" \"curl/7.37.1\", \"accept\" \"/\"}, :server-port 6542, :content-length 19, :content-type \"application/edn\", :character-encoding nil, :uri \"/ajax-example\", :server-name \"localhost\", :query-string nil, :body #, :edn-params {:name :barnabasss}, :scheme :http, :request-method :put}"

Switching their order in the example-routes switches which one works. Can anyone explain?

Upvotes: 0

Related Questions