rasen58
rasen58

Reputation: 5081

Ring response unable to return JSON body on session POST

I'm using ring.util.response and for some reason when setting cookies, I'm unable to return a json body from this POST request.

Returning json (dictionary) in the line below results in the error below. Whereas returning a plaintext string response works fine.

Does anyone know what I'm doing wrong?

I'm able to return json payloads fine from other GET routes.

(ns tool.session
  (:require
    [tool.config :refer [env]]
    [clj-http.client :as client]
    [clojure.tools.cli :refer [parse-opts]]
    [clojure.tools.logging :as log]
    [clojure.string :as s]
    [cheshire.core :refer :all]
    [slingshot.slingshot :refer [try+, throw+]]
    [ring.util.codec]
    [ring.util.response :refer [response]]
    [mount.core :as mount])
  )

;; Functions for user web session related access
(defn set-session-user! [business-external-id data-preferences-uuid {session :session}]
  (-> (response (str "Business id set to: " business-external-id) )
  ;; (-> (response {} ) ;; PUTTING this line results in the error below. Whereas returning a plaintext string response works fine.
      (assoc :session (assoc session :business_external_id business-external-id))
      (assoc :session (assoc session :data_preferences_uuid data-preferences-uuid))
      (assoc :headers {"Content-Type" "text/plain"})))

The error I get is

2022-09-21 00:51:21,838 [XNIO-1 task-2] ERROR io.undertow.request - UT005071: Undertow request failed HttpServerExchange{ POST /login/session-post}
java.lang.UnsupportedOperationException: Body class not supported: class clojure.lang.PersistentArrayMap
        at ring.adapter.undertow.response$eval20252$fn__20253.invoke(response.clj:46)
        at ring.adapter.undertow.response$eval20207$fn__20208$G__20198__20215.invoke(response.clj:11)
        at ring.adapter.undertow.response$set_exchange_response.invokeStatic(response.clj:59)
        at ring.adapter.undertow.response$set_exchange_response.invoke(response.clj:52)
        at ring.adapter.undertow$handle_request.invokeStatic(undertow.clj:23)
        at ring.adapter.undertow$handle_request.invoke(undertow.clj:19)
        at ring.adapter.undertow$undertow_handler$fn$reify__20336.handleRequest(undertow.clj:41)
        at io.undertow.server.session.SessionAttachmentHandler.handleRequest(SessionAttachmentHandler.java:68)
        at io.undertow.server.Connectors.executeRootHandler(Connectors.java:387)
        at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:852)
        at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
        at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2019)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1558)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1449)
        at org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1280)
        at java.base/java.lang.Thread.run(Thread.java:833)

For more context, that code is called from my main route

(defn log-in-to-business [{:keys [body-params] :as req}]
  (def business-uuid (:business_uuid body-params))
  (def data-preferences-uuid (:data_preferences_uuid body-params))

  (session/set-session-user! business-uuid data-preferences-uuid req)
)

Upvotes: 1

Views: 289

Answers (1)

Martin Půda
Martin Půda

Reputation: 7568

I guess that :body of the request has to be a string, so you have to convert your hash-map representing JSON payload with some library (data.json or cheshire):

(cheshire.core/encode {})
=> "{}"

And then pass this result into ring.util.response/response.

Some notes about your code:

In set-session-user!, you change :session of response to (assoc session :business_external_id business-external-id).

Then you overwrite this value to (assoc session :data_preferences_uuid data-preferences-uuid), losing :business_external_id from the previous step.

I think you should use merge or something like that:

(defn set-session-user! [business-external-id data-preferences-uuid {session :session}]
  (-> (response (str "Business id set to: " business-external-id))
      (assoc :session (merge session {:business_external_id  business-external-id
                                      :data_preferences_uuid data-preferences-uuid}))
      (assoc :headers {"Content-Type" "text/plain"})))

And in log-in-to-business, unless you really need to create global variables, you should use let instead (which creates local variables):

(defn log-in-to-business [{:keys [body-params] :as req}]
  (let [business-uuid (:business_uuid body-params)
        data-preferences-uuid (:data_preferences_uuid body-params)]
    (session/set-session-user! business-uuid data-preferences-uuid req)))

Upvotes: 2

Related Questions