clojureringhttp-kit

How to use http.kit server with drawbridge ring handler?


I have following server code:

(ns tweet-sentiment.server
(:require [clojure.java.io :as io]
            [tweet-sentiment.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel start-less]]
            [compojure.core :refer [GET POST defroutes]]
            [compojure.route :refer [resources]]
            [net.cgrand.enlive-html :refer [deftemplate]]
            [net.cgrand.reload :refer [auto-reload]]
            [ring.middleware.reload :as reload]
            [ring.middleware.defaults :refer [wrap-defaults api-defaults]]
            [tweet-sentiment.utils :refer [generate-response]]
            [environ.core :refer [env]]
            [org.httpkit.server :refer [run-server]]
            [ring.middleware.edn :refer [wrap-edn-params]]
            [tweet-sentiment.tweets :refer [tweets]]
            [tweet-sentiment.dandelion :refer [dandelion-sentiment]]
            [clojure.core.async :refer [>!! <!! put! take! pipe chan]]
            [cemerick.drawbridge :as drawbridge]
            [ring.middleware.basic-authentication :refer [wrap-basic-authentication]]
            )
  (:gen-class))

(defonce server (atom nil))

(deftemplate page (io/resource "index.html") []
             [:body] (if is-dev? inject-devmode-html identity))

(defroutes routes
           (resources "/")
           (resources "/react" {:root "react"})
           (GET "/*" req (page)))


(defn wrap-drawbridge [handler]
  (fn [req]
    (if (= "/repl" (:uri req))
      (drawbridge/ring-handler req)
      (handler req))))

