4

I have the following simple server in Clojure using Compojure (which is some flavor of the ring pattern). Everything was working fine in development, and now that I'm in prod I can't get CORS to work for the life of me - I have a wrap-preflight function which seems to work fine, but I keep getting CORS errors in terminal and neither the post or get requests for my comment system work. I am totally lost and very frustrated, I've asked around and no one else seems to know.

Here is the main core.clj code - If anyone has any ideas please let me know. You can see the errors live at thedailyblech.com (not an advert, but maybe it will help debug).

Thank you!

(ns clojure-play.core
  (:use     org.httpkit.server
            [compojure.core :refer :all]
            [compojure.route :as route]
            [clojure.data.json :as json]
            [clojure.tools.logging :only [info]]
            [clojure-play.routes :as routes]
            [ring.middleware.json :only [wrap-json-body]]
            [ring.middleware.cors :refer [wrap-cors]])
  (:require [monger.core :as mg]
            [monger.collection :as mc]
            [clojure.edn :as edn]
            [clojure.java.io :as io]
            [compojure.handler :as handler])
  (:import [org.bson.types ObjectId]
           [com.mongodb DB WriteConcern])
  (:gen-class))
(println "in the beginning was the command line...")

(defonce channels (atom #{}))

(defn connect! [channel]
  (info "channel open")
  (swap! channels conj channel))

(defn notify-clients [msg]
  (doseq [channel @channels]
    (send! channel msg)))

(defn disconnect! [channel status]
  (info "channel closed:" status)
  (swap! channels #(remove #{channel} %)))


(defn ws-handler [request]
  (with-channel request channel
    (connect! channel)
    (on-close channel (partial disconnect! channel))
    (on-receive channel #(notify-clients %))))

(defn my-routes [db]
  (routes
   (GET "/foo" [] "Hello Foo")
   (GET "/bar" [] "Hello Bar")
   (GET "/json_example/:name" [] routes/json_example)
   (GET "/json_example" [] routes/json_example)
   (POST "/email" [] routes/post_email)
   (POST "/write_comment" [] (fn [req] (routes/write_comment req db)))
   (POST "/update_comment" [] (fn [req] (routes/update_comment req db)))
   (GET "/read_comments/:path" [path] (fn [req] (routes/read_comments req db path)))
   (GET "/read_comments/:path1/:path2" [path1 path2] (fn [req] (routes/read_comments req db (str path1 "/" path2))))
   (GET "/ws" [] ws-handler)))

(defn connectDB []
  (defonce connection
    (let
     [uri "mongodb://somemlabthingy"
      {:keys [conn db]} (mg/connect-via-uri uri)]
      {:conn conn
       :db db}))
  {:db (:db connection)
   :conn (:conn connection)})

(def cors-headers
  "Generic CORS headers"
  {"Access-Control-Allow-Origin"  "*"
   "Access-Control-Allow-Headers" "*"
   "Access-Control-Allow-Methods" "GET POST OPTIONS DELETE PUT"})

(defn preflight?
  "Returns true if the request is a preflight request"
  [request]
  (= (request :request-method) :options))

(defn -main
  "this is main"
  [& args]

  (println "hello there main")

  (def db (get (connectDB) :db))

  (println (read-string (slurp (io/resource "environment/config.edn"))))


  (defn wrap-preflight [handler]
    (fn [request]
      (do
        (println "inside wrap-preflight")
        (println "value of request")
        (println request)
        (println "value of handler")
        (println handler)
        (if (preflight? request)
          {:status 200
           :headers cors-headers
           :body "preflight complete"}
          (handler request)))))

  (run-server
   (wrap-preflight
    (wrap-cors
     (wrap-json-body
      (my-routes db)
      {:keywords? true :bigdecimals? true})
     :access-control-allow-origin [#"http://www.thedailyblech.com"]
     :access-control-allow-methods [:get :put :post :delete :options]
     :access-control-allow-headers ["Origin" "X-Requested-With"
                                    "Content-Type" "Accept"]))
   {:port 4000}))
Peter Weyand
  • 2,159
  • 9
  • 40
  • 72
  • Should that `"Access-Control-Allow-Methods" "GET POST OPTIONS DELETE PUT"` be `"Access-Control-Allow-Methods" "GET, POST, OPTIONS, DELETE, PUT"` instead? – J.J. Hakala Sep 20 '19 at 16:03

4 Answers4

5

The CORS middleware handles the preflight stuff automatically -- you do not need separate middleware for it, nor do you need to produce your own headers etc.

You have it wrapping the routes which is correct -- so CORS-checking happens first, then routing. You should remove your custom preflight middleware and it should work at that point.

We use wrap-cors at work and the only complication we hit was in allowing enough headers (some inserted by production infrastructure, like load balancers). We ended up with this:

                           :access-control-allow-headers #{"accept"
                                                           "accept-encoding"
                                                           "accept-language"
                                                           "authorization"
                                                           "content-type"
                                                           "origin"}

For what it's worth, here's what we have for methods:

                           :access-control-allow-methods [:delete :get
                                                          :patch :post :put]

(you do not need :options in there)

Sean Corfield
  • 6,297
  • 22
  • 31
  • Thanks man. It turned out to be an NGINX issue. Networking is a pile of toothpicks, I'm telling you. Appreciate it! – Peter Weyand Sep 20 '19 at 18:47
  • Ah, probably similar to a problem we initially had with CloudFront and which headers it would/would not pass along. – Sean Corfield Sep 21 '19 at 22:34
  • @SeanCorfield, I have a similar issue when using system.components, and the headers don't seem to be in the response. Can you please help? https://stackoverflow.com/questions/60536686/wrap-cors-middleware-not-working-with-system-components – zengod Mar 05 '20 at 00:49
4

After digging around for hours, I found this to be super helpful on an issues post on the ring-cors github, for which documentation was severely lacking.

Using the linked gist, I was able to get past the CORS issues:

; Copied from linked gist
(def cors-headers
  "Generic CORS headers"
  {"Access-Control-Allow-Origin"  "*"
   "Access-Control-Allow-Headers" "*"
   "Access-Control-Allow-Methods" "GET"})

(defn preflight?
  "Returns true if the request is a preflight request"
  [request]
  (= (request :request-method) :options))

(defn all-cors
  "Allow requests from all origins - also check preflight"
  [handler]
  (fn [request]
    (if (preflight? request)
      {:status 200
       :headers cors-headers
       :body "preflight complete"}
      (let [response (handler request)]
        (update-in response [:headers]
                   merge cors-headers )))))

; my -main
(defn -main
  "Main entry point"
  [& args]
  (let [port (Integer/parseInt (or (System/getenv "PORT") "8081"))]

  (server/run-server
    (all-cors
      (wrap-defaults #'app-routes site-defaults))
        {:port port})
    (println "Running on" port)))

This finally allowed me to see the headers properly set in Chrome dev tools and also got rid of the warning on my React front-end.

Access-Control-Allow-Headers: *
Access-Control-Allow-Methods: GET
Access-Control-Allow-Origin: *
pmac
  • 51
  • 4
1

Might be worth trying and adding an explicit

(OPTIONS "/*" req handle-preflight)

route to your Compojure routes - in my case that's what made it work.

lukaszkorecki
  • 1,743
  • 1
  • 15
  • 19
1

(RING-MIDDLEWARE-CORS/wrap-cors

:access-control-allow-credentials "true"

:access-control-allow-origin [#".*"]

:access-control-allow-headers #{"accept" "accept-encoding" "accept-language" "authorization" "content-type" "origin"}

:access-control-allow-methods [:get :put :post :delete :options])

Minal Chauhan
  • 6,025
  • 8
  • 21
  • 41
PlumpMath
  • 21
  • 3
  • I am a clojure and clojurescript developer. Help me with this thread. This setting is in use. @Minal Chauhan Why did you do -1? If there is no example of actual use, it is quite confusing so I put it up. – PlumpMath May 05 '21 at 01:44