Skip to content

Commit 371c452

Browse files
authored
Merge pull request #481 from ring-clojure/jetty11
Update the Jetty adapter to version 11.0 Fixes #439.
2 parents a7c1360 + 06b9f5e commit 371c452

File tree

9 files changed

+437
-22
lines changed

9 files changed

+437
-22
lines changed

.github/workflows/test.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
uses: actions/setup-java@v3
1212
with:
1313
distribution: 'zulu'
14-
java-version: '8'
14+
java-version: '11'
1515

1616
- name: Install clojure tools
1717
uses: DeLaGuardo/[email protected]
@@ -25,5 +25,9 @@ jobs:
2525
key: cljdeps-${{ hashFiles('project.clj', 'ring-*/project.clj') }}
2626
restore-keys: cljdeps-
2727

28+
- name: Install undeployed Jakarta servlet project locally
29+
run: lein install
30+
working-directory: ./ring-jakarta-servlet
31+
2832
- name: Run tests
2933
run: lein sub test-all

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#)))))

0 commit comments

Comments
 (0)