I keep getting an empty http request with cljs-http

Hello everyone,

I’m picked up coding as a hobby a few years ago, I can do stuff but a lot of CS/SE concepts are unfamiliar to me. And I think this is one of those cases where this lack of knowledge is slowing me down a bit.

I’m building my first web app using clojure/clojurescript for backend/frontend and I just can’t make it work. The backend looks like this:

(ns example.handler
  (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [clojure.data.json :as json]
            [clj-http.client :as client]
            [datomic.api :as d]
            [ring.util.response :as resp]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults]])
  (:use [hiccup.core]))


  ;; Route
  (defroutes app-routes
    (GET "/info" [] (json/write-str {:data (some-function x)})))

Everything is fine up to this point, when I go to localhost:3000/info I see on the browser something like {"data":10} which is the expected result

The issue is when I try to get this data from clojurescript, here’s the code:

(ns example-cljs.core
  (:require [reagent.core :as reagent :refer [atom]]
            [clojure.core.async :as async]
            [cljs-http.client :as http]))

(def example-req (atom nil))

(defn get-data []
  (async/go
    (let [response (async/<! (http/get "localhost:3000/info"))]
      (reset! example-req response))))

which is basically the example from cljs-http documentation, but what I get is this: {:status 0, :success true, :body "", :headers {}, :trace-redirects ["localhost:3000/info" "localhost:3000/info"], :error-code :no-error, :error-text ""}

I have no idea of what is going on and it’s driving me insane. Any help or hints in the right direction will be great.

Thanks.

I never used cljs-http, so this is a wild guess, but did you try with http:// prefixed?

Yeah, I tried and got the same result. Thanks tho

try https://github.com/axios/axios if you don’t trust cljs-http. it returns promises and won’t be hard to be used in cljs now.

Status 0? Have a look at https://stackoverflow.com/questions/872206/what-does-it-mean-when-an-http-request-returns-status-code-0/49573256

Also make sure to bring up the browser’s “Developer Tools” and watch the Console and the Network displays.

Could either be the server or the http request tool. I think the best option would be to try doing a native fetch request and see if that works.

(-> (js/fetch "http://localhost:3000/info")
    (.then #(.json %))
    (.then println))

If it returns the expected result then clearly cljs-http is doing something odd.
If it returns the same result it would appear the server is choking somewhere.

Even though prefixing “http://” didn’t appear to solve the problem, it’s a best practice to specify the protocol. The exception is “//localhost:3000” which will use the current protocol.

Lastly, is your clojurescript app being served over a separate port from your clojure server?

1 Like

Hello everyone, after some reseach I found this tutorial so I’m just gonna copy-paste what I found that solved the issue:

Add ring-cors dependency to hello-compojure in project.clj.

 :dependencies [[org.clojure/clojure "1.10.0"]
                 [org.clojure/tools.nrepl "0.2.13"]
                 [com.datomic/datomic-pro "0.9.6024"]
                 [org.clojure/data.json "0.2.6"]
                 [ring-cors "0.1.13"]
                 [compojure "1.6.1"]
                 [hiccup "1.0.5"]
                 [ring/ring-defaults "0.3.2"]]

Require it in handler.clj in the server.

(ns hello-compojure.handler
      (:require [compojure.core :refer :all]
                [compojure.route :as route]
                [datomic.api :as d]
                [clojure.data.json :as json]
                [ring.util.response :as resp]
                [ring.middleware.cors :refer [wrap-cors]]
                [ring.middleware.defaults :refer [wrap-defaults site-defaults]])

And finally replace app definition in handler.clj with this :

(def app
  (-> app-routes
      (wrap-cors :access-control-allow-origin [#".*"]
                 :access-control-allow-methods [:post :get]
                 :access-control-allow-credentials "true"
                 :access-control-allow-headers "Content-Type, Accept, Authorization, Authentication, If-Match, If-None-Match, If-Modified-Since, If-Unmodified-Since")
      (wrap-defaults (assoc-in site-defaults [:security :anti-forgery] false))))

Thanks everyone for your help, I’ll try what you guys suggested so I can get a better understanding of what is going on and the reasons behind it.

1 Like

Hum, I actually wondered if it could be SOP (same origin policy).

Basically, JavaScript running inside a modern web browser is not allowed by the browser to make AJAX requests to places other than the server that served the JavaScript itself.

If you run your JavaScript elsewhere, like in NodeJS for example, that restrictions isn’t there.

CORS is a mechanism to relax the same origin policy. So the server which is being called by the AJAX from the in-browser JavaScript can return along it’s response a header that tells the browser if it’s fine or not for the current JavaScript to read your response. That’s a kind of whitelisting, so it might say, ok everyone is allowed to read it, or it might say, only those origins are allowed to read it.

I always find it a bit confusing, so I hope I got it right.

Also, it helps to understand why it is in place. SOP isn’t a way to protect your server side data or APIs. What it is, is a way for users to be sure their browser will not allow their credentials from one website to be used from another.

So even if the browser makes an authenticated request to your server, you want to tell the browser, hey, if this request was not made to me from one of my own allowed origins, it should be blocked.

For example, say you login to Facebook, and your Facebook session is now stored in your browser. Now you visit bad.com, and I return some JavaScript code which will make a request to Facebook for getting your private photos by using your browser session (thus will be authorized). After receiving the private photos, I send them back to my server. Thus allowing me to steal your Facebook information.

That’s why SOP is in place.

CORS is how you can bypass the SOP restrictions.

2 Likes