protocol-websocket 0.13.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/protocol/websocket/binary_frame.rb +6 -1
- data/lib/protocol/websocket/close_frame.rb +8 -1
- data/lib/protocol/websocket/coder/json.rb +32 -0
- data/lib/protocol/websocket/coder.rb +15 -0
- data/lib/protocol/websocket/connection.rb +45 -9
- data/lib/protocol/websocket/continuation_frame.rb +3 -1
- data/lib/protocol/websocket/error.rb +4 -4
- data/lib/protocol/websocket/extension/compression.rb +2 -1
- data/lib/protocol/websocket/extensions.rb +5 -3
- data/lib/protocol/websocket/frame.rb +2 -1
- data/lib/protocol/websocket/framer.rb +6 -2
- data/lib/protocol/websocket/json_message.rb +5 -14
- data/lib/protocol/websocket/message.rb +30 -2
- data/lib/protocol/websocket/ping_frame.rb +2 -1
- data/lib/protocol/websocket/pong_frame.rb +2 -1
- data/lib/protocol/websocket/text_frame.rb +3 -2
- data/lib/protocol/websocket/version.rb +2 -2
- data/lib/protocol/websocket.rb +2 -1
- data/readme.md +8 -4
- data.tar.gz.sig +0 -0
- metadata +5 -4
- metadata.gz.sig +0 -0
- data/lib/protocol/websocket/extensions.md +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec6ec828408c7579273d456c0a9ce582a794757b5c532e228c45eca4779acee1
|
4
|
+
data.tar.gz: 5abe7c8dcb8e1e5cd64e62e06cb0f7585ec0fa8646baf4cecaa6945542cf8b33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e5daf9a48e29e811b255516dd777fafbbdf084c8ed681d81ebe2fc67eead205702186b42231abfa032060afc62d439a2e6c8c90d1696eec58be37c40cf52d2a9
|
7
|
+
data.tar.gz: 6dc89a62a3179b9e2d397cac57204a5d73897ada519abba42d3f85ec184c6f74c1c2f26d81b2ef5277c0fa4b17c35cf283019f99e9a01537d32fece66c76db2b
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
# Copyright, 2021, by Aurora Nockert.
|
6
6
|
|
7
7
|
require_relative 'frame'
|
@@ -9,17 +9,22 @@ require_relative 'message'
|
|
9
9
|
|
10
10
|
module Protocol
|
11
11
|
module WebSocket
|
12
|
+
# Represents a binary frame that is sent or received by a WebSocket connection.
|
12
13
|
class BinaryFrame < Frame
|
13
14
|
OPCODE = 0x2
|
14
15
|
|
16
|
+
# @returns [Boolean] if the frame contains data.
|
15
17
|
def data?
|
16
18
|
true
|
17
19
|
end
|
18
20
|
|
21
|
+
# Decode the binary buffer into a suitable binary message.
|
22
|
+
# @parameter buffer [String] The binary data to unpack.
|
19
23
|
def read_message(buffer)
|
20
24
|
return BinaryMessage.new(buffer)
|
21
25
|
end
|
22
26
|
|
27
|
+
# Apply this frame to the specified connection.
|
23
28
|
def apply(connection)
|
24
29
|
connection.receive_binary(self)
|
25
30
|
end
|
@@ -1,17 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
# Copyright, 2021, by Aurora Nockert.
|
6
6
|
|
7
7
|
require_relative 'frame'
|
8
8
|
|
9
9
|
module Protocol
|
10
10
|
module WebSocket
|
11
|
+
# Represents a close frame that is sent or received by a WebSocket connection.
|
11
12
|
class CloseFrame < Frame
|
12
13
|
OPCODE = 0x8
|
13
14
|
FORMAT = "na*"
|
14
15
|
|
16
|
+
# Unpack the frame data into a close code and reason.
|
17
|
+
# @returns [Tuple(Integer, String)] The close code and reason.
|
15
18
|
def unpack
|
16
19
|
data = super
|
17
20
|
|
@@ -40,7 +43,10 @@ module Protocol
|
|
40
43
|
end
|
41
44
|
end
|
42
45
|
|
46
|
+
# Pack a close code and reason into the frame data.
|
43
47
|
# If code is missing, reason is ignored.
|
48
|
+
# @parameter code [Integer | Nil] The close code.
|
49
|
+
# @parameter reason [String | Nil] The close reason.
|
44
50
|
def pack(code = nil, reason = nil)
|
45
51
|
if code
|
46
52
|
if reason and reason.encoding != Encoding::UTF_8
|
@@ -53,6 +59,7 @@ module Protocol
|
|
53
59
|
end
|
54
60
|
end
|
55
61
|
|
62
|
+
# Apply this frame to the specified connection.
|
56
63
|
def apply(connection)
|
57
64
|
connection.receive_close(self)
|
58
65
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2024, by Samuel Williams.
|
5
|
+
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
module Protocol
|
9
|
+
module WebSocket
|
10
|
+
module Coder
|
11
|
+
# A JSON coder that uses the standard JSON library.
|
12
|
+
class JSON
|
13
|
+
def initialize(**options)
|
14
|
+
@options = options
|
15
|
+
end
|
16
|
+
|
17
|
+
# Parse a JSON buffer into an object.
|
18
|
+
def parse(buffer)
|
19
|
+
::JSON.parse(buffer, **@options)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Generate a JSON buffer from an object.
|
23
|
+
def generate(object)
|
24
|
+
::JSON.generate(object, **@options)
|
25
|
+
end
|
26
|
+
|
27
|
+
# The default JSON coder. This coder will symbolize names.
|
28
|
+
DEFAULT = new(symbolize_names: true)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2024, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative 'coder/json'
|
7
|
+
|
8
|
+
module Protocol
|
9
|
+
module WebSocket
|
10
|
+
module Coder
|
11
|
+
# The default coder for WebSocket messages.
|
12
|
+
DEFAULT = JSON::DEFAULT
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
# Copyright, 2019, by William T. Nelson.
|
6
6
|
# Copyright, 2021, by Aurora Nockert.
|
7
7
|
|
@@ -26,21 +26,26 @@ module Protocol
|
|
26
26
|
@writer = self
|
27
27
|
end
|
28
28
|
|
29
|
-
# The framer which is used for reading and writing frames.
|
29
|
+
# @attribute [Framer] The framer which is used for reading and writing frames.
|
30
30
|
attr :framer
|
31
31
|
|
32
|
-
# The
|
32
|
+
# @attribte [String | Nil] The optional mask which is used when generating frames.
|
33
33
|
attr :mask
|
34
34
|
|
35
|
-
# The allowed reserved bits
|
35
|
+
# @attribute [Integer] The allowed reserved bits.
|
36
36
|
attr :reserved
|
37
37
|
|
38
|
-
# Buffered frames which form part of a complete message.
|
38
|
+
# @attribute [Array(Frame)] Buffered frames which form part of a complete message.
|
39
39
|
attr_accessor :frames
|
40
40
|
|
41
|
+
# @attribute [Object] The reader which is used to unpack frames into messages.
|
41
42
|
attr_accessor :reader
|
43
|
+
|
44
|
+
# @attribute [Object] The writer which is used to pack messages into frames.
|
42
45
|
attr_accessor :writer
|
43
46
|
|
47
|
+
# Reserve a bit in the reserved flags for an extension.
|
48
|
+
# @parameter bit [Integer] The bit to reserve, see {Frame::RESERVED} for more details.
|
44
49
|
def reserve!(bit)
|
45
50
|
if (@reserved & bit).zero?
|
46
51
|
raise ArgumentError, "Unable to use #{bit}!"
|
@@ -51,10 +56,12 @@ module Protocol
|
|
51
56
|
return true
|
52
57
|
end
|
53
58
|
|
59
|
+
# Flush the underlying framer to ensure all buffered data is written to the connection.
|
54
60
|
def flush
|
55
61
|
@framer.flush
|
56
62
|
end
|
57
63
|
|
64
|
+
# Transition the connection to the open state (the default for new connections).
|
58
65
|
def open!
|
59
66
|
@state = :open
|
60
67
|
|
@@ -62,6 +69,7 @@ module Protocol
|
|
62
69
|
end
|
63
70
|
|
64
71
|
# If not already closed, transition the connection to the closed state and send a close frame.
|
72
|
+
# Will try to send a close frame with the specified code and reason, but will ignore any errors that occur while sending.
|
65
73
|
def close!(...)
|
66
74
|
unless @state == :closed
|
67
75
|
@state = :closed
|
@@ -76,17 +84,19 @@ module Protocol
|
|
76
84
|
return self
|
77
85
|
end
|
78
86
|
|
87
|
+
# @returns [Boolean] if the connection is in the closed state.
|
79
88
|
def closed?
|
80
89
|
@state == :closed
|
81
90
|
end
|
82
91
|
|
83
|
-
# Immediately transition the connection to the closed state and close the underlying connection.
|
92
|
+
# Immediately transition the connection to the closed state *and* close the underlying connection.
|
84
93
|
def close(...)
|
85
94
|
close!(...)
|
86
95
|
|
87
96
|
@framer.close
|
88
97
|
end
|
89
98
|
|
99
|
+
# Read a frame from the framer, and apply it to the connection.
|
90
100
|
def read_frame
|
91
101
|
return nil if closed?
|
92
102
|
|
@@ -109,12 +119,15 @@ module Protocol
|
|
109
119
|
raise
|
110
120
|
end
|
111
121
|
|
122
|
+
# Write a frame to the framer.
|
123
|
+
# Note: This does not immediately write the frame to the connection, you must call {#flush} to ensure the frame is written.
|
112
124
|
def write_frame(frame)
|
113
125
|
@framer.write_frame(frame)
|
114
126
|
|
115
127
|
return frame
|
116
128
|
end
|
117
129
|
|
130
|
+
# Receive a text frame from the connection.
|
118
131
|
def receive_text(frame)
|
119
132
|
if @frames.empty?
|
120
133
|
@frames << frame
|
@@ -123,6 +136,7 @@ module Protocol
|
|
123
136
|
end
|
124
137
|
end
|
125
138
|
|
139
|
+
# Receive a binary frame for the connection.
|
126
140
|
def receive_binary(frame)
|
127
141
|
if @frames.empty?
|
128
142
|
@frames << frame
|
@@ -131,6 +145,7 @@ module Protocol
|
|
131
145
|
end
|
132
146
|
end
|
133
147
|
|
148
|
+
# Receive a continuation frame for the connection.
|
134
149
|
def receive_continuation(frame)
|
135
150
|
if @frames.any?
|
136
151
|
@frames << frame
|
@@ -138,7 +153,8 @@ module Protocol
|
|
138
153
|
raise ProtocolError, "Received unexpected continuation!"
|
139
154
|
end
|
140
155
|
end
|
141
|
-
|
156
|
+
|
157
|
+
# Receive a close frame from the connection.
|
142
158
|
def receive_close(frame)
|
143
159
|
code, reason = frame.unpack
|
144
160
|
|
@@ -150,6 +166,8 @@ module Protocol
|
|
150
166
|
end
|
151
167
|
end
|
152
168
|
|
169
|
+
# Send a ping frame with the specified data.
|
170
|
+
# @parameter data [String] The data to send in the ping frame.
|
153
171
|
def send_ping(data = "")
|
154
172
|
if @state != :closed
|
155
173
|
frame = PingFrame.new(mask: @mask)
|
@@ -161,6 +179,7 @@ module Protocol
|
|
161
179
|
end
|
162
180
|
end
|
163
181
|
|
182
|
+
# Receive a ping frame from the connection.
|
164
183
|
def receive_ping(frame)
|
165
184
|
if @state != :closed
|
166
185
|
write_frame(frame.reply(mask: @mask))
|
@@ -169,14 +188,19 @@ module Protocol
|
|
169
188
|
end
|
170
189
|
end
|
171
190
|
|
191
|
+
# Receive a pong frame from the connection. By default, this method does nothing.
|
172
192
|
def receive_pong(frame)
|
173
193
|
# Ignore.
|
174
194
|
end
|
175
195
|
|
196
|
+
# Receive a frame that is not a control frame. By default, this method raises a {ProtocolError}.
|
176
197
|
def receive_frame(frame)
|
177
198
|
raise ProtocolError, "Unhandled frame: #{frame}"
|
178
199
|
end
|
179
200
|
|
201
|
+
# Pack a text frame with the specified buffer. This is used by the {#writer} interface.
|
202
|
+
# @parameter buffer [String] The text to pack into the frame.
|
203
|
+
# @returns [TextFrame] The packed frame.
|
180
204
|
def pack_text_frame(buffer, **options)
|
181
205
|
frame = TextFrame.new(mask: @mask)
|
182
206
|
frame.pack(buffer)
|
@@ -184,10 +208,15 @@ module Protocol
|
|
184
208
|
return frame
|
185
209
|
end
|
186
210
|
|
211
|
+
# Send a text frame with the specified buffer.
|
212
|
+
# @parameter buffer [String] The text to send.
|
187
213
|
def send_text(buffer, **options)
|
188
214
|
write_frame(@writer.pack_text_frame(buffer, **options))
|
189
215
|
end
|
190
216
|
|
217
|
+
# Pack a binary frame with the specified buffer. This is used by the {#writer} interface.
|
218
|
+
# @parameter buffer [String] The binary data to pack into the frame.
|
219
|
+
# @returns [BinaryFrame] The packed frame.
|
191
220
|
def pack_binary_frame(buffer, **options)
|
192
221
|
frame = BinaryFrame.new(mask: @mask)
|
193
222
|
frame.pack(buffer)
|
@@ -195,11 +224,15 @@ module Protocol
|
|
195
224
|
return frame
|
196
225
|
end
|
197
226
|
|
227
|
+
# Send a binary frame with the specified buffer.
|
228
|
+
# @parameter buffer [String] The binary data to send.
|
198
229
|
def send_binary(buffer, **options)
|
199
230
|
write_frame(@writer.pack_binary_frame(buffer, **options))
|
200
231
|
end
|
201
232
|
|
202
233
|
# Send a control frame with data containing a specified control sequence to begin the closing handshake. Does not close the connection, until the remote end responds with a close frame.
|
234
|
+
# @parameter code [Integer] The close code to send.
|
235
|
+
# @parameter reason [String] The reason for closing the connection.
|
203
236
|
def send_close(code = Error::NO_ERROR, reason = "")
|
204
237
|
frame = CloseFrame.new(mask: @mask)
|
205
238
|
frame.pack(code, reason)
|
@@ -223,12 +256,15 @@ module Protocol
|
|
223
256
|
message.send(self, **options)
|
224
257
|
end
|
225
258
|
|
226
|
-
# The default implementation for reading a message buffer.
|
259
|
+
# The default implementation for reading a message buffer. This is used by the {#reader} interface.
|
227
260
|
def unpack_frames(frames)
|
228
261
|
frames.map(&:unpack).join("")
|
229
262
|
end
|
230
263
|
|
231
|
-
# Read a message from the connection.
|
264
|
+
# Read a message from the connection. If an error occurs while reading the message, the connection will be closed.
|
265
|
+
#
|
266
|
+
# If the message is fragmented, this method will buffer the frames until a complete message is received.
|
267
|
+
#
|
232
268
|
# @returns message [Message] The received message.
|
233
269
|
def read(**options)
|
234
270
|
@framer.flush
|
@@ -1,15 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative 'frame'
|
7
7
|
|
8
8
|
module Protocol
|
9
9
|
module WebSocket
|
10
|
+
# Represents a continuation frame that is sent or received by a WebSocket connection when a message is split into multiple frames.
|
10
11
|
class ContinuationFrame < Frame
|
11
12
|
OPCODE = 0x0
|
12
13
|
|
14
|
+
# Apply this frame to the specified connection.
|
13
15
|
def apply(connection)
|
14
16
|
connection.receive_continuation(self)
|
15
17
|
end
|
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
|
6
6
|
require 'protocol/http/error'
|
7
7
|
|
8
8
|
module Protocol
|
9
9
|
module WebSocket
|
10
|
+
# Represents an error that occurred during the WebSocket protocol negotiation or communication.
|
10
11
|
# Status codes as defined by <https://tools.ietf.org/html/rfc6455#section-7.4.1>.
|
11
12
|
class Error < HTTP::Error
|
12
13
|
# Indicates a normal closure, meaning that the purpose for which the connection was established has been fulfilled.
|
@@ -24,9 +25,7 @@ module Protocol
|
|
24
25
|
# There are other status codes but most of them are "implementation specific".
|
25
26
|
end
|
26
27
|
|
27
|
-
# Raised by stream or connection handlers, results in GOAWAY frame
|
28
|
-
# which signals termination of the current connection. You *cannot*
|
29
|
-
# recover from this exception, or any exceptions subclassed from it.
|
28
|
+
# Raised by stream or connection handlers, results in GOAWAY frame which signals termination of the current connection. You *cannot* recover from this exception, or any exceptions subclassed from it.
|
30
29
|
class ProtocolError < Error
|
31
30
|
def initialize(message, code = PROTOCOL_ERROR)
|
32
31
|
super(message)
|
@@ -34,6 +33,7 @@ module Protocol
|
|
34
33
|
@code = code
|
35
34
|
end
|
36
35
|
|
36
|
+
# @attribute [Integer] The status code associated with the error.
|
37
37
|
attr :code
|
38
38
|
end
|
39
39
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2022-
|
4
|
+
# Copyright, 2022-2024, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative 'compression/constants'
|
7
7
|
require_relative 'compression/inflate'
|
@@ -10,6 +10,7 @@ require_relative 'compression/deflate'
|
|
10
10
|
module Protocol
|
11
11
|
module WebSocket
|
12
12
|
module Extension
|
13
|
+
# Provides support for the permessage-deflate extension.
|
13
14
|
module Compression
|
14
15
|
# Client offer to server, construct a list of requested compression parameters suitable for the `Sec-WebSocket-Extensions` header.
|
15
16
|
# @returns [Array(String)] a list of compression parameters suitable to send to the server.
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2022-
|
4
|
+
# Copyright, 2022-2024, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative 'extension/compression'
|
7
7
|
require_relative 'headers'
|
@@ -65,6 +65,8 @@ module Protocol
|
|
65
65
|
@accepted << [klass, options]
|
66
66
|
end
|
67
67
|
end
|
68
|
+
|
69
|
+
return @accepted
|
68
70
|
end
|
69
71
|
|
70
72
|
def apply(connection)
|
@@ -112,14 +114,14 @@ module Protocol
|
|
112
114
|
# The extension is accepted and no further offers will be considered:
|
113
115
|
named.delete(name)
|
114
116
|
|
115
|
-
yield header
|
117
|
+
yield header if block_given?
|
116
118
|
|
117
119
|
@accepted << [klass, options]
|
118
120
|
end
|
119
121
|
end
|
120
122
|
end
|
121
123
|
|
122
|
-
return
|
124
|
+
return @accepted
|
123
125
|
end
|
124
126
|
|
125
127
|
def apply(connection)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
# Copyright, 2019, by Soumya.
|
6
6
|
# Copyright, 2021, by Aurora Nockert.
|
7
7
|
|
@@ -50,6 +50,7 @@ module Protocol
|
|
50
50
|
@opcode & 0x8 != 0
|
51
51
|
end
|
52
52
|
|
53
|
+
# @returns [Boolean] if the frame contains data.
|
53
54
|
def data?
|
54
55
|
false
|
55
56
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative 'frame'
|
7
7
|
|
@@ -34,16 +34,18 @@ module Protocol
|
|
34
34
|
@frames = frames
|
35
35
|
end
|
36
36
|
|
37
|
+
# Close the underlying stream.
|
37
38
|
def close
|
38
39
|
@stream.close
|
39
40
|
end
|
40
41
|
|
42
|
+
# Flush the underlying stream.
|
41
43
|
def flush
|
42
44
|
@stream.flush
|
43
45
|
end
|
44
46
|
|
45
47
|
# Read a frame from the underlying stream.
|
46
|
-
# @returns [Frame]
|
48
|
+
# @returns [Frame] the frame read from the stream.
|
47
49
|
def read_frame(maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE)
|
48
50
|
# Read the header:
|
49
51
|
finished, flags, opcode = read_header
|
@@ -55,10 +57,12 @@ module Protocol
|
|
55
57
|
return frame
|
56
58
|
end
|
57
59
|
|
60
|
+
# Write a frame to the underlying stream.
|
58
61
|
def write_frame(frame)
|
59
62
|
frame.write(@stream)
|
60
63
|
end
|
61
64
|
|
65
|
+
# Read the header of the frame.
|
62
66
|
def read_header
|
63
67
|
if buffer = @stream.read(1) and buffer.bytesize == 1
|
64
68
|
return Frame.parse_header(buffer)
|
@@ -1,32 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2022-
|
5
|
-
|
6
|
-
require 'json'
|
4
|
+
# Copyright, 2022-2024, by Samuel Williams.
|
7
5
|
|
8
6
|
require_relative 'message'
|
9
7
|
|
8
|
+
warn "Protocol::WebSocket::JSONMessage is deprecated. Use Protocol::WebSocket::TextMessage instead."
|
9
|
+
|
10
10
|
module Protocol
|
11
11
|
module WebSocket
|
12
|
+
# @deprecated Use {TextMessage} instead.
|
12
13
|
class JSONMessage < TextMessage
|
13
14
|
def self.wrap(message)
|
14
|
-
|
15
|
-
self.new(message.buffer)
|
16
|
-
end
|
15
|
+
message
|
17
16
|
end
|
18
17
|
|
19
18
|
def self.generate(object)
|
20
19
|
self.new(JSON.generate(object))
|
21
20
|
end
|
22
|
-
|
23
|
-
def parse(symbolize_names: true, **options)
|
24
|
-
JSON.parse(@buffer, symbolize_names: symbolize_names, **options)
|
25
|
-
end
|
26
|
-
|
27
|
-
def to_h
|
28
|
-
parse.to_h
|
29
|
-
end
|
30
21
|
end
|
31
22
|
end
|
32
23
|
end
|
@@ -1,43 +1,71 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2022-
|
4
|
+
# Copyright, 2022-2024, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative 'frame'
|
7
|
+
require_relative 'coder'
|
7
8
|
|
8
9
|
module Protocol
|
9
10
|
module WebSocket
|
11
|
+
# Represents a message that can be sent or received over a WebSocket connection.
|
10
12
|
class Message
|
13
|
+
# Create a new message from a buffer.
|
14
|
+
# @attribute buffer [String] The message buffer.
|
11
15
|
def initialize(buffer)
|
12
16
|
@buffer = buffer
|
13
17
|
end
|
14
18
|
|
19
|
+
# @attribute [String] The message buffer.
|
15
20
|
attr :buffer
|
16
21
|
|
22
|
+
# @returns [Integer] The size of the message buffer.
|
17
23
|
def size
|
18
24
|
@buffer.bytesize
|
19
25
|
end
|
20
26
|
|
21
|
-
#
|
27
|
+
# Compare this message to another message or buffer.
|
22
28
|
def == other
|
23
29
|
@buffer == other.to_str
|
24
30
|
end
|
25
31
|
|
32
|
+
# A message is implicitly convertible to it's buffer.
|
26
33
|
def to_str
|
27
34
|
@buffer
|
28
35
|
end
|
29
36
|
|
37
|
+
# The encoding of the message buffer.
|
38
|
+
# @returns [Encoding]
|
30
39
|
def encoding
|
31
40
|
@buffer.encoding
|
32
41
|
end
|
42
|
+
|
43
|
+
# Generate a message from a value using the given coder.
|
44
|
+
# @property value [Object] The value to encode.
|
45
|
+
# @property coder [Coder] The coder to use. Defaults to JSON.
|
46
|
+
def self.generate(value, coder = Coder::DEFAULT)
|
47
|
+
new(coder.generate(value))
|
48
|
+
end
|
49
|
+
|
50
|
+
# Parse the message buffer using the given coder. Defaults to JSON.
|
51
|
+
def parse(coder = Coder::DEFAULT)
|
52
|
+
coder.parse(@buffer)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Convert the message buffer to a hash using the given coder. Defaults to JSON.
|
56
|
+
def to_h(...)
|
57
|
+
parse(...).to_h
|
58
|
+
end
|
33
59
|
end
|
34
60
|
|
61
|
+
# Represents a text message that can be sent or received over a WebSocket connection.
|
35
62
|
class TextMessage < Message
|
36
63
|
def send(connection, **options)
|
37
64
|
connection.send_text(@buffer, **options)
|
38
65
|
end
|
39
66
|
end
|
40
67
|
|
68
|
+
# Represents a binary message that can be sent or received over a WebSocket connection.
|
41
69
|
class BinaryMessage < Message
|
42
70
|
def send(connection, **options)
|
43
71
|
connection.send_binary(@buffer, **options)
|
@@ -1,13 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative 'frame'
|
7
7
|
require_relative 'pong_frame'
|
8
8
|
|
9
9
|
module Protocol
|
10
10
|
module WebSocket
|
11
|
+
# Represents a ping frame that is sent or received by a WebSocket connection.
|
11
12
|
class PingFrame < Frame
|
12
13
|
OPCODE = 0x9
|
13
14
|
|
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative 'frame'
|
7
7
|
|
8
8
|
module Protocol
|
9
9
|
module WebSocket
|
10
|
+
# Represents a pong frame that is sent or received by a WebSocket connection.
|
10
11
|
class PongFrame < Frame
|
11
12
|
OPCODE = 0xA
|
12
13
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
# Copyright, 2021, by Aurora Nockert.
|
6
6
|
|
7
7
|
require_relative 'frame'
|
@@ -9,10 +9,11 @@ require_relative 'message'
|
|
9
9
|
|
10
10
|
module Protocol
|
11
11
|
module WebSocket
|
12
|
-
#
|
12
|
+
# Represents a text frame that is sent or received by a WebSocket connection.
|
13
13
|
class TextFrame < Frame
|
14
14
|
OPCODE = 0x1
|
15
15
|
|
16
|
+
# @returns [Boolean] if the frame contains data.
|
16
17
|
def data?
|
17
18
|
true
|
18
19
|
end
|
data/lib/protocol/websocket.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative 'websocket/version'
|
7
7
|
require_relative 'websocket/framer'
|
8
8
|
require_relative 'websocket/connection'
|
9
|
+
require_relative 'websocket/message'
|
data/readme.md
CHANGED
@@ -6,7 +6,11 @@ Provides a low-level implementation of the WebSocket protocol according to [RFC6
|
|
6
6
|
|
7
7
|
## Usage
|
8
8
|
|
9
|
-
Please see the [project documentation](https://socketry.github.io/protocol-websocket).
|
9
|
+
Please see the [project documentation](https://socketry.github.io/protocol-websocket/) for more details.
|
10
|
+
|
11
|
+
- [Getting Started](https://socketry.github.io/protocol-websocket/guides/getting-started/index) - This guide explains how to use `protocol-websocket` for implementing a websocket client and server.
|
12
|
+
|
13
|
+
- [Extensions](https://socketry.github.io/protocol-websocket/guides/extensions/index) - This guide explains how to use `protocol-websocket` for implementing a websocket client and server using extensions.
|
10
14
|
|
11
15
|
## Contributing
|
12
16
|
|
@@ -20,8 +24,8 @@ We welcome contributions to this project.
|
|
20
24
|
|
21
25
|
### Developer Certificate of Origin
|
22
26
|
|
23
|
-
|
27
|
+
In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
|
24
28
|
|
25
|
-
###
|
29
|
+
### Community Guidelines
|
26
30
|
|
27
|
-
This project is
|
31
|
+
This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: protocol-websocket
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -41,7 +41,7 @@ cert_chain:
|
|
41
41
|
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
42
42
|
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
43
43
|
-----END CERTIFICATE-----
|
44
|
-
date: 2024-
|
44
|
+
date: 2024-07-03 00:00:00.000000000 Z
|
45
45
|
dependencies:
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
47
|
name: protocol-http
|
@@ -66,6 +66,8 @@ files:
|
|
66
66
|
- lib/protocol/websocket.rb
|
67
67
|
- lib/protocol/websocket/binary_frame.rb
|
68
68
|
- lib/protocol/websocket/close_frame.rb
|
69
|
+
- lib/protocol/websocket/coder.rb
|
70
|
+
- lib/protocol/websocket/coder/json.rb
|
69
71
|
- lib/protocol/websocket/connection.rb
|
70
72
|
- lib/protocol/websocket/continuation_frame.rb
|
71
73
|
- lib/protocol/websocket/error.rb
|
@@ -73,7 +75,6 @@ files:
|
|
73
75
|
- lib/protocol/websocket/extension/compression/constants.rb
|
74
76
|
- lib/protocol/websocket/extension/compression/deflate.rb
|
75
77
|
- lib/protocol/websocket/extension/compression/inflate.rb
|
76
|
-
- lib/protocol/websocket/extensions.md
|
77
78
|
- lib/protocol/websocket/extensions.rb
|
78
79
|
- lib/protocol/websocket/frame.rb
|
79
80
|
- lib/protocol/websocket/framer.rb
|
@@ -107,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
108
|
- !ruby/object:Gem::Version
|
108
109
|
version: '0'
|
109
110
|
requirements: []
|
110
|
-
rubygems_version: 3.
|
111
|
+
rubygems_version: 3.5.11
|
111
112
|
signing_key:
|
112
113
|
specification_version: 4
|
113
114
|
summary: A low level implementation of the WebSocket protocol.
|
metadata.gz.sig
CHANGED
Binary file
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# Extensions
|
2
|
-
|
3
|
-
WebSockets have a mechanism for implementing extensions. The only published extension is for per-message compression. It operates on complete messages rather than individual frames.
|
4
|
-
|
5
|
-
## Setup
|
6
|
-
|
7
|
-
Clients need to define a set of extensions they want to support. The server then receives this via the `Sec-WebSocket-Extensions` header which includes a list of:
|
8
|
-
|
9
|
-
Name, Options
|
10
|
-
|
11
|
-
The server processes this and returns a subset of accepted `(Name, Options)`. It also instantiates the extensions and applies them to the server connection object.
|
12
|
-
|
13
|
-
The client receives a list of accepted `(Name, Options)` and instantiates the extensions and applies them to the client connection object.
|