Skip to content

Commit 1632c47

Browse files
committed
Add support for WebSocket subprotocols
1 parent 48c0388 commit 1632c47

File tree

5 files changed

+71
-9
lines changed

5 files changed

+71
-9
lines changed

SPEC-alpha.md

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -222,28 +222,44 @@ For example:
222222
## 3. Websockets
223223

224224
A HTTP request can be promoted into a websocket by means of an
225-
"upgrade" header.
225+
""upgrade" header.
226226

227227
In this situation, a Ring handler may choose to respond with a
228228
websocket response instead of a HTTP response.
229229

230230
### 3.1. Websocket Responses
231231

232-
A websocket response is a map that has the `:ring.websocket/listener`
233-
key, which maps to a websocket listener, described in section 3.2.
232+
A websocket response is a map that represents a WebSocket, and may be
233+
returned from a handler in place of a response map.
234234

235235
```clojure
236236
(fn [request]
237237
#:ring.websocket{:listener websocket-listener})
238238
```
239239

240-
A websocket response may be returned from a synchronous listener, or
241-
via the response callback of an asynchronous listener.
240+
It may also be used from an asynchronous handler.
242241

243242
```clojure
244243
(fn [request respond raise]
245244
(respond #:ring.websocket{:listener websocket-listener}))
246-
```
245+
246+
A websocket response contains the following keys. Any key not marked as
247+
**required** may be omitted.
248+
249+
| Key | Type | Required |
250+
| ------------------------ | ----------------------- | -------- |
251+
|`:ring.websocket/listener`|`ring.websocket/Listener`| Yes |
252+
|`:ring.websocket/protocol`|`String` | |
253+
254+
#### :ring.websocket/listener
255+
256+
An event listener that satisfies the `ring.websocket/Listener` protocol,
257+
as described in section 3.2.
258+
259+
#### :ring.websocket/protocol
260+
261+
An optional websocket subprotocol. Must be one of the values listed in
262+
the `Sec-Websocket-Protocol` header on the request.
247263

248264
### 3.2. Websocket Listeners
249265

ring-core/src/ring/websocket.clj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
(ns ring.websocket
22
"Protocols and utility functions for websocket support."
33
(:refer-clojure :exclude [send])
4+
(:require [clojure.string :as str])
45
(:import [java.nio ByteBuffer]))
56

67
(defprotocol Listener
@@ -134,3 +135,11 @@
134135
"Returns true if the response contains a websocket listener."
135136
[response]
136137
(contains? response ::listener))
138+
139+
(defn request-protocols
140+
"Returns a collection of websocket subprotocols from a request map."
141+
[request]
142+
(some-> (:headers request)
143+
(get "sec-websocket-protocol")
144+
(str/split #",")
145+
(as-> ps (map str/trim ps))))
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
(ns ring.test.websocket
2+
(:require [clojure.test :refer [deftest is testing]]
3+
[ring.websocket :as ws]))
4+
5+
(deftest test-request-protocols
6+
(is (empty? (ws/request-protocols {:headers {}})))
7+
(is (= ["mqtt"]
8+
(ws/request-protocols {:headers {"sec-websocket-protocol" "mqtt"}})))
9+
(is (= ["mqtt" "soap"]
10+
(ws/request-protocols
11+
{:headers {"sec-websocket-protocol" "mqtt, soap"}}))))

ring-jetty-adapter/src/ring/adapter/jetty.clj

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
(:require [ring.util.jakarta.servlet :as servlet]
66
[ring.websocket :as ws])
77
(:import [java.nio ByteBuffer]
8+
[java.util ArrayList]
89
[org.eclipse.jetty.server
910
Request
1011
Server
@@ -19,9 +20,12 @@
1920
[org.eclipse.jetty.util.thread ThreadPool QueuedThreadPool]
2021
[org.eclipse.jetty.util.ssl SslContextFactory$Server KeyStoreScanner]
2122
[org.eclipse.jetty.websocket.server
23+
JettyServerUpgradeRequest
24+
JettyServerUpgradeResponse
2225
JettyWebSocketServerContainer
2326
JettyWebSocketCreator]
2427
[org.eclipse.jetty.websocket.api
28+
ExtensionConfig
2529
Session
2630
WebSocketConnectionListener
2731
WebSocketListener
@@ -79,9 +83,13 @@
7983
(onWebSocketPong [_ payload]
8084
(ws/on-pong listener @socket payload)))))
8185

82-
(defn- websocket-creator [{listener ::ws/listener}]
86+
(defn- ^JettyWebSocketCreator websocket-creator
87+
[{:keys [::ws/listener ::ws/protocol]}]
8388
(reify JettyWebSocketCreator
84-
(createWebSocket [_ _ _]
89+
(createWebSocket [_ ^JettyServerUpgradeRequest _req
90+
^JettyServerUpgradeResponse resp]
91+
(when protocol
92+
(.setAcceptedSubProtocol resp protocol))
8593
(websocket-listener listener))))
8694

8795
(defn- upgrade-to-websocket [^HttpServletRequest request response response-map]

ring-jetty-adapter/test/ring/adapter/test/jetty.clj

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,7 @@
728728
(ws/close sock)
729729
(swap! log conj [:open? (ws/open? sock)]))
730730
(on-message [_ _ _])
731-
(on-pong [_ _ data])
731+
(on-pong [_ _ _])
732732
(on-error [_ _ _])
733733
(on-close [_ _ code reason]
734734
(swap! log conj [:close])))})]
@@ -738,6 +738,24 @@
738738
(is (= [[:open? true] [:open? false] [:close]]
739739
@log))))
740740

741+
(testing "subprotocols"
742+
(let [log (atom [])
743+
handler (constantly
744+
{::ws/protocol "mqtt"
745+
::ws/listener
746+
(reify ws/Listener
747+
(on-open [_ _])
748+
(on-message [_ _ _])
749+
(on-pong [_ _ _])
750+
(on-error [_ _ _])
751+
(on-close [_ _ _ _]))})]
752+
(with-server handler {:port test-port}
753+
(let [ws @(hato/websocket test-websocket-url
754+
{:subprotocols ["soap" "mqtt"]})]
755+
(is (= "mqtt" (.getSubprotocol ^java.net.http.WebSocket ws)))
756+
@(hato/close! ws)
757+
(Thread/sleep 100)))))
758+
741759
(testing "sending websocket messages asynchronously"
742760
(let [log (atom [])
743761
handler (constantly

0 commit comments

Comments
 (0)