Skip to content

Commit 145ca76

Browse files
committed
Add ring-jakarta-servlet project
As the javax.servlet package has been renamed to jakarta.servlet from 5.0 onward, the ring-servlet project has been duplicated to a new ring-jakarta-servlet project. This allows backward compatibility to be maintained for ring-servlet.
1 parent a7c1360 commit 145ca76

File tree

3 files changed

+397
-0
lines changed

3 files changed

+397
-0
lines changed

ring-jakarta-servlet/project.clj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
(defproject ring/ring-jakarta-servlet "1.10.0"
2+
:description "Ring Jakarta servlet utilities."
3+
:url "https://github.com/ring-clojure/ring"
4+
:scm {:dir ".."}
5+
:license {:name "The MIT License"
6+
:url "http://opensource.org/licenses/MIT"}
7+
:dependencies [[org.clojure/clojure "1.7.0"]
8+
[ring/ring-core "1.10.0"]]
9+
:aliases {"test-all" ["with-profile" "default:+1.8:+1.9:+1.10:+1.11" "test"]}
10+
:profiles
11+
{:provided {:dependencies [[jakarta.servlet/jakarta.servlet-api "5.0.0"]]}
12+
:dev {:dependencies [[jakarta.servlet/jakarta.servlet-api "5.0.0"]]}
13+
:1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]}
14+
:1.9 {:dependencies [[org.clojure/clojure "1.9.0"]]}
15+
:1.10 {:dependencies [[org.clojure/clojure "1.10.3"]]}
16+
:1.11 {:dependencies [[org.clojure/clojure "1.11.1"]]}})
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
(ns ring.util.jakarta.servlet
2+
"Compatibility functions for turning a ring handler into a Java servlet."
3+
(:require [clojure.string :as string]
4+
[ring.core.protocols :as protocols])
5+
(:import [java.util Locale]
6+
[jakarta.servlet AsyncContext]
7+
[jakarta.servlet.http
8+
HttpServlet
9+
HttpServletRequest
10+
HttpServletResponse]))
11+
12+
(defn- get-headers [^HttpServletRequest request]
13+
(reduce
14+
(fn [headers ^String name]
15+
(assoc headers
16+
(.toLowerCase name Locale/ENGLISH)
17+
(->> (.getHeaders request name)
18+
(enumeration-seq)
19+
(string/join ","))))
20+
{}
21+
(enumeration-seq (.getHeaderNames request))))
22+
23+
(defn- get-content-length [^HttpServletRequest request]
24+
(let [length (.getContentLength request)]
25+
(when (>= length 0) length)))
26+
27+
(defn- get-client-cert [^HttpServletRequest request]
28+
(first (.getAttribute request "jakarta.servlet.request.X509Certificate")))
29+
30+
(defn build-request-map
31+
"Create the request map from the HttpServletRequest object."
32+
[^HttpServletRequest request]
33+
{:server-port (.getServerPort request)
34+
:server-name (.getServerName request)
35+
:remote-addr (.getRemoteAddr request)
36+
:uri (.getRequestURI request)
37+
:query-string (.getQueryString request)
38+
:scheme (keyword (.getScheme request))
39+
:request-method (keyword (.toLowerCase (.getMethod request) Locale/ENGLISH))
40+
:protocol (.getProtocol request)
41+
:headers (get-headers request)
42+
:content-type (.getContentType request)
43+
:content-length (get-content-length request)
44+
:character-encoding (.getCharacterEncoding request)
45+
:ssl-client-cert (get-client-cert request)
46+
:body (.getInputStream request)})
47+
48+
(defn merge-servlet-keys
49+
"Associate servlet-specific keys with the request map for use with legacy
50+
systems."
51+
[request-map
52+
^HttpServlet servlet
53+
^HttpServletRequest request
54+
^HttpServletResponse response]
55+
(merge request-map
56+
{:servlet servlet
57+
:servlet-request request
58+
:servlet-response response
59+
:servlet-context (.getServletContext servlet)
60+
:servlet-context-path (.getContextPath request)}))
61+
62+
(defn- set-headers [^HttpServletResponse response, headers]
63+
(doseq [[key val-or-vals] headers]
64+
(if (string? val-or-vals)
65+
(.setHeader response key val-or-vals)
66+
(doseq [val val-or-vals]
67+
(.addHeader response key val))))
68+
; Some headers must be set through specific methods
69+
(when-let [content-type (get headers "Content-Type")]
70+
(.setContentType response content-type)))
71+
72+
(defn- make-output-stream
73+
[^HttpServletResponse response ^AsyncContext context]
74+
(let [os (.getOutputStream response)]
75+
(if (nil? context)
76+
os
77+
(proxy [java.io.FilterOutputStream] [os]
78+
(write
79+
([b] (.write os b))
80+
([b off len] (.write os b off len)))
81+
(close []
82+
(.close os)
83+
(.complete context))))))
84+
85+
(defn update-servlet-response
86+
"Update the HttpServletResponse using a response map. Takes an optional
87+
AsyncContext."
88+
([response response-map]
89+
(update-servlet-response response nil response-map))
90+
([^HttpServletResponse response context response-map]
91+
(let [{:keys [status headers body]} response-map]
92+
(when (nil? response)
93+
(throw (NullPointerException. "HttpServletResponse is nil")))
94+
(when (nil? response-map)
95+
(throw (NullPointerException. "Response map is nil")))
96+
(when status
97+
(.setStatus response status))
98+
(set-headers response headers)
99+
(let [output-stream (make-output-stream response context)]
100+
(protocols/write-body-to-stream body response-map output-stream)))))
101+
102+
(defn- make-blocking-service-method [handler]
103+
(fn [servlet request response]
104+
(-> request
105+
(build-request-map)
106+
(merge-servlet-keys servlet request response)
107+
(handler)
108+
(->> (update-servlet-response response)))))
109+
110+
(defn- make-async-service-method [handler]
111+
(fn [servlet ^HttpServletRequest request ^HttpServletResponse response]
112+
(let [^AsyncContext context (.startAsync request)]
113+
(handler
114+
(-> request
115+
(build-request-map)
116+
(merge-servlet-keys servlet request response))
117+
(fn [response-map]
118+
(update-servlet-response response context response-map))
119+
(fn [^Throwable exception]
120+
(.sendError response 500 (.getMessage exception))
121+
(.complete context))))))
122+
123+
(defn make-service-method
124+
"Turns a handler into a function that takes the same arguments and has the
125+
same return value as the service method in the HttpServlet class."
126+
([handler]
127+
(make-service-method handler {}))
128+
([handler options]
129+
(if (:async? options)
130+
(make-async-service-method handler)
131+
(make-blocking-service-method handler))))
132+
133+
(defn servlet
134+
"Create a servlet from a Ring handler."
135+
([handler]
136+
(servlet handler {}))
137+
([handler options]
138+
(let [service-method (make-service-method handler options)]
139+
(proxy [HttpServlet] []
140+
(service [request response]
141+
(service-method this request response))))))
142+
143+
(defmacro defservice
144+
"Defines a service method with an optional prefix suitable for being used by
145+
genclass to compile a HttpServlet class.
146+
147+
For example:
148+
149+
(defservice my-handler)
150+
(defservice \"my-prefix-\" my-handler)"
151+
([handler]
152+
`(defservice "-" ~handler))
153+
([prefix handler]
154+
(if (map? handler)
155+
`(defservice "-" ~prefix ~handler)
156+
`(defservice ~prefix ~handler {})))
157+
([prefix handler options]
158+
`(let [service-method# (make-service-method ~handler ~options)]
159+
(defn ~(symbol (str prefix "service"))
160+
[servlet# request# response#]
161+
(service-method# servlet# request# response#)))))
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
(ns ring.util.jakarta.test.servlet
2+
(:require [clojure.test :refer :all]
3+
[ring.util.jakarta.servlet :refer :all]
4+
[ring.core.protocols :as proto])
5+
(:import [java.util Locale]))
6+
7+
(defmacro ^:private with-locale [locale & body]
8+
`(let [old-locale# (Locale/getDefault)]
9+
(try (Locale/setDefault ~locale)
10+
(do ~@body)
11+
(finally (Locale/setDefault old-locale#)))))
12+
13+
(defn- enumeration [coll]
14+
(let [e (atom coll)]
15+
(proxy [java.util.Enumeration] []
16+
(hasMoreElements [] (not (empty? @e)))
17+
(nextElement [] (let [f (first @e)] (swap! e rest) f)))))
18+
19+
(defn- async-context [completed]
20+
(proxy [jakarta.servlet.AsyncContext] []
21+
(complete [] (reset! completed true))))
22+
23+
(defn- servlet-request [request]
24+
(let [attributes {"jakarta.servlet.request.X509Certificate"
25+
[(request :ssl-client-cert)]}]
26+
(proxy [jakarta.servlet.http.HttpServletRequest] []
27+
(getServerPort [] (request :server-port))
28+
(getServerName [] (request :server-name))
29+
(getRemoteAddr [] (request :remote-addr))
30+
(getRequestURI [] (request :uri))
31+
(getQueryString [] (request :query-string))
32+
(getContextPath [] (request :servlet-context-path))
33+
(getScheme [] (name (request :scheme)))
34+
(getMethod [] (-> request :request-method name .toUpperCase))
35+
(getProtocol [] (request :protocol))
36+
(getHeaderNames [] (enumeration (keys (request :headers))))
37+
(getHeaders [name] (enumeration (get-in request [:headers name])))
38+
(getContentType [] (request :content-type))
39+
(getContentLength [] (or (request :content-length) -1))
40+
(getCharacterEncoding [] (request :character-encoding))
41+
(getAttribute [k] (attributes k))
42+
(getInputStream [] (request :body))
43+
(startAsync [] (async-context (request :completed))))))
44+
45+
(defn- servlet-response [response]
46+
(let [output-stream (java.io.ByteArrayOutputStream.)]
47+
(swap! response assoc :body output-stream)
48+
(proxy [jakarta.servlet.http.HttpServletResponse] []
49+
(getOutputStream []
50+
(proxy [jakarta.servlet.ServletOutputStream] []
51+
(write
52+
([b] (.write output-stream b))
53+
([b off len] (.write output-stream b off len)))))
54+
(setStatus [status]
55+
(swap! response assoc :status status))
56+
(setHeader [name value]
57+
(swap! response assoc-in [:headers name] value))
58+
(setCharacterEncoding [value])
59+
(setContentType [value]
60+
(swap! response assoc :content-type value)))))
61+
62+
(defn- servlet-config []
63+
(proxy [jakarta.servlet.ServletConfig] []
64+
(getServletContext [] nil)))
65+
66+
(defn- run-servlet
67+
([handler request response]
68+
(run-servlet handler request response {}))
69+
([handler request response options]
70+
(doto (servlet handler options)
71+
(.init (servlet-config))
72+
(.service (servlet-request request)
73+
(servlet-response response)))))
74+
75+
(deftest make-service-method-test
76+
(let [handler (constantly {:status 201
77+
:headers {}})
78+
method (make-service-method handler)
79+
servlet (doto (proxy [jakarta.servlet.http.HttpServlet] [])
80+
(.init (servlet-config)))
81+
request {:server-port 8080
82+
:server-name "foobar"
83+
:remote-addr "127.0.0.1"
84+
:uri "/foo"
85+
:scheme :http
86+
:request-method :get
87+
:protocol "HTTP/1.1"
88+
:headers {}}
89+
response (atom {})]
90+
(method servlet (servlet-request request) (servlet-response response))
91+
(is (= (@response :status) 201))))
92+
93+
(deftest servlet-test
94+
(let [body (proxy [jakarta.servlet.ServletInputStream] [])
95+
cert (proxy [java.security.cert.X509Certificate] [])
96+
request {:server-port 8080
97+
:server-name "foobar"
98+
:remote-addr "127.0.0.1"
99+
:uri "/foo"
100+
:query-string "a=b"
101+
:scheme :http
102+
:request-method :get
103+
:protocol "HTTP/1.1"
104+
:headers {"X-Client" ["Foo", "Bar"]
105+
"X-Server" ["Baz"]
106+
"X-Capital-I" ["Qux"]}
107+
:content-type "text/plain"
108+
:content-length 10
109+
:character-encoding "UTF-8"
110+
:servlet-context-path "/foo"
111+
:ssl-client-cert cert
112+
:body body}
113+
response (atom {})]
114+
(letfn [(handler [r]
115+
(are [k v] (= (r k) v)
116+
:server-port 8080
117+
:server-name "foobar"
118+
:remote-addr "127.0.0.1"
119+
:uri "/foo"
120+
:query-string "a=b"
121+
:scheme :http
122+
:request-method :get
123+
:protocol "HTTP/1.1"
124+
:headers {"x-client" "Foo,Bar"
125+
"x-server" "Baz"
126+
"x-capital-i" "Qux"}
127+
:content-type "text/plain"
128+
:content-length 10
129+
:character-encoding "UTF-8"
130+
:servlet-context-path "/foo"
131+
:ssl-client-cert cert
132+
:body body)
133+
{:status 200, :headers {}})]
134+
(testing "request"
135+
(run-servlet handler request response))
136+
(testing "mapping request header names to lower case"
137+
(with-locale (Locale. "tr")
138+
(run-servlet handler request response))))
139+
(testing "response"
140+
(letfn [(handler [r]
141+
{:status 200
142+
:headers {"Content-Type" "text/plain"
143+
"X-Server" "Bar"}
144+
:body "Hello World"})]
145+
(run-servlet handler request response)
146+
(is (= (@response :status) 200))
147+
(is (= (@response :content-type) "text/plain"))
148+
(is (= (get-in @response [:headers "X-Server"]) "Bar"))
149+
(is (= (.toString (@response :body)) "Hello World"))))))
150+
151+
(deftest servlet-cps-test
152+
(let [handler (fn [_ respond _]
153+
(respond {:status 200
154+
:headers {"Content-Type" "text/plain"}
155+
:body "Hello World"}))
156+
request {:completed (atom false)
157+
:server-port 8080
158+
:server-name "foobar"
159+
:remote-addr "127.0.0.1"
160+
:uri "/foo"
161+
:scheme :http
162+
:request-method :get
163+
:protocol "HTTP/1.1"
164+
:headers {}
165+
:body nil}
166+
response (atom {})]
167+
(run-servlet handler request response {:async? true})
168+
(is (= @(:completed request) true))
169+
(is (= (@response :status) 200))
170+
(is (= (@response :content-type) "text/plain"))
171+
(is (= (.toString (@response :body)) "Hello World"))))
172+
173+
(defn- defservice-test* [service]
174+
(let [body (proxy [jakarta.servlet.ServletInputStream] [])
175+
servlet (doto (proxy [jakarta.servlet.http.HttpServlet] [])
176+
(.init (servlet-config)))
177+
request {:server-port 8080
178+
:server-name "foobar"
179+
:remote-addr "127.0.0.1"
180+
:uri "/foo"
181+
:query-string ""
182+
:scheme :http
183+
:request-method :get
184+
:headers {}
185+
:content-type "text/plain"
186+
:content-length 10
187+
:character-encoding "UTF-8"
188+
:body body}
189+
response (atom {})]
190+
(service servlet
191+
(servlet-request request)
192+
(servlet-response response))
193+
(is (= (@response :status) 200))
194+
(is (= (get-in @response [:headers "Content-Type" ]) "text/plain"))
195+
(is (= (.toString (@response :body)) "Hello World"))))
196+
197+
(defn- service-handler [_]
198+
{:status 200
199+
:headers {"Content-Type" "text/plain"}
200+
:body "Hello World"})
201+
202+
(defservice "foo-" service-handler)
203+
(defservice service-handler {})
204+
205+
(deftest defservice-test
206+
(defservice-test* foo-service)
207+
(defservice-test* -service))
208+
209+
(deftest make-output-stream-test
210+
(let [response (atom {})]
211+
(update-servlet-response
212+
(servlet-response response)
213+
(async-context (atom false))
214+
{:status 200
215+
:headers {}
216+
:body (reify proto/StreamableResponseBody
217+
(write-body-to-stream [_ _ os]
218+
(.write os (int \h))
219+
(.write os (.getBytes "ello"))))})
220+
(is (= "hello" (.toString (:body @response))))))

0 commit comments

Comments
 (0)