(def http-handler
  (if is-dev?
    (-> #'routes
        (wrap-defaults api-defaults)
        reload/wrap-reload
        wrap-edn-params
        wrap-drawbridge)

    (-> (wrap-defaults #'routes api-defaults)
        wrap-edn-params
        wrap-drawbridge)))

(defn run-web-server [& [port]]
  (let [port (Integer. (or port (env :port) 10555))]
    (println (format "Starting web server on port %d." port))
    (reset! server (run-server http-handler {:port port :join? false}))))

(defn run-auto-reload [& [port]]
  (auto-reload *ns*)
  (start-figwheel)
  (start-less))

(defn run [& [port]]
  (when is-dev?
    (run-auto-reload))
  (run-web-server port))

(defn stop-server []
  (when-not (nil? @server)
    (@server :timeout 0)
    (reset! server nil)))

(defn restart-server []
  (stop-server)
  (run-web-server))

(defn -main [& [port]]
  (run port))

When I try to connect to drawbridge REPL as dev or production:

lein repl :connect http://localhost:10555/repl

I get following error:

ERROR - POST /repl
java.lang.IllegalArgumentException: No value supplied for key: {:remote-addr "127.0.0.1", :headers {"accept-encoding" "gzip, deflate", "connection" "close", "content-length" "48", "content-type" "application/x-www-form-urlencoded", "host" "localhost:10555", "user-agent" "Apache-HttpClient/4.3.3 (java 1.5)"}, :async-channel #<AsyncChannel /127.0.0.1:10555<->/127.0.0.1:60374>, :server-port 10555, :content-length 48, :websocket? false, :content-type "application/x-www-form-urlencoded", :character-encoding "utf8", :uri "/repl", :server-name "localhost", :query-string nil, :body #<BytesInputStream BytesInputStream[len=48]>, :scheme :http, :request-method :post}
    at clojure.lang.PersistentHashMap.create(PersistentHashMap.java:77)
    at cemerick.drawbridge$ring_handler.doInvoke(drawbridge.clj:49)
    at clojure.lang.RestFn.invoke(RestFn.java:408)
    at tweet_sentiment.server$wrap_drawbridge$fn__21884.invoke(server.clj:36)
    at org.httpkit.server.HttpHandler.run(RingHandler.java:91)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

Problem is obviously in drawbridge/ring-handler function. These are my dependencies:

[[org.clojure/clojure "1.6.0"]
                 [org.clojure/clojurescript "0.0-3058" :scope "provided"]
                 [ring "1.3.2"]
                 [ring/ring-defaults "0.1.4"]
                 [compojure "1.3.2"]
                 [enlive "1.1.6"]
                 [org.omcljs/om "0.8.8"]
                 [om-sync "0.1.1"]
                 [environ "1.0.0"]
                 [http-kit "2.1.19"]
                 [fogus/ring-edn "0.3.0"]
                 [prismatic/om-tools "0.3.11"]
                 [secretary "1.2.3"]
                 [sablono "0.3.4"]
                 [twitter-api "0.7.8"]
                 [racehub/om-bootstrap "0.5.3"]
                 [cheshire "5.2.0"]
                 [com.cemerick/drawbridge "0.0.7"]
                 [ring-basic-authentication "1.0.1"]]

Did anybody get working http.kit server with drawbrige? What am I missing?


Solution

  • I eventually figured it out, server file should look somewhat like this

    (ns tweet-sentiment.server
      (:require [clojure.java.io :as io]
                [tweet-sentiment.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel start-less]]
                [compojure.core :refer [GET POST defroutes]]
                [compojure.route :refer [resources]]
                [net.cgrand.enlive-html :refer [deftemplate]]
                [net.cgrand.reload :refer [auto-reload]]
                [ring.middleware.reload :as reload]
                [ring.middleware.defaults :refer [wrap-defaults api-defaults]]
                [tweet-sentiment.utils :refer [generate-response]]
                [environ.core :refer [env]]
                [org.httpkit.server :refer [run-server]]
                [ring.middleware.edn :refer [wrap-edn-params]]
                [clojure.core.async :refer [>!! <!! put! take! pipe chan]]
                [cemerick.drawbridge :as drawbridge]
                [ring.middleware.basic-authentication :refer [wrap-basic-authentication]]
                [ring.middleware.params :as params]
                [ring.middleware.keyword-params :as keyword-params]
                [ring.middleware.nested-params :as nested-params]
                [ring.middleware.session :as session]
                )
      (:gen-class))
    
    (defonce server (atom nil))
    
    (deftemplate page (io/resource "index.html") []
                 [:body] (if is-dev? inject-devmode-html identity))
    
    (defroutes routes
               (resources "/")
               (resources "/react" {:root "react"})
               (GET "/*" req (page)))
    
    (defn authenticated? [name pass]
      (= [name pass] [(System/getenv "AUTH_USER") (System/getenv "AUTH_PASS")]))
    
    (def drawbridge-handler
      (-> (drawbridge/ring-handler)
          (keyword-params/wrap-keyword-params)
          (nested-params/wrap-nested-params)
          (params/wrap-params)
          (session/wrap-session)))
    
    (defn http-handler [handler]
      (-> handler
          (wrap-defaults api-defaults)
          wrap-edn-params))
    
    (defn wrap-http [handler]
      (fn [req]
        (let [handler (if (= "/repl" (:uri req))
                        (wrap-basic-authentication drawbridge-handler authenticated?)
                        (if is-dev?
                          (-> handler
                              http-handler
                              reload/wrap-reload)
                          (-> handler
                              http-handler)))]
          (handler req))))
    
    (defn run-web-server [& [port]]
      (let [port (Integer. (or port (env :port) 10555))]
        (println (format "Starting web server on port %d." port))
        (reset! server
                (run-server (wrap-http #'routes) {:port port :join? false})
                )))
    
    (defn run-auto-reload [& [port]]
      (auto-reload *ns*)
      (start-figwheel)
      (start-less))
    
    (defn run [& [port]]
      (when is-dev?
        (run-auto-reload))
      (run-web-server port))
    
    (defn stop-server []
      (when-not (nil? @server)
        (@server :timeout 0)
        (reset! server nil)))
    
    (defn restart-server []
      (stop-server)
      (run-web-server))
    
    (defn -main [& [port]]
      (run port))