webtube 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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>.
|