diff --git a/README.md b/README.md index 3b8cf72..f9d248a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,32 @@ # com.s-exp/nima -Helidon/Nima ring adapter, loom based +Helidon/Nima [RING](https://github.com/ring-clojure/ring/blob/master/SPEC) adapter for clojure, loom based + +It's early days to expect breakage. But it mostly works already, just remember +Nima is alpha2 status right now, so do not use this in prod please. + + +```clojure +(require 's-exp.nima) + +(def server + (nima/start! {:port 8080 + :handler + (fn [{:as request :keys [body headers ...]}] + {:status 200 + :body "Hello world" + :headers {"Something" "Interesting"}})})) +;; ... + +(nima/stop! server) + +``` ## Installation -`{:deps {com.s-exp/nima {:git/sha "..." :git/url "https://github.com/mpenet/nima"}}}` +```clojure +{:deps {com.s-exp/nima {:git/sha "..." :git/url "https://github.com/mpenet/nima"}}} +``` ## License diff --git a/deps.edn b/deps.edn index 62db709..67d5aad 100644 --- a/deps.edn +++ b/deps.edn @@ -9,5 +9,6 @@ :extra-paths ["test"] :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"} com.exoscale/eftest {:mvn/version "1.0.0"}} - :exec-fn nima.test-runner/-main - :main-opts ["-m" "blabla"]}}} + ;; :exec-fn nima.test-runner/-main + ;; :main-opts ["-m" "blabla"] + }}} diff --git a/resources/headers.txt b/resources/headers.txt new file mode 100644 index 0000000..a2a4d7a --- /dev/null +++ b/resources/headers.txt @@ -0,0 +1,214 @@ +A-IM +Accept +Accept-Additions +Accept-CH +Accept-Charset +Accept-Datetime +Accept-Encoding +Accept-Features +Accept-Language +Accept-Patch +Accept-Post +Accept-Ranges +Access-Control +Access-Control-Allow-Credentials +Access-Control-Allow-Headers +Access-Control-Allow-Methods +Access-Control-Allow-Origin +Access-Control-Expose-Headers +Access-Control-Max-Age +Access-Control-Request-Headers +Access-Control-Request-Method +Age +Allow +ALPN +Alt-Svc +Alt-Used +Alternates +AMP-Cache-Transform +Apply-To-Redirect-Ref +Authentication-Control +Authentication-Info +Authorization +C-Ext +C-Man +C-Opt +C-PEP +C-PEP-Info +Cache-Control +Cache-Status +Cal-Managed-ID +CalDAV-Timezones +Capsule-Protocol +CDN-Cache-Control +CDN-Loop +Cert-Not-After +Cert-Not-Before +Clear-Site-Data +Close +Configuration-Context +Connection +Content-Base +Content-Disposition +Content-Encoding +Content-ID +Content-Language +Content-Length +Content-Location +Content-MD5 +Content-Range +Content-Script-Type +Content-Security-Policy +Content-Security-Policy-Report-Only +Content-Style-Type +Content-Type +Content-Version +Cookie +Cookie2 +Cross-Origin-Embedder-Policy +Cross-Origin-Embedder-Policy-Report-Only +Cross-Origin-Opener-Policy +Cross-Origin-Opener-Policy-Report-Only +Cross-Origin-Resource-Policy +DASL +Date +DAV +Default-Style +Delta-Base +Depth +Derived-From +Destination +Differential-ID +Digest +Early-Data +EDIINT-Features +ETag +Expect +Expect-CT +Expires +Ext +Forwarded +From +GetProfile +Hobareg +Host +HTTP2-Settings +If +If-Match +If-Modified-Since +If-None-Match +If-Range +If-Schedule-Tag-Match +If-Unmodified-Since +IM +Include-Referred-Token-Binding-ID +Isolation +Keep-Alive +Label +Last-Event-ID +Last-Modified +Link +Location +Lock-Token +Man +Max-Forwards +Memento-Datetime +Meter +Method-Check +Method-Check-Expires +MIME-Version +Negotiate +OData-EntityId +OData-Isolation +OData-MaxVersion +OData-Version +Opt +Optional-WWW-Authenticate +Ordering-Type +Origin +Origin-Agent-Cluster +OSCORE +OSLC-Core-Version +Overwrite +P3P +PEP +Pep-Info +PICS-Label +Ping-From +Ping-To +Position +Pragma +Prefer +Preference-Applied +Priority +ProfileObject +Protocol +Protocol-Info +Protocol-Query +Protocol-Request +Proxy-Authenticate +Proxy-Authentication-Info +Proxy-Authorization +Proxy-Features +Proxy-Instruction +Proxy-Status +Public +Public-Key-Pins +Public-Key-Pins-Report-Only +Range +Redirect-Ref +Referer +Referer-Root +Refresh +Repeatability-Client-ID +Repeatability-First-Sent +Repeatability-Request-ID +Repeatability-Result +Replay-Nonce +Retry-After +Safe +Schedule-Reply +Schedule-Tag +Sec-GPC +Sec-Token-Binding +Sec-WebSocket-Accept +Sec-WebSocket-Extensions +Sec-WebSocket-Key +Sec-WebSocket-Protocol +Sec-WebSocket-Version +Security-Scheme +Server +Server-Timing +Set-Cookie +Set-Cookie2 +SetProfile +SLUG +SoapAction +Status-URI +Strict-Transport-Security +Sunset +Surrogate-Capability +Surrogate-Control +TCN +TE +Timeout +Timing-Allow-Origin +Topic +Traceparent +Tracestate +Trailer +Transfer-Encoding +TTL +Upgrade +Urgency +URI +User-Agent +Variant-Vary +Vary +Via +Want-Digest +Warning +WWW-Authenticate +X-Content-Type-Options +X-Frame-Options +* \ No newline at end of file diff --git a/src/s_exp/nima.clj b/src/s_exp/nima.clj index 92e7de9..e724a8b 100644 --- a/src/s_exp/nima.clj +++ b/src/s_exp/nima.clj @@ -1,35 +1,52 @@ (ns s-exp.nima - (:require [clojure.string :as str]) + (:require [clojure.java.io :as io] + [clojure.string :as str]) (:import (io.helidon.common.http Http$HeaderValue Http$Status) - (io.helidon.nima.webserver WebServer WebServer$Builder) - (io.helidon.nima.webserver.http HttpRouting$Builder + (io.helidon.nima.webserver ListenerConfiguration$Builder + WebServer + WebServer$Builder) + (io.helidon.nima.webserver.http Handler Handler - Handler - ServerResponse - ServerRequest))) + HttpRouting$Builder + ServerRequest + ServerResponse))) (set! *warn-on-reflection* true) -(def default-server-options {:port 8080}) +(def default-server-options + {:port 8080 + :default-socket {:write-queue-length 1 + :backlog 1024 + :max-payload-size -1}}) (defn set-server-response-headers! [^ServerResponse server-response headers] (run! (fn [[k v]] (.header server-response (name k) - ^"[Ljava.lang.String;" - ;; slow, into-array calls into `seq` - (into-array String - (if (coll? v) - (map str v) - [(str v)])))) + (if (coll? v) + ^"[Ljava.lang.String;" (into-array String (eduction (map str) v)) + (doto ^"[Ljava.lang.String;" (make-array String 1) + (aset 0 (cond-> v (not (string? v)) str)))))) headers)) +(declare header-key->ring-header-key) +(eval + `(defn ~'header-key->ring-header-key + [k#] + (case k# + ~@(mapcat (juxt identity str/lower-case) + (->> "headers.txt" + io/resource + io/reader + line-seq)) + (str/lower-case k#)))) + (defn server-request->ring-headers [^ServerRequest server-request] (-> (reduce (fn [m ^Http$HeaderValue h] (assoc! m - (keyword (str/lower-case (.name h))) + (header-key->ring-header-key (.name h)) (.values h))) (transient {}) (some-> server-request .headers)) @@ -42,7 +59,6 @@ (.send (:body ring-response)))) (defn server-request->ring-method [^ServerRequest server-request] - (let [method (-> server-request .prologue .method @@ -58,6 +74,10 @@ "PATCH" :patch (keyword (str/lower-case method))))) +(defn server-request->ring-protocol [^ServerRequest server-request] + (let [prologue (.prologue server-request)] + (str (.protocol prologue) "/" (.protocolVersion prologue)))) + (defn server-request->ring-request [^ServerRequest server-request ^ServerResponse server-response] (let [headers (server-request->ring-headers server-request) @@ -74,7 +94,7 @@ :scheme (case (.protocol prologue) "HTTP" :http "HTTPS" :https) - :protocol (str (.protocol prologue) "/" (.protocolVersion prologue)) + :protocol (server-request->ring-protocol server-request) ;; :ssl-client-cert (some-> request .remotePeer .tlsCertificates) :request-method (server-request->ring-method server-request) :headers headers @@ -105,18 +125,34 @@ [^WebServer$Builder builder _ port _] (.port builder (int port))) +(defmethod set-server-option! :default-socket + [^WebServer$Builder builder _ + {:as cfg :keys [write-queue-length backlog max-payload-size receive-buffer-size]} _] + (doto builder + (.defaultSocket + (reify java.util.function.Consumer + (accept [_ listener-configuration-builder] + (let [listener (doto ^ListenerConfiguration$Builder listener-configuration-builder + (.writeQueueLength (int write-queue-length)) + (.backlog (int backlog)) + (.maxPayloadSize (long max-payload-size)))] + (when receive-buffer-size + (.receiveBufferSize listener receive-buffer-size)))))))) + (defmethod set-server-option! :handler [^WebServer$Builder builder _ handler opts] (set-ring1-handler! builder handler opts)) -(defn server-builder [options] +(defn server-builder + ^WebServer$Builder + [options] (reduce (fn [builder [k v]] (set-server-option! builder k v options)) (WebServer/builder) options)) -(defn make-server [opts] - (let [opts (merge default-server-options opts)] +(defn start! [opts] + (let [opts (merge-with merge default-server-options opts)] (-> (server-builder opts) (.start)))) @@ -124,9 +160,12 @@ (.stop server)) (comment - (def s (make-server - {:handler - (fn [req] - {:status 200 :body (str (System/currentTimeMillis)) :headers {"stuff" "lolo"}})})) + (def r {:status 200 :body "" :headers {:foo [1 2] :bar "bay"}}) + (def s (start! + {:default-socket + {:write-queue-length 100 + :backlog 3000} + :handler (fn [req] r)})) (stop! s)) +