webtube 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/GPL-3 +674 -0
- data/Manifest.txt +9 -0
- data/README +779 -0
- data/bin/wsc +239 -0
- data/lib/webtube.rb +757 -0
- data/lib/webtube/vital-statistics.rb +74 -0
- data/lib/webtube/webrick.rb +187 -0
- data/sample-server.rb +47 -0
- data/webtube.gemspec +17 -0
- metadata +55 -0
data/Manifest.txt
ADDED
data/README
ADDED
@@ -0,0 +1,779 @@
|
|
1
|
+
This is Webtube, a Ruby implementation of the WebSocket protocol defined in
|
2
|
+
RFC 6455.
|
3
|
+
|
4
|
+
|
5
|
+
== Sample client
|
6
|
+
|
7
|
+
(Also see [[wsc]], a basic command line utility for talking to WebSocket
|
8
|
+
servers and included with the Webtube distribution.)
|
9
|
+
|
10
|
+
require 'webtube'
|
11
|
+
|
12
|
+
$webtube = Webtube.connect 'ws://echo.websocket.org/'
|
13
|
+
$webtube.send_message 'Hello, echo server!'
|
14
|
+
|
15
|
+
class << $listener = Object.new
|
16
|
+
def onmessage webtube, message, opcode
|
17
|
+
puts "The echo server says: #{message.inspect}"
|
18
|
+
# We only care about one message.
|
19
|
+
$webtube.close
|
20
|
+
return
|
21
|
+
end
|
22
|
+
end
|
23
|
+
$webtube.run $listener
|
24
|
+
|
25
|
+
|
26
|
+
== Sample server
|
27
|
+
|
28
|
+
(This code is also available as a separate file; see [[sample-server.rb]].)
|
29
|
+
|
30
|
+
#! /usr/bin/ruby
|
31
|
+
|
32
|
+
# A sample WEBrick server using the Webtube API. It listens on port 8888 and
|
33
|
+
# provides two services: [[/diag]], which logs all the events from
|
34
|
+
# [[Webtube#run]] and remains silent towards the client (although note that
|
35
|
+
# the Webtube library pongs the pings), and [[/echo]], which echos.
|
36
|
+
|
37
|
+
require 'webrick'
|
38
|
+
require 'webtube/webrick'
|
39
|
+
|
40
|
+
class << diagnostic_listener = Object.new
|
41
|
+
def respond_to? name
|
42
|
+
return (name.to_s =~ /^on/ or super)
|
43
|
+
end
|
44
|
+
|
45
|
+
def method_missing name, *args
|
46
|
+
output = "- #{name}("
|
47
|
+
args.each_with_index do |arg, i|
|
48
|
+
output << ', ' unless i.zero?
|
49
|
+
if i.zero? and arg.is_a? Webtube then
|
50
|
+
output << arg.to_s
|
51
|
+
else
|
52
|
+
output << arg.inspect
|
53
|
+
end
|
54
|
+
end
|
55
|
+
output << ")"
|
56
|
+
puts output
|
57
|
+
return
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class << echo_listener = Object.new
|
62
|
+
def onmessage webtube, data, opcode
|
63
|
+
webtube.send_message data, opcode
|
64
|
+
return
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
server = WEBrick::HTTPServer.new(:Port => 8888)
|
69
|
+
server.mount_webtube '/diag', diagnostic_listener
|
70
|
+
server.mount_webtube '/echo', echo_listener
|
71
|
+
|
72
|
+
begin
|
73
|
+
server.start
|
74
|
+
ensure
|
75
|
+
server.shutdown
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
== WebSocketCat's commands
|
80
|
+
|
81
|
+
Webtube comes with [[wsc]], a command line utility for talking to WebSocket
|
82
|
+
server. A session might look like this:
|
83
|
+
|
84
|
+
$ wsc ws://echo.websocket.org/
|
85
|
+
Connecting to ws://echo.websocket.org/ ...
|
86
|
+
| 101 Web Socket Protocol Handshake
|
87
|
+
| connection: Upgrade
|
88
|
+
| date: Fri, 10 Oct 2014 12:43:49 GMT
|
89
|
+
| sec-websocket-accept: Uk2zpTKu2TaQtJ2esybCgeB89qk=
|
90
|
+
| server: Kaazing Gateway
|
91
|
+
| upgrade: websocket
|
92
|
+
|
93
|
+
*** open
|
94
|
+
Hello, server!
|
95
|
+
<<< Hello, server!
|
96
|
+
/ping
|
97
|
+
(Ping sent.)
|
98
|
+
*** pong ""
|
99
|
+
/close 1001
|
100
|
+
*** close
|
101
|
+
|
102
|
+
Prefixed with [[|]] is the HTTP response header from the server, with [[<<<]]
|
103
|
+
are incoming text messages (non-text messages are prefixed with [[<N<]] where N
|
104
|
+
is the message opcode), and with [[***]] are miscellaneous other events. Lines
|
105
|
+
entered by user are sent to the server as text messages. The user can invoke
|
106
|
+
some special commands using the slash prefix:
|
107
|
+
|
108
|
+
- [[/ping [message]]] sends a ping frame to the server.
|
109
|
+
|
110
|
+
- [[/close [status [explanation]]]] sends a close frame to the server. The
|
111
|
+
status code is specified as an unsigned decimal number.
|
112
|
+
|
113
|
+
- [[/N [payload]]] sends a message or control frame of opcode [[N]], given as a
|
114
|
+
single hex digit, to the server. Per protocol specification, [[/1]] is text
|
115
|
+
message, [[/2]] is binary message, [[/8]] is close, [[/9]] is ping, [[/A]] is
|
116
|
+
pong. Other opcodes can have application-specific meaning. Note that the
|
117
|
+
specification requires kicking clients (or servers) who send messages so
|
118
|
+
cryptic that the server (or client) can't understand them.
|
119
|
+
|
120
|
+
- [[/help]] shows online help.
|
121
|
+
|
122
|
+
If you need to start a text message with a slash, you can double it for escape,
|
123
|
+
or you can use the explicit [[/1]] command. EOF from stdin is equivalent to
|
124
|
+
[[/close 1000]].
|
125
|
+
|
126
|
+
|
127
|
+
== API overview
|
128
|
+
|
129
|
+
=== The [[Webtube]] class
|
130
|
+
|
131
|
+
(Direct) instances of the [[Webtube]] class are hashed and [[eql?]]-compared by
|
132
|
+
identity, thus behaving in an intuitive manner when used as keys of a [[Hash]]
|
133
|
+
or elements of a [[Set]].
|
134
|
+
|
135
|
+
In addition to the methods described below, each [[Webtube]] instance will have
|
136
|
+
the readable and writable attributes [[header]], [[session]], and [[context]].
|
137
|
+
[[Webtube]] does not care about them; they are intended to facilitate user code
|
138
|
+
associating contextual or environmental data with the [[Webtube]].
|
139
|
+
|
140
|
+
The [[Webtube]]-[[WEBrick]] integration (in particular,
|
141
|
+
[[WEBrick::HTTPServer#accept_webtube]]) sets the [[header]] attribtue of newly
|
142
|
+
created [[Webtube]] instances to the [[WEBrick::HTTPRequest]] used to establish
|
143
|
+
the WebSocket connection; this may facilitate extracting HTTP header fields,
|
144
|
+
URL query parameters or cookies from the request at a later time. (Note that
|
145
|
+
because the upgrade request is necessary delivered using the HTTP [[GET]]
|
146
|
+
method, the HTTP file upload protocol, which requires [[POST]] is not available
|
147
|
+
for WebSocket connections. Also note that the WebDAV extensions are mutually
|
148
|
+
incompatible with the WebSocket protocol, within the bounds of a single HTTP
|
149
|
+
request, for the same reason.)
|
150
|
+
|
151
|
+
|
152
|
+
==== [[Webtube::connect]]: connect to a remote server
|
153
|
+
|
154
|
+
A WebSocket connection to a remote server can be set up by calling
|
155
|
+
[[Webtube::connect]]. The calling interface goes like this:
|
156
|
+
|
157
|
+
Webtube::connect(url,
|
158
|
+
header_fields: {},
|
159
|
+
ssl_verify_mode: nil,
|
160
|
+
on_http_response: nil,
|
161
|
+
allow_rsv_bits: 0,
|
162
|
+
allow_opcodes: [Webtube::OPCODE_TEXT, Webtube::OPCODE_BINARY],
|
163
|
+
close_socket: true)
|
164
|
+
|
165
|
+
- [[url]] is a [[String]] representing the target of the connection in the URL
|
166
|
+
form, using either the [[ws:]] or [[wss:]] protocol prefix. A convenient
|
167
|
+
public server for basic testing is available on [[ws://echo.websocket.org/]].
|
168
|
+
|
169
|
+
- [[header_fields]] is a [[Hash]] for specifying HTTP header fields for the
|
170
|
+
request. [[Webtube]] will consider entries specified here to have a priority
|
171
|
+
over automatically created header fields even for the fields defined by the
|
172
|
+
WebSocket standard such as [[Upgrade]] and [[Sec-WebSocket-Key]]; this should
|
173
|
+
be used cautiously.
|
174
|
+
|
175
|
+
- [[ssl_verify_mode]], if not [[nil]] and if the connection is encrypted, will
|
176
|
+
override OpenSSL's default mode of verifying the certificate at the beginning
|
177
|
+
of the SSL or TLS session. Supported values are
|
178
|
+
[[OpenSSL::SSL::VERIFY_PEER]] (the default, and the recommended value) and
|
179
|
+
[[OpenSSL::SSL::VERIFY_NONE]] (to be used with great caution). If the [[url]]
|
180
|
+
parameter has an [[ws:]] prefix, so the connection is not to be encrypted,
|
181
|
+
[[ssl_verify_mode]] has no effect.
|
182
|
+
|
183
|
+
- [[on_http_response]], if supplied, will be called with the
|
184
|
+
[[Net::HTTPResponse]] instance representing the server's HTTP-level response
|
185
|
+
to the request to initiate a WebSocket connection. This allows the client
|
186
|
+
code, for an example, to display the response header fields to the user.
|
187
|
+
|
188
|
+
The following fields will be passed on to [[Webtube::new]] intact:
|
189
|
+
|
190
|
+
- [[allow_rsv_bits]] is an [[Integer]], a bitmap of the reserved bits (4 for
|
191
|
+
RSV1, 2 for RSV2, 1 for RSV3) that, when appearing on inbound frames, should
|
192
|
+
be considered 'known'. The WebSocket protocol specification mandates failing
|
193
|
+
the connection if a frame with unknown reserved bits should arrive, and
|
194
|
+
[[Webtube]] complies. Note that the current version of [[Webtube]] does not
|
195
|
+
offer a convenient way for the client code to access the reserved bits of data
|
196
|
+
messages, only of control frames.
|
197
|
+
|
198
|
+
- [[allow_opcodes]] specifies the opcodes of messages and control frames that
|
199
|
+
should be considered 'known'. The WebSocket protocol specification mandates
|
200
|
+
failing the connection if a frame with an unknown opcode should arrive, and
|
201
|
+
[[Webtube]] complies. The [[Webtube]] instance will store this object and use
|
202
|
+
its [[include?]] method for the test, thus allowing either [[Array]], [[Set]]
|
203
|
+
or [[Range]] instances to work. The opcodes subject to this kind of filtering
|
204
|
+
are the data message opcodes 1-7 and the control frame opcodes 8-15; note,
|
205
|
+
however, that the control frame opcodes 8 ([[OPCODE_CLOSE]]), 9
|
206
|
+
([[OPCODE_PING]]), and 10 ([[OPCODE_PONG]]) are essential infrastructural
|
207
|
+
opcodes defined by the standard, so [[Webtube]] will always consider them
|
208
|
+
'known'. However, control frames of these opcodes will be passed to the
|
209
|
+
[[oncontrolframe]] event handler (if any) only if [[allow_opcodes]] approves
|
210
|
+
such opcodes.
|
211
|
+
|
212
|
+
- [[close_socket]] specifies whether the [[Webtube]] instance should close the
|
213
|
+
[[Socket]] when the connection is terminated. The default is [[true]]; it
|
214
|
+
may need to be set to [[false]] in order to suppress [[Webtube]]'s closure of
|
215
|
+
its socket in contexts sockets are managed by other means, such as the WEBrick
|
216
|
+
server.
|
217
|
+
|
218
|
+
Upon success, [[Webtube::connect]] will return the [[Webtube]] instance
|
219
|
+
representing the client endpoint. Upon WebSocket-level failure with lower
|
220
|
+
layers intact, a [[Webtube::WebSocketUpgradeFailed]] exception will be thrown
|
221
|
+
instead. This exception derives from [[StandardError]] as most run-time
|
222
|
+
errors. In the the current version of [[Webtube]], the specific known
|
223
|
+
subclasses are:
|
224
|
+
|
225
|
+
- [[Webtube::WebSocketDeclined]] for when the server is not responding to the
|
226
|
+
WebSocket connection initiation request affirmatively (which can, by design
|
227
|
+
of the protocol, mean that the server wants to speak plain old HTTP);
|
228
|
+
|
229
|
+
- [[Webtube::WebSocketVersionMismatch]] for when the server requests usage of a
|
230
|
+
WebSocket protocol version that [[Webtube]] does not know. At the time of
|
231
|
+
[[Webtube]]'s creation, only one WebSocket protocol version -- namely, 13 --
|
232
|
+
has been defined (by RFC 6455]), but this may change in the future, and it is
|
233
|
+
possible that a hypothetical future server will not be backwards compatible to
|
234
|
+
the original WebSocket protocol.
|
235
|
+
|
236
|
+
Other exceptions representing TCP-level, HTTP-level, or infrastructure failures
|
237
|
+
can also occur.
|
238
|
+
|
239
|
+
|
240
|
+
==== [[Webtube::new]]: wrap a [[Socket]] into a [[Webtube]]
|
241
|
+
|
242
|
+
An instance of [[Webtube]] represents an endpoint of a WebSocket connection.
|
243
|
+
Because the protocol's core is symmetric, both ends can be represented by
|
244
|
+
instances of the same class. Note that the constructor assumes the opening
|
245
|
+
handshake and initial negotiation is complete. Code needing to connect to a
|
246
|
+
remote WebSocket server should usually call [[Webtube::connect]] instead of
|
247
|
+
invoking the constructor directly. Code needing to accept incoming WebSocket
|
248
|
+
connections should usually call the server-specific glue code such as
|
249
|
+
[[WEBrick::HTTPServer#accept_webtube]] (available after [[require
|
250
|
+
'webtube/webrick']]).
|
251
|
+
|
252
|
+
The constructor's calling interface goes like this:
|
253
|
+
|
254
|
+
Webtube::new(socket, serverp,
|
255
|
+
allow_rsv_bits: 0,
|
256
|
+
allow_opcodes: [Webtube::OPCODE_TEXT],
|
257
|
+
close_socket: true)
|
258
|
+
|
259
|
+
- [[socket]] is the underlying [[Socket]] instance for sending and receiving
|
260
|
+
data.
|
261
|
+
|
262
|
+
- [[serverp]] is a Boolean indicating whether this socket represents the server
|
263
|
+
(as contrary to the client) side of the connection. While the WebSocket
|
264
|
+
protocol is largely symmetric, it requires a special masking procedure on
|
265
|
+
frames transmitted by the client to the server, and prohibits it on frames
|
266
|
+
transmitted by the server to the client. Along with masking itself, this is
|
267
|
+
reflected in a header flag of each frame.
|
268
|
+
|
269
|
+
- [[allow_rsv_bits]] is an [[Integer]], a bitmap of the reserved bits (4 for
|
270
|
+
RSV1, 2 for RSV2, 1 for RSV3) that, when appearing on inbound frames, should
|
271
|
+
be considered 'known'. The WebSocket protocol specification mandates failing
|
272
|
+
the connection if a frame with unknown reserved bits should arrive, and
|
273
|
+
[[Webtube]] complies. Note that the current version of [[Webtube]] does not
|
274
|
+
offer a convenient way for the client code to access the reserved bits of data
|
275
|
+
messages, only of control frames.
|
276
|
+
|
277
|
+
- [[allow_opcodes]] specifies the opcodes of messages and control frames that
|
278
|
+
should be considered 'known'. The WebSocket protocol specification mandates
|
279
|
+
failing the connection if a frame with an unknown opcode should arrive, and
|
280
|
+
[[Webtube]] complies. The [[Webtube]] instance will store this object and use
|
281
|
+
its [[include?]] method for the test, thus allowing either [[Array]], [[Set]]
|
282
|
+
or [[Range]] instances to work. The opcodes subject to this kind of filtering
|
283
|
+
are the data message opcodes 1-7 and the control frame opcodes 8-15; note,
|
284
|
+
however, that the control frame opcodes 8 ([[OPCODE_CLOSE]]), 9
|
285
|
+
([[OPCODE_PING]]), and 10 ([[OPCODE_PONG]]) are essential infrastructural
|
286
|
+
opcodes defined by the standard, so [[Webtube]] will always consider them
|
287
|
+
'known'. However, control frames of these opcodes will be passed to the
|
288
|
+
[[oncontrolframe]] event handler (if any) only if [[allow_opcodes]] approves
|
289
|
+
such opcodes.
|
290
|
+
|
291
|
+
- [[close_socket]] specifies whether the [[Webtube]] instance should close the
|
292
|
+
[[Socket]] when the connection is terminated. The default is [[true]]; it
|
293
|
+
may need to be set to [[false]] in order to suppress [[Webtube]]'s closure of
|
294
|
+
its socket in contexts sockets are managed by other means, such as the WEBrick
|
295
|
+
server.
|
296
|
+
|
297
|
+
|
298
|
+
==== [[Webtube#run]]: the loop for incoming events
|
299
|
+
|
300
|
+
run(listener)
|
301
|
+
|
302
|
+
This method runs a loop to read all the messages and control frames coming in
|
303
|
+
via this WebSocket, and hands events to the given [[listener]]. The listener
|
304
|
+
can implement the following methods:
|
305
|
+
|
306
|
+
- [[onopen(webtube)]] will be called as soon as the channel is set up.
|
307
|
+
|
308
|
+
- [[onmessage(webtube, message_body, opcode)]] will be called with each
|
309
|
+
arriving data message once it has been defragmented. The data will be passed
|
310
|
+
to it as a [[String]], encoded in [[UTF-8]] for [[OPCODE_TEXT]] messages and
|
311
|
+
in [[ASCII-8BIT]] for all the other message opcodes.
|
312
|
+
|
313
|
+
- oncontrolframe(webtube, frame) will be called upon receipt of a control frame
|
314
|
+
whose opcode is listed in the [[allow_opcodes]] parameter of this [[Webtube]]
|
315
|
+
instance. The frame is repreented by an instance of [[Webtube::Frame]]. Note
|
316
|
+
that [[Webtube]] handles connection closures ([[OPCODE_CLOSE]]) and ponging
|
317
|
+
all the pings ([[OPCODE_PING]]) automatically.
|
318
|
+
|
319
|
+
- [[onping(webtube, frame)]] will be called upon receipt of an [[OPCODE_PING]]
|
320
|
+
frame. [[Webtube]] will take care of ponging all the pings, but the
|
321
|
+
listener may want to process such an event for statistical information.
|
322
|
+
|
323
|
+
- [[onpong(webtube, frame)]] will be called upon receipt of an [[OPCODE_PONG]]
|
324
|
+
frame.
|
325
|
+
|
326
|
+
- [[onclose(webtube)]] will be called upon closure of the connection, for any
|
327
|
+
reason.
|
328
|
+
|
329
|
+
- [[onannoyedclose(webtube, frame)]] will be called upon receipt of an
|
330
|
+
[[OPCODE_CLOSE]] frame with an explicit status code other than 1000.
|
331
|
+
This typically indicates that the other side is annoyed, so the listener
|
332
|
+
may want to log the condition for debugging or further analysis.
|
333
|
+
Normally, once the handler returns, [[Webtube]] will respond with a close
|
334
|
+
frame of the same status code and close the connection, but the handler
|
335
|
+
may call [[Webtube#close]] to request a closure with a different status
|
336
|
+
code or without one.
|
337
|
+
|
338
|
+
- [[onexception(webtube, exception)]] will be called if an unhandled
|
339
|
+
[[Exception]] is raised during the [[Webtube]]'s lifecycle, including all of
|
340
|
+
the listener event handlers. The handler may log the exception but should
|
341
|
+
return normally so that the [[Webtube]] can issue a proper close frame for
|
342
|
+
the other end and invoke the [[onclose]] handler, after which the exception
|
343
|
+
will be raised again so the caller of [[Webtube#run]] will have a chance of
|
344
|
+
handling the exception.
|
345
|
+
|
346
|
+
Before calling any of the handlers, [[respond_to?]] will be used to check
|
347
|
+
implementedness.
|
348
|
+
|
349
|
+
If an exception occurs during processing, it may implement a specific
|
350
|
+
status code to be passed to the other end via the [[OPCODE_CLOSE]] frame by
|
351
|
+
implementing the [[websocket_close_status_code]] method returning the code
|
352
|
+
as an integer. The default code, used if the exception does not specify
|
353
|
+
one, is 1011 'unexpected condition'. An exception may explicitly suppress
|
354
|
+
sending any code by having [[websocket_close_status_code]] return [[nil]]
|
355
|
+
instead of an integer.
|
356
|
+
|
357
|
+
Note that [[Webtube#run]] will not return until the connection will have been
|
358
|
+
closed. If the caller needs to get other work done in the connection's
|
359
|
+
lifetime, it will need to either handle this work inside calls to the
|
360
|
+
[[listener]] or set up separate [[Thread]]s for the [[Webtube#run]] and for the
|
361
|
+
other work.
|
362
|
+
|
363
|
+
[[Webtube#run]] will raise instances of [[Webtube::ProtocolError]] if events
|
364
|
+
that the WebSocket protocol does not permit should happen. In the current
|
365
|
+
version of [[Webtube]], the specific known subclasses are:
|
366
|
+
|
367
|
+
- [[Webtube::BrokenFrame]] indicates that a complete frame could not be read
|
368
|
+
from the underlying TCP connection. The [[partial_frame]] attribute holds as
|
369
|
+
much data, as a [[String]] of [[ASCII-8BIT]] encoding, as was available.
|
370
|
+
|
371
|
+
- [[Webtube::UnknownReservedBit]] indicates that a frame with an RSV bit not
|
372
|
+
specifically permitted by the [[allow_rsv_bits]] parameter was received. The
|
373
|
+
[[frame]] attribute holds the frame as a [[Webtube::Frame]] instance.
|
374
|
+
|
375
|
+
- [[Webtube::UnknownOpcode]] indicates that a frame with an opcode not
|
376
|
+
specifically permitted by the [[allow_opcodes]] parameter, or by the standard,
|
377
|
+
was received. The [[frame]] attribute holds the frame as a [[Webtube::Frame]]
|
378
|
+
instance. Note that if the opcode indicates a data message (as contrary to a
|
379
|
+
control frame), the [[frame]] will hold only its first fragment, as WebSocket
|
380
|
+
data messages are subject to fragmentation and the message's opcode is stored
|
381
|
+
in the first fragment.
|
382
|
+
|
383
|
+
- [[Webtube::UnmaskedFrameToServer]] indicates that a [[Webtube]] running in
|
384
|
+
server mode received a frame without masking. As per the WebSocket standard,
|
385
|
+
[[Webtube]] considers this a fatal protocol failure. The [[frame]] attribute
|
386
|
+
holds the frame as a [[Webtube::Frame]] instance.
|
387
|
+
|
388
|
+
- [[Webtube::MaskedFrameToClient]] indicates that a [[Webtube]] running in
|
389
|
+
client mode received a frame with masking. As per the WebSocket standard,
|
390
|
+
[[Webtube]] considers this a fatal protocol failure. The [[frame]] attribute
|
391
|
+
holds the frame as a [[Webtube::Frame]] instance.
|
392
|
+
|
393
|
+
- [[Webtube::MissingContinuationFrame]] indicates receipt of a new data message
|
394
|
+
initial frame while the [[Webtube]] was expecting a continuation frame of a
|
395
|
+
fragmented data message. Note that control frames are permitted to arrive
|
396
|
+
interleaved with fragments of a data message.
|
397
|
+
|
398
|
+
- [[Webtube::UnexpectedContinuationFrame]] indicates receipt of a data message
|
399
|
+
continuation frame while the [[Webtube]] was not expecting one. The [[frame]]
|
400
|
+
attribute holds the frame as a [[Webtube::Frame]] instance.
|
401
|
+
|
402
|
+
- [[Webtube::BadlyEncodedText]] indicates receipt of a text message
|
403
|
+
([[OPCODE_TEXT]]) whose content is not a valid UTF-8 string. As per the
|
404
|
+
WebSocket standard, [[Webtube]] considers this a fatal protocol failure. The
|
405
|
+
[[data]] attribute holds the payload as a [[String]] instance of the
|
406
|
+
[[ASCII-8BIT]] encoding.
|
407
|
+
|
408
|
+
- [[Webtube::FragmenedControlFrame]] indicates receipt of a control frame whose
|
409
|
+
[[FIN]] flag is not set.
|
410
|
+
|
411
|
+
Other exceptions representing TCP-level, HTTP-level, or infrastructure failures
|
412
|
+
can also occur.
|
413
|
+
|
414
|
+
|
415
|
+
==== [[Webtube#send_message]]: transmit a message or control frame
|
416
|
+
|
417
|
+
send_message(payload, opcode = Webtube::OPCODE_TEXT)
|
418
|
+
|
419
|
+
This method transmits the given [[payload]], a [[String]], over this WebSocket
|
420
|
+
connection to its other end using the given [[opcode]]. If [[opcode]] is
|
421
|
+
[[Webtube::OPCODE_TEXT]] and [[payload]] is not encoded in [[UTF-8]], it will
|
422
|
+
recode the payload to [[UTF-8]] first, as required by the WebSocket standard.
|
423
|
+
|
424
|
+
It is safe to call [[send_message]] from multiple threads concurrently; each
|
425
|
+
[[Webtube]] uses an internal lock to make sure that two data messages, despite
|
426
|
+
possible fragmentation, will not be interleaved.
|
427
|
+
|
428
|
+
An exception will be raised if [[send_message]] is called after closure of the
|
429
|
+
[[Webtube]]. The exception's class and ancestry is currently not defined,
|
430
|
+
except that it will derive, directly or indirectly, from [[StandardError]]. It
|
431
|
+
may derive from [[RuntimeError]] but this is not guaranteed.
|
432
|
+
|
433
|
+
|
434
|
+
==== [[Webtube#close]]: close a WebSocket connection
|
435
|
+
|
436
|
+
close(status_code = 1000, explanation = "")
|
437
|
+
|
438
|
+
This method transmits an [[OPCODE_CLOSE]] control frame of the specified
|
439
|
+
[[status_code]] and [[explanation]], aborts a pending wait to receive frame (if
|
440
|
+
any), and marks the [[Webtube]] dead, thus blocking further transmissions. If
|
441
|
+
the [[close_socket]] parameter of the [[Webtube]] is set, it will also close
|
442
|
+
the underlying socket.
|
443
|
+
|
444
|
+
The [[status_code]] can be explicitly set to [[nil]], thus causing the
|
445
|
+
transmitted close frame to not contain a payload (that is, neither the status
|
446
|
+
code nor the explanation). The default is 1000, indicating normal closure.
|
447
|
+
|
448
|
+
If [[explanation]] is not encoded in UTF-8, it will be recoded, as required by
|
449
|
+
the WebSocket protocol specification.
|
450
|
+
|
451
|
+
Attempting to close a [[Webtube]] that has already been closed will cause an
|
452
|
+
exception as attempting to transmit via a closed [[Webtube]]; see
|
453
|
+
[[Webtube#send_message]].
|
454
|
+
|
455
|
+
|
456
|
+
=== The [[Webtube::Frame]] class
|
457
|
+
|
458
|
+
Instances of this class represent individual WebSocket frames. They are
|
459
|
+
exposed to user code via the [[oncontrolframe]], [[onping]], and [[onpong]]
|
460
|
+
events and some exceptions inheriting from [[ProtocolError]].
|
461
|
+
|
462
|
+
There is currently no convenient interface for user code to build
|
463
|
+
[[Webtube::Frame]] instances by hand, but manipulating some header fields may
|
464
|
+
function as expected. (Manipulating the payload will usually not, due to this
|
465
|
+
interfering with the length- and masking-related header fields.)
|
466
|
+
|
467
|
+
The following methods are user-serviceable:
|
468
|
+
|
469
|
+
- [[Webtube::Frame#header]] returns the header as a [[String]] encoded in
|
470
|
+
[[ASCII-8BIT]].
|
471
|
+
|
472
|
+
- [[Webtube::Frame#header=]] replaces the header. There is no validation; use
|
473
|
+
with caution.
|
474
|
+
|
475
|
+
- [[Webtube::Frame#body]] returns the body as a [[String]] encoded in
|
476
|
+
[[ASCII-8BIT]].
|
477
|
+
|
478
|
+
- [[Webtube::Frame#body=]] replaces the body. There is no validation or
|
479
|
+
masking; use with extreme caution.
|
480
|
+
|
481
|
+
- [[Webtube::Frame#fin?]] extracts and returns (as [[Boolean]]) the [[FIN]]
|
482
|
+
flag of this frame.
|
483
|
+
|
484
|
+
- [[Webtube::Frame#fin=]] replaces the [[FIN]] flag of this frame.
|
485
|
+
|
486
|
+
- [[Webtube::Frame#rsv1]], [[Webtube::Frame#rsv2]], and [[Webtube::Frame#rsv3]]
|
487
|
+
extract the RSV1, RSV2, and RSV3 flags of this frame, as [[Boolean]]
|
488
|
+
instances, correspondingly.
|
489
|
+
|
490
|
+
- [[Webtube::Frame#rsv]] extracts the RSV1, RSV2, and RSV3 bitfield as an
|
491
|
+
integer in the range of [[0 .. 7]].
|
492
|
+
|
493
|
+
- [[Webtube::Frame#opcode]] extracts the opcode of this frame as an integer in
|
494
|
+
the range of [[0 .. 15]].
|
495
|
+
|
496
|
+
- [[Webtube::Frame#opcode=]] replaces the opcode.
|
497
|
+
|
498
|
+
- [[Webtube::Frame#control_frame?]] checks whether this frame is considered a
|
499
|
+
control frame, defined as having an opcode of 8 or greater.
|
500
|
+
|
501
|
+
- [[Webtube::Frame#masked?]] extracts the [[MSK]] flag of this frame.
|
502
|
+
|
503
|
+
- [[Webtube::Frame#payload_length]] extracts the payload length, in whichever
|
504
|
+
of the three ways defined by the protocol specification it is encoded, from
|
505
|
+
the frame's header.
|
506
|
+
|
507
|
+
- [[Webtube::Frame#mask]] extracts the mask of this frame, as a 4-byte
|
508
|
+
[[String]] encoded in [[ASCII-8BIT]], if the [[MSK]] flag is not set. If it
|
509
|
+
is not set, this method returns [[nil]].
|
510
|
+
|
511
|
+
- [[Webtube::Frame#payload]] retrieves the payload of this frame, demasking the
|
512
|
+
frame's body if necessary.
|
513
|
+
|
514
|
+
- [[Webtube::Frame::read_from_socket(socket)]] reads all the bytes of one
|
515
|
+
WebSocket frame from the given [[IO]] instance (which must provide data of
|
516
|
+
the plain [[ASCII-8BIT]] encoding, and emphatically not a multibyte encoding)
|
517
|
+
and returns a [[Webtube::Frame]] instance representing the frame, or raises
|
518
|
+
[[Webtube::BrokenFrame]] if the inbound traffic ends before the whole frame
|
519
|
+
will have been read. Note that this will involve calling [[IO#read]] twice
|
520
|
+
or thrice, and is therefore unsafe to be called in multithreaded code unless
|
521
|
+
external locking or synchronisation measures are used. (This method is
|
522
|
+
mainly intended for internal use by [[Webtube#run]], but it may be of use in
|
523
|
+
other contexts, such as parsing stored sequences of WebSocket frames.)
|
524
|
+
|
525
|
+
|
526
|
+
=== WEBrick integration
|
527
|
+
|
528
|
+
These classes and methods are defined in the separately loadable
|
529
|
+
[['webtube/webrick']]. Note that this file will, in addition to defining new
|
530
|
+
classes and methods, replace the [[initialize]] and [[shutdown]] methods of the
|
531
|
+
[[WEBrick::HTTPServer]] class to make sure all the [[Webtube]] instances
|
532
|
+
associated with this server will be properly shut down upon the server's
|
533
|
+
shutdown.
|
534
|
+
|
535
|
+
|
536
|
+
==== [[WEBrick::HTTPRequest#websocket_upgrade_request?]]
|
537
|
+
|
538
|
+
This method checks whether this HTTP request is a valid request to establish a
|
539
|
+
WebSocket connection.
|
540
|
+
|
541
|
+
|
542
|
+
==== [[WEBrick::HTTPServer#webtubes]]
|
543
|
+
|
544
|
+
Retrieve the [[Webtube::Vital_Statistics]] instance for this server.
|
545
|
+
|
546
|
+
==== [[WEBrick::HTTPServer#accept_webtube]]
|
547
|
+
|
548
|
+
accept_webtube(request, response, listener,
|
549
|
+
session: nil,
|
550
|
+
context: nil)
|
551
|
+
|
552
|
+
Given a [[request]] and a [[response]] object, as prepared by a
|
553
|
+
[[WEBrick::HTTPServer]] for processing in a portlet, this method attempts to
|
554
|
+
accept the client's request to establish a WebSocket connection. The
|
555
|
+
[[request]] must actually contain such a request; see
|
556
|
+
[[websocket_upgrade_request?]].
|
557
|
+
|
558
|
+
The attempt will fail in the theoretical case the client and the server can't
|
559
|
+
agree on the protocol version to use. In such a case, [[accept_webtube]] will
|
560
|
+
prepare a 426 'Upgrade required' response, explaining in plain text what the
|
561
|
+
problem is and advertising, using the [[Sec-WebSocket-Version]] header field,
|
562
|
+
the protocol version (specifically, 13) it is prepared to speak. When this
|
563
|
+
happens, the WebSocket session will never be set up and no [[listener]] events
|
564
|
+
will be called.
|
565
|
+
|
566
|
+
Note that [[accept_webtube]] will manipulate [[response]] and return
|
567
|
+
immediately. The actual WebSocket session will begin once WEBrick attempts to
|
568
|
+
deliver the [[response]], and this will be signalled by the newly constructed
|
569
|
+
[[Webtube]] instance delivering an [[onopen]] event to [[listener]].
|
570
|
+
|
571
|
+
Also note that the loop to process incoming WebSocket frames will hog the
|
572
|
+
whole thread; in order to deliver asynchronous messages over the
|
573
|
+
WebSocket, [[Webtube#send_message]] needs to be called from another
|
574
|
+
thread. (For synchronous messages, it can safely be called from the
|
575
|
+
handlers inside [[listener]].)
|
576
|
+
|
577
|
+
See [[Webtube#run]] for a list of the supported methods for the
|
578
|
+
[[listener]].
|
579
|
+
|
580
|
+
The [[session]] and [[context]] parameters, if given, will be stored in the
|
581
|
+
[[Webtube]] instance as attributes. The [[Webtube]] itself will not care about
|
582
|
+
them, but this mechanism may be of use for the user code. [[accept_webtube]]
|
583
|
+
stores the [[request]] in the [[Webtube]] instance's [[header]] attribute; for
|
584
|
+
this reason, it does not accept [[header]] as a parameter.
|
585
|
+
|
586
|
+
==== [[WEBrick::HTTPServer#mount_webtube]]
|
587
|
+
|
588
|
+
mount_webtube(dir, listener)
|
589
|
+
|
590
|
+
This method mounts at the specified virtual directory a WEBrick portlet
|
591
|
+
implementing a WebSocket-only service, using the given [[listener]] as its
|
592
|
+
backend. (Note that there is only one listener for the whole service but each
|
593
|
+
event passed to the listener will have a specific [[Webtube]] instance as its
|
594
|
+
first parameter.)
|
595
|
+
|
596
|
+
The portlet itself is implemented by the class
|
597
|
+
[[WEBrick::HTTPServlet::WebtubeHandler]]. The implementation details are
|
598
|
+
deliberately left undocumented in the current version of [[Webtube]], and they
|
599
|
+
may change radically in the future. For now, the class should be considered
|
600
|
+
opaque.
|
601
|
+
|
602
|
+
|
603
|
+
== Limitations and possible future work
|
604
|
+
|
605
|
+
- The WebSocket specification permits interleaving control frames with
|
606
|
+
fragments of a data message. The current [[Webtube]] implementation engages
|
607
|
+
an internal lock serialising all calls of [[send_message]]. A future version
|
608
|
+
may ignore this lock if [[send_message]] is called to transmit a control frame
|
609
|
+
that fits into the [[PIPE_BUF]] limit and is thus not subject to the risk of
|
610
|
+
the OS kernel's [[write()]] syscall handling it partially and causing a
|
611
|
+
WebSocket frame structure breakage.
|
612
|
+
|
613
|
+
- Such ignoring may, in a future version, be configurable on a per-opcode
|
614
|
+
basis.
|
615
|
+
|
616
|
+
- The WebSocket specification provides for handling the content of fragmented
|
617
|
+
data messages even before the reception of the final frame. The current
|
618
|
+
[[Webtube]] implementation sponges up all the fragments before triggering
|
619
|
+
[[onmessage]].
|
620
|
+
|
621
|
+
- Some approaches worth considering involve delivering fragments of data
|
622
|
+
messages to the listener as they arrive or extracting parts -- such as text
|
623
|
+
lines, sequences of complete UTF-8 code points, or fixed-length data blocks
|
624
|
+
-- from the fragment sponge as soon as they can be wholly extracted.
|
625
|
+
|
626
|
+
- The WebSocket specification provides for extensions defining semantics for
|
627
|
+
the reserved bits of both data and control frames, and for extra header fields
|
628
|
+
between the WebSocket header and the ultimate payload. [[Webtube]] only
|
629
|
+
provides for handling reserved bits of incoming control frames but not data
|
630
|
+
frames, and does not provide for a convenient way to transmit frames with
|
631
|
+
reserved bits or extra header fields set.
|
632
|
+
|
633
|
+
- In particular, work is currently underway to define a WebSocket protocol
|
634
|
+
extension for transparent compression of frames and/or messages. At this
|
635
|
+
time, there are multiple competing proposals, and IETF has not released a
|
636
|
+
final specification, but a future version of [[Webtube]] may implement one
|
637
|
+
or more such extension. The most promising one, at this time, seems
|
638
|
+
[[permessage-deflate]].
|
639
|
+
|
640
|
+
- The WebSocket specification provides for explicit negotiation of a
|
641
|
+
subprotocol between the client and the server. While [[Webtube]] exposes the
|
642
|
+
relevant HTTP header field ([[Sec-WebSocket-Protocol]]) to client-side user
|
643
|
+
code, it does not provide any sort of direct support, and explicitly
|
644
|
+
supporting subprotocols on the server side may be cumbersome. A future
|
645
|
+
version of [[Webtube]] may provide a declarative way for configuring the
|
646
|
+
subprotocol negotiation: more explicitly expose the subprotocol field on the
|
647
|
+
client-side API, and providing parameters for declaring the supported
|
648
|
+
subprotocols and their order of precedence, or alternatively a hook to a more
|
649
|
+
complex subprotocol choie mechanism, on the server-side API.
|
650
|
+
|
651
|
+
- On the server side, [[Webtube]] currently only actively integrates with
|
652
|
+
WEBrick. A future version may also provide support for integration with
|
653
|
+
Puma, Sinatra, and/or EventMachine.
|
654
|
+
|
655
|
+
- A future version of [[Webtube]] may provide an interface for explicitly
|
656
|
+
specifying the fragmentation strategy for outbound data messages instead of
|
657
|
+
relying on a one-size-fits-all [[PIPE_BUF]] bases approach.
|
658
|
+
|
659
|
+
- In particular, on systems exposing the results of Path MTU Discovery of
|
660
|
+
connected TCP sockets to userspace code, a future version of [[Webtube]]
|
661
|
+
may use these results to choose a message fragment size according to the
|
662
|
+
path's MTU. (This will become nontrivial once compression and SSL get
|
663
|
+
involved.)
|
664
|
+
|
665
|
+
- Currently, [[Webtube#run]] necessarily hogs its whole thread until the
|
666
|
+
connection closes. A future version may, as an alternative, provide a more
|
667
|
+
incremental approach. Some of the partially overlapping and partially
|
668
|
+
alternative approaches worth considering include:
|
669
|
+
|
670
|
+
- a [[receive_message]] method that would hang until a message arrives (buth
|
671
|
+
how would it interact with control frames? Particularly, control frames
|
672
|
+
not defined by the standard?);
|
673
|
+
|
674
|
+
- an option for [[run]] to leave the loop after processing one incoming frame
|
675
|
+
or message;
|
676
|
+
|
677
|
+
- an option for [[run]] to leave the loop after passage of a given timeout;
|
678
|
+
|
679
|
+
- a non-blocking [[has_data?]] method that would check whether the underlying
|
680
|
+
[[Socket]] has at last one byte of data available;
|
681
|
+
|
682
|
+
- a non-blocking [[has_frame?]] method that would check whether the
|
683
|
+
underlying [[Socket]] has at least on complete WebSocket frame available
|
684
|
+
(if not, this would require storing the partial frame in a slot of
|
685
|
+
[[Webtube]] instead of a local variable of [[run]]).
|
686
|
+
|
687
|
+
- The HTTP specification provides a mechanism for redirecting clients. It is
|
688
|
+
not entirely clear how this should affect WebSocket clients, although there
|
689
|
+
are some obvious approaches. A future version of [[Webtube::connect]] may
|
690
|
+
implement one or more of them.
|
691
|
+
|
692
|
+
- A future version of the client API for [[Webtube]] may support transparent
|
693
|
+
automatic reconnection upon loss of connection while retaining the same
|
694
|
+
instance, subject to restrictions for thrashing and persisting failure. This
|
695
|
+
may need lead to defining new events for the listener.
|
696
|
+
|
697
|
+
- A future version of [[Webtube]] may provide for a higher-level interface, for
|
698
|
+
example, by transparently JSON-encoding and -decoding objects as they are
|
699
|
+
transmitted and received.
|
700
|
+
|
701
|
+
- A future version of [[Webtube]] may implement a 'queue of unhandled messages'
|
702
|
+
inside the [[Webtube]] instance (or more likely, inside an instance of its
|
703
|
+
subclass), define a mechanism (or several) for matching outbound and inbound
|
704
|
+
messages, and provide for a synchronous method that would transmit a message
|
705
|
+
and wait until receipt of a matching response, storing messages arriving in
|
706
|
+
the intervening time for use by a future call of this method, or by
|
707
|
+
concurrent calls of this method from other threads.
|
708
|
+
|
709
|
+
- A future version of [[Webtube]] may define a hook for the caller to manually
|
710
|
+
check the SSL certificate so as to facilitate secure SSL/TLS connections
|
711
|
+
using self-signed certificates validated using a custom procedure instead of
|
712
|
+
relying to a 'trusted third party' CA.
|
713
|
+
|
714
|
+
- Also, or alternatively, it may expose [[OpenSSL]]'s certificate
|
715
|
+
verification hooks.
|
716
|
+
|
717
|
+
- A future version of [[Webtube::connect]] may explicitly support using a
|
718
|
+
client SSL certificate.
|
719
|
+
|
720
|
+
- A future version of [[Webtube::connect]] may expose the proxy configuration
|
721
|
+
subsystem of [[Net::HTTP]].
|
722
|
+
|
723
|
+
- The current implementation of [[Webtube#send_message]] uses a string (and
|
724
|
+
thus, implicitly, [[RuntimeError]]), rather than an explicit subclass of
|
725
|
+
[[Exception]], to report attempt to transmit data through a dead WebSocket.
|
726
|
+
A future version of [[Webtube]] is likely to provide such an explicit
|
727
|
+
subclass of defined ancestry. It is not currently clear whether this should
|
728
|
+
inherit from [[Webtube::ProtocolError]]; arguments both ways are conceivable.
|
729
|
+
|
730
|
+
- A future version of [[Webtube]] may offer a better differentiation between
|
731
|
+
reasons of a WebSocket's closure.
|
732
|
+
|
733
|
+
- A future version of [[Webtube]] may perhaps define listener event(s) for
|
734
|
+
outbound messages as well as inbound ones.
|
735
|
+
|
736
|
+
- A future version of [[Webtube]] may define parameters for setting the
|
737
|
+
[[IP_TOS]] and [[IP_TTL]] socket options.
|
738
|
+
|
739
|
+
- The WebSocket protocol specification has a strict rule against multiple
|
740
|
+
WebSocket connections between the same pair of endpoints being simultaneously
|
741
|
+
in the /connecting/ state. [[Webtube]] is currently not implementing or
|
742
|
+
enforcing this. A future version may provide a serialisation mechanism.
|
743
|
+
|
744
|
+
- A future version of [[Webtube]] may provide an explicit mechanism for
|
745
|
+
transmitting hand-crafted [[Frame]] instances.
|
746
|
+
|
747
|
+
- This will probably need a better abstraction for frame masking.
|
748
|
+
|
749
|
+
- A future version of [[Webtube]] may provide a standalone WebSocket server
|
750
|
+
implementing only the minimal needed amount of HTTP/1.1.
|
751
|
+
|
752
|
+
- This will need a mechanism for [[Webtube]] to validate the [[Origin]]
|
753
|
+
header field in the request. This is currently not implemented (but may be
|
754
|
+
implemented in a future version.)
|
755
|
+
|
756
|
+
|
757
|
+
== Copyright and licensing
|
758
|
+
|
759
|
+
Webtube is copyright (c) 2014 by Andres Soolo and Knitten Development OÜ.
|
760
|
+
Webtube is published as free software under the terms and conditions of the
|
761
|
+
GNU General Public License version 3.
|
762
|
+
|
763
|
+
It is the default policy of us at Knitten Development to release our free
|
764
|
+
software under the GPL v3, which we believe provides a reasonable and
|
765
|
+
well-considered, if somewhat conservative, balance between the interests and
|
766
|
+
concerns of producers, consumers, and prosumers of free software. However, we
|
767
|
+
realise that some users of our free software may be better served by other
|
768
|
+
balances. For this reason, Knitten Development would like it be known that:
|
769
|
+
|
770
|
+
- we are willing to consider, on a case-by-case basis, offering packages of our
|
771
|
+
free software optionally also under certain other open source software
|
772
|
+
licenses, as certified by the Open Source Initiative(tm), provided that this
|
773
|
+
furthers the creation or advancement of specific free software projects that
|
774
|
+
Knitten Development may find worthwile, at our discretion; and
|
775
|
+
|
776
|
+
- we are available to negotiate standard non-exclusive commercial licenses for
|
777
|
+
free software that we have released, in exchange for a reasonable fee.
|
778
|
+
|
779
|
+
For any enquiries, please write to <licensing@knitten-dev.co.uk>.
|