protocol-websocket 0.13.0 → 0.14.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 +1 -2
- 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/readme.md +5 -1
- 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: 03d416a8b81559d07517a00db06f2740b42c1a4e311ab2b1cbc22fcd58041f49
|
4
|
+
data.tar.gz: af5e7dcf5f2a3d28a7b21a959827462753b5af2f28e9b5f2b41af03c9a09f590
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ce48f4ee999d45de148dab68f3b5c7adaca5c35d0d67a2f4cf5a24a80428de545620c9c4eae7c0e96c8950da49b48c34fd98fcde3fb558b0e8111edd971e418
|
7
|
+
data.tar.gz: 20a33a6c1b1a58cff116973575cf5de64778e9a4a3c5ce74c7de2310c3b4f1ee37e290d6cac705c3e2698cb4452bad9f9de2494104b0b04daa0deab0ab589a4c
|
checksums.yaml.gz.sig
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
|
2
|
-
�%��1�dGHjW��j��n�k��l��{�@9�������8������R�/,Z�e7u��I���Y��7)������,�����r(
|
1
|
+
DN�3D�*�_C�CZwꌆ#�>��ׂKί�Z���X�$u�v��C�UFu�^1m�'�)��+�t�@�����ϝ�J�&���C�Bg�����i���ȿ:�-�U��mu=7�b}1��`�|�����y����F6��{��k�3rgE��TЪ!]vi6?{q��Ψ�Ƅ� �| ���F�J�PD�Y��` W�B��5R�'�"-,gV�6�g_���`K�i�b�`p���8�L��+�3���\���Ҥd�}|��2kn�ޢ�TܬH��Jq�)��6��o�����-��GhT�iv����f|�6-�k�ױ�z�{�fK�mo�?��<*3�V���v/�2��� V��ZlԤN�z��.���iۧ�����L
|
@@ -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/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
|
|
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.14.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-06-20 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.9
|
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.
|