diff --git a/deps.edn b/deps.edn index 163693a..bf2f937 100644 --- a/deps.edn +++ b/deps.edn @@ -10,6 +10,7 @@ com.github.strojure/zmap {:mvn/version "1.3.26"} io.helidon.http/helidon-http {:mvn/version "4.0.0-RC1"} io.helidon.webserver/helidon-webserver {:mvn/version "4.0.0-RC1"} + io.helidon.webserver/helidon-webserver-websocket {:mvn/version "4.0.0-RC1"} io.helidon.webserver/helidon-webserver-http2 {:mvn/version "4.0.0-RC1"}} :aliases @@ -17,8 +18,10 @@ :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"} less-awful-ssl/less-awful-ssl {:mvn/version "1.0.6"} eftest/eftest {:mvn/version "0.6.0"} - clj-http/clj-http {:mvn/version "3.12.0"} - ring/ring-core {:mvn/version "1.10.0"}}} + ring/ring-core {:mvn/version "1.10.0"} + stylefruits/gniazdo {:mvn/version "1.2.2"} + clj-http/clj-http {:mvn/version "3.12.0"}}} + :project {:extra-deps {io.github.exoscale/tools.project {:git/sha "4ad527fac7bdc24f649404205ca31754339f856d"}} :ns-default exoscale.tools.project diff --git a/src/s_exp/mina.clj b/src/s_exp/mina.clj index 1053ab1..4c78e02 100644 --- a/src/s_exp/mina.clj +++ b/src/s_exp/mina.clj @@ -1,6 +1,8 @@ (ns s-exp.mina - (:require [s-exp.mina.handler] - [s-exp.mina.options :as options]) + (:require [s-exp.mina.http.routing] + [s-exp.mina.options :as options] + [s-exp.mina.websocket] + [s-exp.mina.websocket.routing]) (:import (io.helidon.webserver WebServer WebServerConfig WebServerConfig$Builder))) (set! *warn-on-reflection* true) @@ -20,6 +22,10 @@ `options` can contain: + * `:http-handler` - ring http handler function + + * `:websocket-endpoints` - websocket endpoints (map-of string-endpoint handler-fns-map), where handler if can be of `:message`, `:ping`, `:pong`, `:close`, `:error`, `:open`, `:http-upgrade`. + * `:host` - host of the default socket * `:port` - port the server listens to, default to 8080 @@ -27,8 +33,8 @@ * `:default-socket` - map-of :write-queue-length :backlog :max-payload-size :receive-buffer-size `:connection-options`(map-of `:socket-receive-buffer-size` `:socket-send-buffer-size` `:socket-reuse-address` `:socket-keep-alive` `:tcp-no-delay` `:read-timeout` `:connect-timeout`) * `:tls` - a `io.helidon.nima.common.tls.Tls` instance" - ([handler options] - (start! (assoc options :handler handler))) + ([http-handler options] + (start! (assoc options :http-handler http-handler))) ([options] (-> (server-builder (merge default-options options)) .build @@ -46,14 +52,56 @@ ;; ;; (prn :aasdf ((:headers _) "accept")) ;; ;; (prn (:headers _)) ;; r)) -;; (def s (start! -;; #'h -;; {:host "0.0.0.0" :port 8080 -;; :write-queue-length 10240 -;; :connection-options {:socket-send-buffer-size 1024}})) - ;; (stop! s) +;; (require 's-exp.mina.websocket) +;; (stop! s) +;; (do +;; (when (resolve 's) (eval `(stop! s))) +;; (def s (start! +;; {:host "0.0.0.0" :port 8080 +;; :websocket-endpoints {"/foo" +;; {;; :subprotocols ["chat"] +;; :error (fn [session e] +;; (prn :err e)) +;; :ping (fn [session data] +;; (prn :ping)) +;; :pong (fn [session data] +;; (prn :pong)) +;; :open (fn [session] +;; ;; (prn :open) +;; (prn :open) +;; (prn session) +;; ;; (s-exp.mina.websocket/send! session "open" true) +;; ;; (prn (.subProtocol session)) +;; ;; ;; (prn session) +;; ;; (prn :sent) +;; ;; (s-exp.mina.websocket/close! session 0 "asdf") +;; ) +;; :close (fn [session status data] +;; (prn :close status)) +;; ;; :http-upgrade +;; ;; (fn [p h] +;; ;; (prn :http-upgrade p h) +;; ;; (java.util.Optional/of h)) +;; :message (fn [session data last?] +;; (prn :message data last?) +;; (s-exp.mina.websocket/send! session data true))}} +;; :write-queue-length 10240 +;; :connection-options {:socket-send-buffer-size 1024}}))) -;; https://api.github.com/repos/mpenet/mina/commits/main?per_page=1 +;; (require '[gniazdo.core :as ws]) + +;; (when (resolve 'socket) (eval `(ws/close socket))) +;; (def socket +;; (ws/connect +;; "ws://localhost:8080/foo" +;; :on-receive #(prn 'received %) +;; ;; :headers {"foo" "bar"} +;; ;; :subprotocols ["chat, foo"] +;; )) +;; (ws/send-msg socket "hello") +;; (ws/close socket) + +;; https://api.github.com/repos/mpenet/mina/commits/main?per_page=1 diff --git a/src/s_exp/mina/request.clj b/src/s_exp/mina/http/header.clj similarity index 50% rename from src/s_exp/mina/request.clj rename to src/s_exp/mina/http/header.clj index 7a481e1..3166432 100644 --- a/src/s_exp/mina/request.clj +++ b/src/s_exp/mina/http/header.clj @@ -1,6 +1,4 @@ -(ns s-exp.mina.request - (:require [clojure.string :as str] - [strojure.zmap.core :as zmap]) +(ns s-exp.mina.http.header (:import (clojure.lang IEditableCollection IFn @@ -8,14 +6,13 @@ IPersistentMap MapEntry MapEquivalence - PersistentHashMap Util) (io.helidon.http Headers Header HeaderName - HeaderNames) - (io.helidon.webserver.http ServerRequest ServerResponse) + HeaderNames + HeaderValues) (java.util Map))) (defn header-name ^HeaderName @@ -39,6 +36,9 @@ (header-name k) not-found))) +(defn kv->header [^HeaderName header-name ^String v] + (HeaderValues/create header-name v)) + (defn ring-headers* [^Headers headers] (-> (reduce (fn [m ^Header h] @@ -52,34 +52,6 @@ (defprotocol RingHeaders (^clojure.lang.APersistentMap ring-headers [_])) -(defn ring-method - [^ServerRequest server-request] - (let [method (-> server-request - .prologue - .method - .text)] - ;; mess with the string as a last resort, try to match against static values - ;; first - (case method - "GET" :get - "POST" :post - "PUT" :put - "DELETE" :delete - "HEAD" :head - "OPTIONS" :options - "TRACE" :trace - "PATCH" :patch - (keyword (str/lower-case method))))) - -(defn ring-protocol - [^ServerRequest server-request] - (case (-> server-request - .prologue - .protocolVersion) - "1.0" "HTTP/1.0" - "1.1" "HTTP/1.1" - "2.0" "HTTP/2")) - ;; inspired by ring-undertow (deftype HeaderMapProxy [^Headers headers ^:volatile-mutable persistent-copy] @@ -145,10 +117,10 @@ (iterator [_] (->> headers - .iterator (eduction (map (fn [^Header header] (MapEntry. (.lowerCase (.headerName header)) - (.value header))))))) + (.value header))))) + .iterator)) IKVReduce (kvreduce [this f init] @@ -171,31 +143,4 @@ (toString [_] (.toString headers))) -(defn ring-request - [^ServerRequest server-request - ^ServerResponse server-response] - (let [qs (let [query (.rawValue (.query server-request))] - (when (not= "" query) query)) - body (let [content (.content server-request)] - (when-not (.consumed content) (.inputStream content))) - ring-request (-> (.asTransient PersistentHashMap/EMPTY) - ;; delayed - (.assoc :server-port (zmap/delay (.port (.localPeer server-request)))) - (.assoc :server-name (zmap/delay (.host (.localPeer server-request)))) - (.assoc :remote-addr (zmap/delay - (let [address ^java.net.InetSocketAddress (.address (.remotePeer server-request))] - (-> address .getAddress .getHostAddress)))) - (.assoc :ssl-client-cert (zmap/delay (some-> server-request .remotePeer .tlsCertificates (.orElse nil) first))) - ;; realized - (.assoc :uri (.rawPath (.path server-request))) - (.assoc :scheme (if (.isSecure server-request) :https :http)) - (.assoc :protocol (ring-protocol server-request)) - (.assoc :request-method (ring-method server-request)) - (.assoc :headers (->HeaderMapProxy (.headers server-request) nil)) - (.assoc ::server-request server-request) - (.assoc ::server-response server-response)) - ;; optional - ring-request (cond-> ring-request - qs (.assoc :query-string qs) - body (.assoc :body body))] - (zmap/wrap (.persistent ring-request)))) + diff --git a/src/s_exp/mina/http/request.clj b/src/s_exp/mina/http/request.clj new file mode 100644 index 0000000..fe20cbe --- /dev/null +++ b/src/s_exp/mina/http/request.clj @@ -0,0 +1,68 @@ +(ns s-exp.mina.http.request + (:require [clojure.string :as str] + [s-exp.mina.http.header :as h] + [strojure.zmap.core :as zmap]) + (:import (clojure.lang PersistentHashMap) + (io.helidon.common.uri UriQuery UriPath) + (io.helidon.http HttpPrologue Headers) + (io.helidon.webserver.http ServerRequest ServerResponse))) + +(defn ring-headers + [^Headers headers] + (h/->HeaderMapProxy headers nil)) + +(defn ring-method [^HttpPrologue prologue] + (let [method (-> prologue .method .text)] + (case method + "GET" :get + "POST" :post + "PUT" :put + "DELETE" :delete + "HEAD" :head + "OPTIONS" :options + "TRACE" :trace + "PATCH" :patch + (keyword (str/lower-case method))))) + +(defn ring-protocol + [^HttpPrologue prologue] + (case (.protocolVersion prologue) + "1.0" "HTTP/1.0" + "1.1" "HTTP/1.1" + "2.0" "HTTP/2")) + +(defn ring-query + [^UriQuery query] + (let [query (.rawValue query)] + (when (not= "" query) query))) + +(defn ring-path [^UriPath path] + (.rawPath path)) + +(defn ring-request + [^ServerRequest server-request + ^ServerResponse server-response] + (let [qs (ring-query (.query server-request)) + body (let [content (.content server-request)] + (when-not (.consumed content) (.inputStream content))) + ring-request (-> (.asTransient PersistentHashMap/EMPTY) + ;; delayed + (.assoc :server-port (zmap/delay (.port (.localPeer server-request)))) + (.assoc :server-name (zmap/delay (.host (.localPeer server-request)))) + (.assoc :remote-addr (zmap/delay + (let [address ^java.net.InetSocketAddress (.address (.remotePeer server-request))] + (-> address .getAddress .getHostAddress)))) + (.assoc :ssl-client-cert (zmap/delay (some-> server-request .remotePeer .tlsCertificates (.orElse nil) first))) + ;; realized + (.assoc :uri (ring-path (.path server-request))) + (.assoc :scheme (if (.isSecure server-request) :https :http)) + (.assoc :protocol (ring-protocol (.prologue server-request))) + (.assoc :request-method (ring-method (.prologue server-request))) + (.assoc :headers (ring-headers (.headers server-request))) + (.assoc ::server-request server-request) + (.assoc ::server-response server-response)) + ;; optional + ring-request (cond-> ring-request + qs (.assoc :query-string (ring-query (.query server-request))) + body (.assoc :body body))] + (zmap/wrap (.persistent ring-request)))) diff --git a/src/s_exp/mina/response.clj b/src/s_exp/mina/http/response.clj similarity index 92% rename from src/s_exp/mina/response.clj rename to src/s_exp/mina/http/response.clj index 6992fd0..6a38c12 100644 --- a/src/s_exp/mina/response.clj +++ b/src/s_exp/mina/http/response.clj @@ -1,13 +1,11 @@ -(ns s-exp.mina.response - (:import (io.helidon.http Header - Headers - HeaderNames +(ns s-exp.mina.http.response + (:import (io.helidon.http HeaderNames HeaderName Status) (io.helidon.webserver.http ServerResponse) (java.io FileInputStream InputStream OutputStream))) -(def ^:no-doc ring-core-loaded? +(def ring-core-available? (try (require 'ring.core.protocols) true @@ -43,7 +41,7 @@ (write-body! [o server-response] (.send ^ServerResponse server-response o))) -(when ring-core-loaded? +(when ring-core-available? (extend-protocol BodyWriter Object (write-body! [o server-response] diff --git a/src/s_exp/mina/handler.clj b/src/s_exp/mina/http/routing.clj similarity index 75% rename from src/s_exp/mina/handler.clj rename to src/s_exp/mina/http/routing.clj index 0b0253d..24e4074 100644 --- a/src/s_exp/mina/handler.clj +++ b/src/s_exp/mina/http/routing.clj @@ -1,7 +1,7 @@ -(ns s-exp.mina.handler - (:require [s-exp.mina.options :as options] - [s-exp.mina.request :as request] - [s-exp.mina.response :as response]) +(ns s-exp.mina.http.routing + (:require [s-exp.mina.http.request :as request] + [s-exp.mina.http.response :as response] + [s-exp.mina.options :as options]) (:import (io.helidon.webserver WebServerConfig$Builder) (io.helidon.webserver.http Handler HttpRouting))) @@ -15,7 +15,7 @@ (.build (doto (HttpRouting/builder) (.any - ^"[Lio.helidon.webserver.http.Handler;" + ^"[Lio.helidon.webserver.http.Handler;" (into-array Handler [(reify Handler (handle [_ server-request server-response] @@ -23,6 +23,6 @@ handler (response/set-response! server-response))))]))))))) -(defmethod options/set-server-option! :handler +(defmethod options/set-server-option! :http-handler [^WebServerConfig$Builder builder _ handler options] (set-ring1-handler! builder handler options)) diff --git a/src/s_exp/mina/websocket.clj b/src/s_exp/mina/websocket.clj new file mode 100644 index 0000000..b9bc14d --- /dev/null +++ b/src/s_exp/mina/websocket.clj @@ -0,0 +1,55 @@ +(ns s-exp.mina.websocket + (:import (io.helidon.common.buffers BufferData) + (io.helidon.websocket WsSession))) + +(set! *warn-on-reflection* true) + +(defprotocol ToBufferData + (buffer-data [x])) + +(extend-protocol ToBufferData + + (Class/forName "[B") + (buffer-data [ba] + (BufferData/create ^"[B" ba)) + + String + (buffer-data [s] + (BufferData/create s)) + + clojure.lang.Sequential + (buffer-data [s] + (BufferData/create ^java.util.List s)) + + BufferData + (buffer-data [bd] + bd) + + nil + (buffer-data [x] + (BufferData/empty))) + +(defn send! + [^WsSession ws-session msg last?] + (if (string? msg) + (.send ws-session ^String msg + (boolean last?)) + (.send ws-session + ^BufferData (buffer-data msg) + (boolean last?)))) + +(defn ping! + [^WsSession ws-session data] + (.ping ws-session (buffer-data data))) + +(defn pong! + [^WsSession ws-session data] + (.pong ws-session (buffer-data data))) + +(defn close! + [^WsSession ws-session code reason] + (.close ws-session (int code) (str reason))) + +(defn terminate! + [^WsSession ws-session] + (.terminate ws-session)) diff --git a/src/s_exp/mina/websocket/listener.clj b/src/s_exp/mina/websocket/listener.clj new file mode 100644 index 0000000..f06d296 --- /dev/null +++ b/src/s_exp/mina/websocket/listener.clj @@ -0,0 +1,118 @@ +(ns s-exp.mina.websocket.listener + (:require [s-exp.mina.http.header :as h] + [s-exp.mina.http.request :as r]) + (:import (io.helidon.common.buffers BufferData) + (io.helidon.http Headers HeaderNames HeaderName Header HeaderValues) + (io.helidon.http HttpPrologue) + (io.helidon.http WritableHeaders) + (io.helidon.webserver.websocket WsUpgrader) + (io.helidon.websocket WsListener WsSession WsUpgradeException) + (java.util Optional))) + +(set! *warn-on-reflection* true) + +;; Client must send Sec-WebSocket-Version and Sec-WebSocket-Key. +;; Server must confirm the protocol by returning Sec-WebSocket-Accept. +;; Client may send a list of application subprotocols via Sec-WebSocket-Protocol. +;; Server must select one of the advertised subprotocols and return it via +;; Sec-WebSocket-Protocol. If the server does not support any, then the +;; connection is aborted. +;; Client may send a list of protocol extensions in Sec-WebSocket-Extensions. +;; Server may confirm one or more selected extensions via +;; Sec-WebSocket-Extensions. If no extensions are provided, then the connection +;; proceeds without them. +;; Finally, once the preceding handshake is complete, and if the handshake is +;; successful, the connection can now be used as a two-way communication channel +;; for exchanging WebSocket messages. From here on, there is no other explicit +;; HTTP communication between the client and server, and the WebSocket protocol +;; takes over. +(defn- header-negotiate + [^HeaderName k allowed-values ^Headers headers response-headers] + (if (seq allowed-values) + (if-let [selected-value (reduce (fn [_ x] + (when (contains? allowed-values x) + (reduced x))) + nil + (.allValues (.get headers k) + true))] + (assoc response-headers (.defaultCase k) selected-value) + (throw (WsUpgradeException. (format "Failed negotiation for %s" + (.defaultCase k))))) + response-headers)) + +(defn negotiate-subprotocols! + [allowed-sub-protocols ^Headers headers response-headers] + (header-negotiate WsUpgrader/PROTOCOL + allowed-sub-protocols + ^Headers headers + response-headers)) + +(defn negotiate-extensions! + [allowed-extensions ^Headers headers response-headers] + (header-negotiate WsUpgrader/EXTENSIONS + allowed-extensions + ^Headers headers + response-headers)) + +(defn http-upgrade-default + [{:as ring-request + ::keys [^Headers headers + allowed-protocols + allowed-extensions]}] + (->> (:headers ring-request) + (negotiate-subprotocols! allowed-protocols headers) + (negotiate-extensions! allowed-extensions headers))) + +(defn headers-response [headers-map] + (let [wh (WritableHeaders/create)] + (run! (fn [[k v]] + (.set wh + (if (sequential? v) + (HeaderValues/create (name k) + ^java.util.Collection v) + (HeaderValues/create (name k) + (str v))))) + headers-map) + (Optional/of wh))) + +(defn make-listener + ^WsListener [{:as _listener + :keys [message ping pong close error open http-upgrade + subprotocols extensions] + :or {message (constantly nil) + ping (constantly nil) + pong (constantly nil) + close (constantly nil) + error (constantly nil) + open (constantly nil)}}] + (let [subprotocols (-> subprotocols not-empty set) + extensions (-> extensions not-empty set)] + (reify WsListener + (^void onMessage [_ ^WsSession session ^String data ^boolean last?] + (message session data last?)) + (^void onMessage [_ ^WsSession session ^BufferData data ^boolean last?] + (message session data last?)) + (^void onPing [_ ^WsSession session ^BufferData data] + (ping session data)) + (^void onPong [_ ^WsSession session ^BufferData data] + (pong session data)) + (^void onClose [_ ^WsSession session ^int status ^String reason] + (close session status reason)) + (^void onError [_ ^WsSession session ^Throwable e] + (error session e)) + (^void onOpen [_ ^WsSession session] + (open session)) + (^Optional onHttpUpgrade [_ ^HttpPrologue http-prologue ^Headers headers] + (let [ring-request {:method (r/ring-method http-prologue) + :protocol (r/ring-protocol http-prologue) + :headers (r/ring-headers headers) + ::allowed-subprotocols subprotocols + ::allowed-extensions extensions + ::http-prologue http-prologue + ::headers headers}] + (headers-response + (if http-upgrade + (http-upgrade ring-request) + (http-upgrade-default ring-request)))))))) + + diff --git a/src/s_exp/mina/websocket/routing.clj b/src/s_exp/mina/websocket/routing.clj new file mode 100644 index 0000000..79fcfd7 --- /dev/null +++ b/src/s_exp/mina/websocket/routing.clj @@ -0,0 +1,21 @@ +(ns s-exp.mina.websocket.routing + (:require [s-exp.mina.options :as options] + [s-exp.mina.websocket.listener :as l]) + (:import (io.helidon.webserver WebServerConfig$Builder) + (io.helidon.webserver.websocket WsRouting WsRouting$Builder))) + +(set! *warn-on-reflection* true) + +(defn set-websocket-endpoints! ^WebServerConfig$Builder + [^WebServerConfig$Builder builder endpoints _options] + (doto builder + (.addRouting + (.build ^WsRouting$Builder + (reduce (fn [^WsRouting$Builder builder [path listener]] + (.endpoint builder ^String path (l/make-listener listener))) + (WsRouting/builder) + endpoints))))) + +(defmethod options/set-server-option! :websocket-endpoints + [^WebServerConfig$Builder builder _ endpoints options] + (set-websocket-endpoints! builder endpoints options)) diff --git a/test/s_exp/mina_test.clj b/test/s_exp/mina_test.clj index 8e1a594..cd14b25 100644 --- a/test/s_exp/mina_test.clj +++ b/test/s_exp/mina_test.clj @@ -1,15 +1,15 @@ (ns s-exp.mina-test (:require [clj-http.client :as client] + [clojure.java.io :as io] [clojure.string :as str] - [clojure.test :refer :all] + [clojure.test :refer [deftest is]] [less.awful.ssl :as ls] - [s-exp.mina :as m] [ring.core.protocols :as p] - [clojure.java.io :as io]) + [s-exp.mina :as m]) (:import (io.helidon.common.tls Tls TlsClientAuth) (io.helidon.common.tls TlsConfig))) -(def ^:dynamic *endpoint*) +(def ^:dynamic *endpoint* nil) (defn status-ok? [response] (some-> response :status (= 200))) @@ -22,57 +22,57 @@ (finally (m/stop! server#)))))) (deftest test-send-headers - (with-server {:handler (fn [req] {:headers {:foo "bar"}})} + (with-server {:http-handler (fn [req] {:headers {:foo "bar"}})} (is (-> (client/get *endpoint*) :headers :foo (= "bar")))) - (with-server {:handler (fn [req] {:headers {:foo ["bar" "baz"]}})} + (with-server {:http-handler (fn [req] {:headers {:foo ["bar" "baz"]}})} (is (-> (client/get *endpoint*) :headers :foo (= ["bar" "baz"]))))) (deftest test-status - (with-server {:handler (fn [req] {:status 201})} + (with-server {:http-handler (fn [req] {:status 201})} (is (-> (client/get *endpoint*) :status (= 201))))) (deftest test-query-string - (with-server {:handler (fn [req] {:body (:query-string req)})} + (with-server {:http-handler (fn [req] {:body (:query-string req)})} (is (-> (client/get (str *endpoint* "?foo=bar")) :body (= "foo=bar")))) - (with-server {:handler (fn [req] {:body (:query-string req)})} + (with-server {:http-handler (fn [req] {:body (:query-string req)})} (is (-> (client/get (str *endpoint* "?")) :body (= "")))) - (with-server {:handler (fn [req] {:body (:query-string req)})} + (with-server {:http-handler (fn [req] {:body (:query-string req)})} (is (-> (client/get (str *endpoint* "")) :body (= ""))))) (deftest test-method - (with-server {:handler (fn [req] {:body (str (:request-method req))})} + (with-server {:http-handler (fn [req] {:body (str (:request-method req))})} (is (-> (client/post *endpoint*) :body (= ":post")))) - (with-server {:handler (fn [req] {:body (str (:request-method req))})} + (with-server {:http-handler (fn [req] {:body (str (:request-method req))})} (is (-> (client/put *endpoint*) :body (= ":put")))) - (with-server {:handler (fn [req] {:body (str (:request-method req))})} + (with-server {:http-handler (fn [req] {:body (str (:request-method req))})} (is (-> (client/delete *endpoint*) :body (= ":delete"))))) (deftest test-uri - (with-server {:handler (fn [req] {:body (:uri req)})} + (with-server {:http-handler (fn [req] {:body (:uri req)})} (is (-> (client/delete (str *endpoint* "foo/bar")) :body (= "/foo/bar"))))) (deftest test-scheme - (with-server {:handler (fn [req] {:body (str (:scheme req))})} + (with-server {:http-handler (fn [req] {:body (str (:scheme req))})} (is (-> (client/get *endpoint*) :body (= ":http"))))) (deftest test-body - (with-server {:handler (fn [req] {})} + (with-server {:http-handler (fn [req] {})} (is (-> (client/get *endpoint*) :body (= "")))) - (with-server {:handler (fn [req] {:body "yes"})} + (with-server {:http-handler (fn [req] {:body "yes"})} (is (-> (client/get *endpoint*) :body (= "yes")))) - (with-server {:handler (fn [req] {:body ["yes" "no"]})} + (with-server {:http-handler (fn [req] {:body ["yes" "no"]})} (is (-> (client/get *endpoint*) :body (= "yesno")))) - (with-server {:handler (fn [req] {:body (.getBytes "yes")})} + (with-server {:http-handler (fn [req] {:body (.getBytes "yes")})} (is (-> (client/get *endpoint*) :body (= "yes")))) - (with-server {:handler (fn [req] {:body (java.io.ByteArrayInputStream. (.getBytes "yes"))})} + (with-server {:http-handler (fn [req] {:body (java.io.ByteArrayInputStream. (.getBytes "yes"))})} (is (-> (client/get *endpoint*) :body (= "yes"))))) (defn tls [] @@ -86,7 +86,7 @@ (.build b))) (deftest test-ssl-context - (with-server {:handler (fn [req] {}) :tls (tls)} + (with-server {:http-handler (fn [req] {}) :tls (tls)} (let [endpoint (str/replace *endpoint* "http://" "https://")] (is (thrown? Exception (client/get endpoint))) (is (status-ok? (client/get endpoint @@ -97,17 +97,17 @@ :trust-store-pass "password"})))))) (deftest test-streamable-body - (with-server {:handler (fn [_req] - {:status 200 - :headers {"content-type" "text/event-stream" - "transfer-encoding" "chunked"} - :body (reify p/StreamableResponseBody - (write-body-to-stream [_ _ output-stream] - (with-open [w (io/writer output-stream)] - (doseq [n (range 1 6)] - (doto w - (.write (str "data: " n "\n\n")) - (.flush))))))})} + (with-server {:http-handler (fn [_req] + {:status 200 + :headers {"content-type" "text/event-stream" + "transfer-encoding" "chunked"} + :body (reify p/StreamableResponseBody + (write-body-to-stream [_ _ output-stream] + (with-open [w (io/writer output-stream)] + (doseq [n (range 1 6)] + (doto w + (.write (str "data: " n "\n\n")) + (.flush))))))})} (let [resp (client/get *endpoint*)] (is (status-ok? resp)) (is (= "data: 1\n\ndata: 2\n\ndata: 3\n\ndata: 4\n\ndata: 5\n\n"