Skip to content

Commit

Permalink
Move websocket protocols to separate library
Browse files Browse the repository at this point in the history
This allows adapters to support Ring websockets without requiring a
dependency on ring-core.
  • Loading branch information
weavejester committed Oct 12, 2023
1 parent 4a69cda commit 7cc8e95
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 61 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ jobs:
key: cljdeps-${{ hashFiles('project.clj', 'ring-*/project.clj') }}
restore-keys: cljdeps-

- name: Install websocket protocols project locally
run: lein install
working-directory: ./ring-websocket-protocols

- name: Install core project locally
run: lein install
working-directory: ./ring-core
Expand Down
1 change: 1 addition & 0 deletions ring-core/project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
:license {:name "The MIT License"
:url "http://opensource.org/licenses/MIT"}
:dependencies [[org.clojure/clojure "1.7.0"]
[org.ring-clojure/ring-websocket-protocols "1.11.0-alpha4"]
[ring/ring-codec "1.2.0"]
[commons-io "2.13.0"]
[org.apache.commons/commons-fileupload2-core "2.0.0-M1"]
Expand Down
74 changes: 13 additions & 61 deletions ring-core/src/ring/websocket.clj
Original file line number Diff line number Diff line change
@@ -1,37 +1,12 @@
(ns ring.websocket
"Protocols and utility functions for websocket support."
(:refer-clojure :exclude [send])
(:require [clojure.string :as str])
(:require [clojure.string :as str]
[ring.websocket.protocols :as p])
(:import [java.nio ByteBuffer]))

(defprotocol Listener
"A protocol for handling websocket events. The second argument is always an
object that satisfies the Socket protocol."
(on-open [listener socket]
"Called when the websocket is opened.")
(on-message [listener socket message]
"Called when a message is received. The message may be a String or a
ByteBuffer.")
(on-pong [listener socket data]
"Called when a pong is received in response to an earlier ping. The client
may provide additional binary data, represented by the data ByteBuffer.")
(on-error [listener socket throwable]
"Called when a Throwable error is thrown.")
(on-close [listener socket code reason]
"Called when the websocket is closed, along with an integer code and a
plaintext string reason for being closed."))

(defprotocol PingListener
"A protocol for handling ping websocket events. The second argument is always
always an object that satisfies the Socket protocol. This is separate from
the Listener protocol as some APIs (for example Jakarta) don't support
listening for ping events."
(on-ping [listener socket data]
"Called when a ping is received from the client. The client may provide
additional binary data, represented by the data ByteBuffer."))

(extend-type clojure.lang.IPersistentMap
Listener
p/Listener
(on-open [m socket]
(when-let [kv (find m :on-open)] ((val kv) socket)))
(on-message [m socket message]
Expand All @@ -42,33 +17,10 @@
(when-let [kv (find m :on-error)] ((val kv) socket throwable)))
(on-close [m socket code reason]
(when-let [kv (find m :on-close)] ((val kv) socket code reason)))
PingListener
p/PingListener
(on-ping [m socket data]
(when-let [kv (find m :on-ping)] ((val kv) socket data))))

(defprotocol Socket
"A protocol for sending data via websocket."
(-open? [socket]
"Returns true if the socket is open; false otherwise.")
(-send [socket message]
"Sends a String or ByteBuffer to the client via the websocket.")
(-ping [socket data]
"Sends a ping message to the client with a ByteBuffer of extra data.")
(-pong [socket data]
"Sends an unsolicited pong message to the client, with a ByteBuffer of extra
data.")
(-close [socket code reason]
"Closes the socket with an integer status code, and a String reason."))

(defprotocol AsyncSocket
"A protocol for sending data asynchronously via websocket. Intended for use
with the Socket protocol."
(-send-async [socket message succeed fail]
"Sends a String or ByteBuffer to the client via the websocket. If it
succeeds, the 'succeed' callback function is called with zero arguments. If
it fails, the 'fail' callback function is called with the exception that was
thrown."))

(defprotocol TextData
"A protocol for converting text data into a String."
(->char-sequence [data]
Expand Down Expand Up @@ -101,42 +53,42 @@
(defn open?
"Returns true if the Socket is open, false otherwise."
[socket]
(boolean (-open? socket)))
(boolean (p/-open? socket)))

(defn send
"Sends text or binary data via a websocket, either synchronously or
asynchronously with callback functions. A convenient wrapper for the -send and
-send-async protocol methods."
([socket message]
(-send socket (encode-message message)))
(p/-send socket (encode-message message)))
([socket message succeed fail]
(-send-async socket (encode-message message) succeed fail)))
(p/-send-async socket (encode-message message) succeed fail)))

(defn ping
"Sends a ping message via a websocket, with an optional byte array or
ByteBuffer that may contain custom session data. A convenient wrapper for the
-ping protocol method."
([socket]
(-ping socket (ByteBuffer/allocate 0)))
(p/-ping socket (ByteBuffer/allocate 0)))
([socket data]
(-ping socket (->byte-buffer data))))
(p/-ping socket (->byte-buffer data))))

(defn pong
"Sends an unsolicited pong message via a websocket, with an optional byte
array or ByteBuffer that may contain custom session data. A convenient wrapper
for the -pong protocol method."
([socket]
(-pong socket (ByteBuffer/allocate 0)))
(p/-pong socket (ByteBuffer/allocate 0)))
([socket data]
(-pong socket (->byte-buffer data))))
(p/-pong socket (->byte-buffer data))))

(defn close
"Closes the websocket, with an optional custom integer status code and reason
string."
([socket]
(-close socket 1000 "Normal Closure"))
(p/-close socket 1000 "Normal Closure"))
([socket code reason]
(-close socket code reason)))
(p/-close socket code reason)))

