Fork me on GitHub
#re-frame
<
2022-03-26
>
fadrian18:03:26

I'm using the :http-xhrio effect handler in an attempt to call a REST API:

(rf/reg-event-fx
 :translation-request
 (fn [{:keys [_db]} [_ rule]]
   (let [body (edn->json rule)]
     (println ":in translation-request - body" body)
     {:http-xhrio {:method          :post
                   :uri             ""
                   :headers         {"Content-Type" "application/json"
                                     "Accept" "*/*"}
                   :body            body
                   :timeout         9000
                   :format          (ajax/json-request-format)
                   :response-format (ajax/text-response-format)
                   :on-success      [:got-translation]
                   :on-failure      [:got-translation-error]}})))
My server wraps the handler in wrap-json-body and wrap-json-response middleware and should be converting the input stream for the body into a :body attribute with the body's json value inside. When I call the API call using Postman, everything is working fine. But when I make the call using the http-xhrio handler, the stream in the server is not converted and the API returns a null result. Does anyone see if I'm doing something wrong with the handling of the :translation-request event using the http-xhrio handler?

p-himik18:03:13

Don't see anything obviously wrong, however: • You shouldn't need :headers at all (`content-type` should be set by :format and accept should be set by :response-format, IIRC) • You can see what's actually being sent in the Network tab of your browser's DevTools, and then compare it to what Postman sends

fadrian19:03:54

I took out the :headers attribute (leaving this in the code):

(rf/reg-event-fx
 :translation-request
 (fn [{:keys [_db]} [_ rule]]
   (let [body (edn->json rule)]
     (println ":in translation-request - body" body)
     {:http-xhrio {:method          :post
                   :uri             ""
                   :body            body
                   :timeout         9000
                   :format          (ajax/json-request-format)
                   :response-format (ajax/text-response-format)
                   :on-success      [:got-translation]
                   :on-failure      [:got-translation-error]}})))
Postman sends a request with a Content-Type header of application/json, while the xhrio request is using a Content-Type header of application/x-www-form-urlencoded. I'm not sure why this is happening, as I tell the system that the type of the request is json in the :format attribute.

fadrian20:03:48

Based on a search of the documentation and code for cljs-ajax (which the http-xhrio handler is based on), I found the following:

:format - specifies the format for the body of the request (Transit, JSON, etc.). Also sets the appropriate Content-Type header. Defaults to :transit if not provided.
So, it can be a keyword, even though the code documentation says you can pass it the request-format object, as well. So I set my request to:
(rf/reg-event-fx
 :translation-request
 (fn [{:keys [_db]} [_ rule]]
   (let [body (edn->json rule)]
     (println ":in translation-request - body" body)
     {:http-xhrio {:method          :post
                   :uri             ""
                   :body            body
                   :timeout         9000
                   :format          :json
                   :response-format (ajax/text-response-format)
                   :on-success      [:got-translation]
                   :on-failure      [:got-translation-error]}})))
thinking that this might work. It didn't. The content type header seems to really be stuck on application/x-www-form-urlencoded, and I can't figure out how to produce a request having an application/json content type.

p-himik20:03:11

Ah, try replacing :body with :params and remove the edn->json call (unless it creates a CLJS object and not a string - then leave it as is).

p-himik20:03:15

From cljs-ajax documentation: > • :body the exact data to send with in the request. If specified, both :params and :request-format are ignored. Note that you can submit js/FormData and other "raw" javascript types through this. >

p-himik20:03:31

> • :params - the parameters that will be sent with the request, format dependent: :transit and :edn can send anything, :json, :text and :raw need to be given a map. GET will add params onto the query string, POST will put the params in the body >

fadrian14:03:47

I got it working... On the way there, I started getting CORS messages that were finally alleviated by adding ring.cors.middleware to my handlers in my server:

(def allowed-origins [#".*"])

(def allowed-methods [:get :post :put])

(def allowed-headers #{:accept :content-type})

(defn wrap [handler]
  (-> handler
      (wrap-json-body {:keywords? true})
      wrap-json-response
      (wrap-cors :access-control-allow-origin allowed-origins
                 :access-control-allow-methods allowed-methods
                 :access-control-allow-headers allowed-headers)))
I also had to modify the code in the client to add the application/json content type:
(rf/reg-event-fx
 :translation-request
 (fn [{:keys [_db]} [_ rule]]
   (let [body (edn->json rule)]
     (println ":in translation-request - body" body)
     {:http-xhrio {:method          :post
                   :uri             ""
                   :headers         {"Content-Type" "application/json"
                                     "Accept" "*/*"}
                   :body            body
                   :timeout         9000
                   :response-format (ajax/text-response-format)
                   :on-success      [:got-translation]
                   :on-failure      [:got-translation-error]}})))
As usual, thanks for your help.

👍 1