(defn upgrade-request?
"Returns true if the request map is a websocket upgrade request."
Expand Down
9 changes: 9 additions & 0 deletions ring-websocket-protocols/project.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
(defproject org.ring-clojure/ring-websocket-protocols "1.11.0-alpha4"
:description "Ring protocols for websockets."
:url "https://github.com/ring-clojure/ring"
:scm {:dir ".."}
:license {:name "The MIT License"
:url "http://opensource.org/licenses/MIT"}
:dependencies []
:profiles
{:dev {:dependencies [[org.clojure/clojure "1.7.0"]]}})
50 changes: 50 additions & 0 deletions ring-websocket-protocols/src/ring/websocket/protocols.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
(ns ring.websocket.protocols)

(defprotocol Listener
"A protocol for handling websocket events. The second argument is always an
object that satisfies the Socket protocol."
(on-open [listener socket]
"Called when the websocket is opened.")
(on-message [listener socket message]
"Called when a message is received. The message may be a String or a
ByteBuffer.")
(on-pong [listener socket data]
"Called when a pong is received in response to an earlier ping. The client
may provide additional binary data, represented by the data ByteBuffer.")
(on-error [listener socket throwable]
"Called when a Throwable error is thrown.")
(on-close [listener socket code reason]
"Called when the websocket is closed, along with an integer code and a
plaintext string reason for being closed."))

(defprotocol PingListener
"A protocol for handling ping websocket events. The second argument is always
always an object that satisfies the Socket protocol. This is separate from
the Listener protocol as some APIs (for example Jakarta) don't support
listening for ping events."
(on-ping [listener socket data]
"Called when a ping is received from the client. The client may provide
additional binary data, represented by the data ByteBuffer."))

(defprotocol Socket
"A protocol for sending data via websocket."
(-open? [socket]
"Returns true if the socket is open; false otherwise.")
(-send [socket message]
"Sends a String or ByteBuffer to the client via the websocket.")
(-ping [socket data]
"Sends a ping message to the client with a ByteBuffer of extra data.")
(-pong [socket data]
"Sends an unsolicited pong message to the client, with a ByteBuffer of extra
data.")
(-close [socket code reason]
"Closes the socket with an integer status code, and a String reason."))

(defprotocol AsyncSocket
"A protocol for sending data asynchronously via websocket. Intended for use
with the Socket protocol."
(-send-async [socket message succeed fail]
"Sends a String or ByteBuffer to the client via the websocket. If it
succeeds, the 'succeed' callback function is called with zero arguments. If
it fails, the 'fail' callback function is called with the exception that was
thrown."))

0 comments on commit 7cc8e95

Please sign in to comment.