sonixlabs-em-websocket 0.3.8 → 0.5.1.1
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/CHANGELOG.rdoc +69 -0
- data/Gemfile +6 -0
- data/LICENCE +7 -0
- data/README.md +100 -56
- data/README.md.BACKUP.14928.md +195 -0
- data/README.md.BASE.14928.md +77 -0
- data/README.md.LOCAL.14928.md +98 -0
- data/README.md.REMOTE.14928.md +142 -0
- data/examples/echo.rb +23 -7
- data/examples/ping.rb +24 -0
- data/examples/test.html +5 -6
- data/lib/em-websocket.rb +4 -2
- data/lib/em-websocket/close03.rb +3 -0
- data/lib/em-websocket/close05.rb +3 -0
- data/lib/em-websocket/close06.rb +3 -0
- data/lib/em-websocket/close75.rb +2 -1
- data/lib/em-websocket/connection.rb +219 -73
- data/lib/em-websocket/framing03.rb +6 -11
- data/lib/em-websocket/framing05.rb +6 -11
- data/lib/em-websocket/framing07.rb +25 -20
- data/lib/em-websocket/framing76.rb +6 -15
- data/lib/em-websocket/handler.rb +69 -28
- data/lib/em-websocket/handler03.rb +0 -1
- data/lib/em-websocket/handler05.rb +0 -1
- data/lib/em-websocket/handler06.rb +0 -1
- data/lib/em-websocket/handler07.rb +0 -1
- data/lib/em-websocket/handler08.rb +0 -1
- data/lib/em-websocket/handler13.rb +0 -1
- data/lib/em-websocket/handler76.rb +2 -0
- data/lib/em-websocket/handshake.rb +156 -0
- data/lib/em-websocket/handshake04.rb +18 -56
- data/lib/em-websocket/handshake75.rb +15 -8
- data/lib/em-websocket/handshake76.rb +15 -14
- data/lib/em-websocket/masking04.rb +4 -30
- data/lib/em-websocket/message_processor_03.rb +13 -4
- data/lib/em-websocket/message_processor_06.rb +25 -13
- data/lib/em-websocket/version.rb +1 -1
- data/lib/em-websocket/websocket.rb +35 -24
- data/spec/helper.rb +82 -55
- data/spec/integration/common_spec.rb +90 -70
- data/spec/integration/draft03_spec.rb +84 -56
- data/spec/integration/draft05_spec.rb +14 -12
- data/spec/integration/draft06_spec.rb +66 -9
- data/spec/integration/draft13_spec.rb +59 -29
- data/spec/integration/draft75_spec.rb +46 -40
- data/spec/integration/draft76_spec.rb +113 -109
- data/spec/integration/gte_03_examples.rb +42 -0
- data/spec/integration/shared_examples.rb +174 -0
- data/spec/unit/framing_spec.rb +83 -110
- data/spec/unit/handshake_spec.rb +216 -0
- data/spec/unit/masking_spec.rb +2 -0
- metadata +31 -71
- data/examples/flash_policy_file_server.rb +0 -21
- data/examples/js/FABridge.js +0 -604
- data/examples/js/WebSocketMain.swf +0 -0
- data/examples/js/swfobject.js +0 -4
- data/examples/js/web_socket.js +0 -312
- data/lib/em-websocket/handler_factory.rb +0 -107
- data/spec/unit/handler_spec.rb +0 -147
data/lib/em-websocket/close06.rb
CHANGED
data/lib/em-websocket/close75.rb
CHANGED
@@ -1,33 +1,39 @@
|
|
1
|
-
require 'addressable/uri'
|
2
|
-
|
3
1
|
module EventMachine
|
4
2
|
module WebSocket
|
5
3
|
class Connection < EventMachine::Connection
|
6
4
|
include Debugger
|
7
5
|
|
6
|
+
attr_writer :max_frame_size
|
7
|
+
|
8
8
|
# define WebSocket callbacks
|
9
9
|
def onopen(&blk); @onopen = blk; end
|
10
10
|
def onclose(&blk); @onclose = blk; end
|
11
11
|
def onerror(&blk); @onerror = blk; end
|
12
12
|
def onmessage(&blk); @onmessage = blk; end
|
13
|
+
def onbinary(&blk); @onbinary = blk; end
|
14
|
+
def onping(&blk); @onping = blk; end
|
15
|
+
def onpong(&blk); @onpong = blk; end
|
13
16
|
|
14
|
-
def trigger_on_message(msg
|
15
|
-
if @onmessage
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
@onmessage.call msg
|
20
|
-
end
|
21
|
-
end
|
17
|
+
def trigger_on_message(msg)
|
18
|
+
@onmessage.call(msg) if defined? @onmessage
|
19
|
+
end
|
20
|
+
def trigger_on_binary(msg)
|
21
|
+
@onbinary.call(msg) if defined? @onbinary
|
22
22
|
end
|
23
|
-
def trigger_on_open
|
24
|
-
@onopen.call if @onopen
|
23
|
+
def trigger_on_open(handshake)
|
24
|
+
@onopen.call(handshake) if defined? @onopen
|
25
25
|
end
|
26
|
-
def trigger_on_close
|
27
|
-
@onclose.call if @onclose
|
26
|
+
def trigger_on_close(event = {})
|
27
|
+
@onclose.call(event) if defined? @onclose
|
28
|
+
end
|
29
|
+
def trigger_on_ping(data)
|
30
|
+
@onping.call(data) if defined? @onping
|
31
|
+
end
|
32
|
+
def trigger_on_pong(data)
|
33
|
+
@onpong.call(data) if defined? @onpong
|
28
34
|
end
|
29
35
|
def trigger_on_error(reason)
|
30
|
-
return false unless @onerror
|
36
|
+
return false unless defined? @onerror
|
31
37
|
@onerror.call(reason)
|
32
38
|
true
|
33
39
|
end
|
@@ -36,8 +42,12 @@ module EventMachine
|
|
36
42
|
@options = options
|
37
43
|
@debug = options[:debug] || false
|
38
44
|
@secure = options[:secure] || false
|
45
|
+
@secure_proxy = options[:secure_proxy] || false
|
39
46
|
@tls_options = options[:tls_options] || {}
|
40
|
-
@
|
47
|
+
@close_timeout = options[:close_timeout]
|
48
|
+
@outbound_limit = options[:outbound_limit] || 0
|
49
|
+
|
50
|
+
@handler = nil
|
41
51
|
|
42
52
|
debug [:initialize]
|
43
53
|
end
|
@@ -45,17 +55,17 @@ module EventMachine
|
|
45
55
|
# Use this method to close the websocket connection cleanly
|
46
56
|
# This sends a close frame and waits for acknowlegement before closing
|
47
57
|
# the connection
|
48
|
-
def
|
49
|
-
if code && !
|
50
|
-
raise "Application code may only use codes
|
58
|
+
def close(code = nil, body = nil)
|
59
|
+
if code && !acceptable_close_code?(code)
|
60
|
+
raise "Application code may only use codes from 1000, 3000-4999"
|
51
61
|
end
|
52
62
|
|
53
|
-
# If code not defined then set to 1000 (normal closure)
|
54
|
-
code ||= 1000
|
55
|
-
|
56
63
|
close_websocket_private(code, body)
|
57
64
|
end
|
58
65
|
|
66
|
+
# Deprecated, to be removed in version 0.6
|
67
|
+
alias :close_websocket :close
|
68
|
+
|
59
69
|
def post_init
|
60
70
|
start_tls(@tls_options) if @secure
|
61
71
|
end
|
@@ -63,27 +73,30 @@ module EventMachine
|
|
63
73
|
def receive_data(data)
|
64
74
|
debug [:receive_data, data]
|
65
75
|
|
66
|
-
if @handler
|
76
|
+
if @handler
|
67
77
|
@handler.receive_data(data)
|
68
78
|
else
|
69
79
|
dispatch(data)
|
70
80
|
end
|
71
|
-
rescue HandshakeError => e
|
72
|
-
debug [:error, e]
|
73
|
-
trigger_on_error(e)
|
74
|
-
# Errors during the handshake require the connection to be aborted
|
75
|
-
abort
|
76
|
-
rescue WebSocketError => e
|
77
|
-
debug [:error, e]
|
78
|
-
trigger_on_error(e)
|
79
|
-
close_websocket_private(1002) # 1002 indicates a protocol error
|
80
81
|
rescue => e
|
81
82
|
debug [:error, e]
|
82
|
-
|
83
|
-
trigger_on_error(e) || raise(e)
|
83
|
+
|
84
84
|
# There is no code defined for application errors, so use 3000
|
85
85
|
# (which is reserved for frameworks)
|
86
|
-
close_websocket_private(3000)
|
86
|
+
close_websocket_private(3000, "Application error")
|
87
|
+
|
88
|
+
# These are application errors - raise unless onerror defined
|
89
|
+
trigger_on_error(e) || raise(e)
|
90
|
+
end
|
91
|
+
|
92
|
+
def send_data(data)
|
93
|
+
if @outbound_limit > 0 &&
|
94
|
+
get_outbound_data_size + data.bytesize > @outbound_limit
|
95
|
+
abort(:outbound_limit_reached)
|
96
|
+
return 0
|
97
|
+
end
|
98
|
+
|
99
|
+
super(data)
|
87
100
|
end
|
88
101
|
|
89
102
|
def unbind
|
@@ -99,18 +112,30 @@ module EventMachine
|
|
99
112
|
def dispatch(data)
|
100
113
|
if data.match(/\A<policy-file-request\s*\/>/)
|
101
114
|
send_flash_cross_domain_file
|
102
|
-
return false
|
103
115
|
else
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
116
|
+
@handshake ||= begin
|
117
|
+
handshake = Handshake.new(@secure || @secure_proxy)
|
118
|
+
|
119
|
+
handshake.callback { |upgrade_response, handler_klass|
|
120
|
+
debug [:accepting_ws_version, handshake.protocol_version]
|
121
|
+
debug [:upgrade_response, upgrade_response]
|
122
|
+
self.send_data(upgrade_response)
|
123
|
+
@handler = handler_klass.new(self, @debug)
|
124
|
+
@handshake = nil
|
125
|
+
trigger_on_open(handshake)
|
126
|
+
}
|
127
|
+
|
128
|
+
handshake.errback { |e|
|
129
|
+
debug [:error, e]
|
130
|
+
trigger_on_error(e)
|
131
|
+
# Handshake errors require the connection to be aborted
|
132
|
+
abort(:handshake_error)
|
133
|
+
}
|
134
|
+
|
135
|
+
handshake
|
110
136
|
end
|
111
|
-
|
112
|
-
@
|
113
|
-
return true
|
137
|
+
|
138
|
+
@handshake.receive_data(data)
|
114
139
|
end
|
115
140
|
end
|
116
141
|
|
@@ -125,58 +150,179 @@ module EventMachine
|
|
125
150
|
close_connection_after_writing
|
126
151
|
end
|
127
152
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
# Also accept ascii only data in other encodings for convenience
|
133
|
-
unless (data.encoding == Encoding.find("UTF-8") && data.valid_encoding?) || data.ascii_only?
|
134
|
-
raise WebSocketError, "Data sent to WebSocket must be valid UTF-8 but was #{data.encoding} (valid: #{data.valid_encoding?})"
|
135
|
-
end
|
136
|
-
# This labels the encoding as binary so that it can be combined with
|
137
|
-
# the BINARY framing
|
138
|
-
data.force_encoding("BINARY")
|
139
|
-
else
|
140
|
-
# TODO: Check that data is valid UTF-8
|
141
|
-
end
|
153
|
+
# Cache encodings since it's moderately expensive to look them up each time
|
154
|
+
ENCODING_SUPPORTED = "string".respond_to?(:force_encoding)
|
155
|
+
UTF8 = Encoding.find("UTF-8") if ENCODING_SUPPORTED
|
156
|
+
BINARY = Encoding.find("BINARY") if ENCODING_SUPPORTED
|
142
157
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
158
|
+
# Send a WebSocket text frame.
|
159
|
+
#
|
160
|
+
# A WebSocketError may be raised if the connection is in an opening or a
|
161
|
+
# closing state, or if the passed in data is not valid UTF-8
|
162
|
+
#
|
163
|
+
def send_text(data)
|
164
|
+
# If we're using Ruby 1.9, be pedantic about encodings
|
165
|
+
if ENCODING_SUPPORTED
|
166
|
+
# Also accept ascii only data in other encodings for convenience
|
167
|
+
unless (data.encoding == UTF8 && data.valid_encoding?) || data.ascii_only?
|
168
|
+
raise WebSocketError, "Data sent to WebSocket must be valid UTF-8 but was #{data.encoding} (valid: #{data.valid_encoding?})"
|
147
169
|
end
|
170
|
+
# This labels the encoding as binary so that it can be combined with
|
171
|
+
# the BINARY framing
|
172
|
+
data.force_encoding(BINARY)
|
148
173
|
else
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
174
|
+
# TODO: Check that data is valid UTF-8
|
175
|
+
end
|
176
|
+
|
177
|
+
if @handler
|
178
|
+
@handler.send_text_frame(data)
|
179
|
+
else
|
180
|
+
raise WebSocketError, "Cannot send data before onopen callback"
|
181
|
+
end
|
182
|
+
|
183
|
+
# Revert data back to the original encoding (which we assume is UTF-8)
|
184
|
+
# Doing this to avoid duping the string - there may be a better way
|
185
|
+
data.force_encoding(UTF8) if ENCODING_SUPPORTED
|
186
|
+
return nil
|
187
|
+
end
|
188
|
+
|
189
|
+
alias :send :send_text
|
190
|
+
|
191
|
+
# Send a WebSocket binary frame.
|
192
|
+
#
|
193
|
+
def send_binary(data)
|
194
|
+
if @handler
|
195
|
+
@handler.send_frame(:binary, data)
|
196
|
+
else
|
197
|
+
raise WebSocketError, "Cannot send binary before onopen callback"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Send a ping to the client. The client must respond with a pong.
|
202
|
+
#
|
203
|
+
# In the case that the client is running a WebSocket draft < 01, false
|
204
|
+
# is returned since ping & pong are not supported
|
205
|
+
#
|
206
|
+
def ping(body = '')
|
207
|
+
if @handler
|
208
|
+
@handler.pingable? ? @handler.send_frame(:ping, body) && true : false
|
209
|
+
else
|
210
|
+
raise WebSocketError, "Cannot ping before onopen callback"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Send an unsolicited pong message, as allowed by the protocol. The
|
215
|
+
# client is not expected to respond to this message.
|
216
|
+
#
|
217
|
+
# em-websocket automatically takes care of sending pong replies to
|
218
|
+
# incoming ping messages, as the protocol demands.
|
219
|
+
#
|
220
|
+
def pong(body = '')
|
221
|
+
if @handler
|
222
|
+
@handler.pingable? ? @handler.send_frame(:pong, body) && true : false
|
223
|
+
else
|
224
|
+
raise WebSocketError, "Cannot ping before onopen callback"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Test whether the connection is pingable (i.e. the WebSocket draft in
|
229
|
+
# use is >= 01)
|
230
|
+
def pingable?
|
231
|
+
if @handler
|
232
|
+
@handler.pingable?
|
233
|
+
else
|
234
|
+
raise WebSocketError, "Cannot test whether pingable before onopen callback"
|
154
235
|
end
|
155
236
|
end
|
156
237
|
|
157
|
-
def
|
158
|
-
|
238
|
+
def supports_close_codes?
|
239
|
+
if @handler
|
240
|
+
@handler.supports_close_codes?
|
241
|
+
else
|
242
|
+
raise WebSocketError, "Cannot test before onopen callback"
|
243
|
+
end
|
159
244
|
end
|
160
245
|
|
161
246
|
def state
|
162
247
|
@handler ? @handler.state : :handshake
|
163
248
|
end
|
164
249
|
|
250
|
+
# Returns the IP address for the remote peer
|
251
|
+
def remote_ip
|
252
|
+
get_peername[2,6].unpack('nC4')[1..4].join('.')
|
253
|
+
end
|
254
|
+
|
255
|
+
# Returns the maximum frame size which this connection is configured to
|
256
|
+
# accept. This can be set globally or on a per connection basis, and
|
257
|
+
# defaults to a value of 10MB if not set.
|
258
|
+
#
|
259
|
+
# The behaviour when a too large frame is received varies by protocol,
|
260
|
+
# but in the newest protocols the connection will be closed with the
|
261
|
+
# correct close code (1009) immediately after receiving the frame header
|
262
|
+
#
|
263
|
+
def max_frame_size
|
264
|
+
defined?(@max_frame_size) ? @max_frame_size : WebSocket.max_frame_size
|
265
|
+
end
|
266
|
+
|
267
|
+
def close_timeout
|
268
|
+
@close_timeout || WebSocket.close_timeout
|
269
|
+
end
|
270
|
+
|
165
271
|
private
|
166
272
|
|
167
273
|
# As definited in draft 06 7.2.2, some failures require that the server
|
168
274
|
# abort the websocket connection rather than close cleanly
|
169
|
-
def abort
|
275
|
+
def abort(reason)
|
276
|
+
debug [:abort, reason]
|
170
277
|
close_connection
|
171
278
|
end
|
172
279
|
|
173
|
-
def close_websocket_private(code, body
|
280
|
+
def close_websocket_private(code, body)
|
174
281
|
if @handler
|
175
282
|
debug [:closing, code]
|
176
283
|
@handler.close_websocket(code, body)
|
177
284
|
else
|
178
285
|
# The handshake hasn't completed - should be safe to terminate
|
179
|
-
abort
|
286
|
+
abort(:handshake_incomplete)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# Allow applications to close with 1000, 1003, 1008, 1011, 3xxx or 4xxx.
|
291
|
+
#
|
292
|
+
# em-websocket uses a few other codes internally which should not be
|
293
|
+
# used by applications
|
294
|
+
#
|
295
|
+
# Browsers generally allow connections to be closed with code 1000,
|
296
|
+
# 3xxx, and 4xxx. em-websocket allows closing with a few other codes
|
297
|
+
# which seem reasonable (for discussion see
|
298
|
+
# https://github.com/igrigorik/em-websocket/issues/98)
|
299
|
+
#
|
300
|
+
# Usage from the rfc:
|
301
|
+
#
|
302
|
+
# 1000 indicates a normal closure
|
303
|
+
#
|
304
|
+
# 1003 indicates that an endpoint is terminating the connection
|
305
|
+
# because it has received a type of data it cannot accept
|
306
|
+
#
|
307
|
+
# 1008 indicates that an endpoint is terminating the connection because
|
308
|
+
# it has received a message that violates its policy
|
309
|
+
#
|
310
|
+
# 1011 indicates that a server is terminating the connection because it
|
311
|
+
# encountered an unexpected condition that prevented it from fulfilling
|
312
|
+
# the request
|
313
|
+
#
|
314
|
+
# Status codes in the range 3000-3999 are reserved for use by libraries,
|
315
|
+
# frameworks, and applications
|
316
|
+
#
|
317
|
+
# Status codes in the range 4000-4999 are reserved for private use and
|
318
|
+
# thus can't be registered
|
319
|
+
#
|
320
|
+
def acceptable_close_code?(code)
|
321
|
+
case code
|
322
|
+
when 1000, 1003, 1008, 1011, (3000..4999)
|
323
|
+
true
|
324
|
+
else
|
325
|
+
false
|
180
326
|
end
|
181
327
|
end
|
182
328
|
end
|
@@ -3,17 +3,13 @@
|
|
3
3
|
module EventMachine
|
4
4
|
module WebSocket
|
5
5
|
module Framing03
|
6
|
-
|
7
|
-
# Set the max frame lenth to very high value (10MB) until there is a
|
8
|
-
# limit specified in the spec to protect against malicious attacks
|
9
|
-
MAXIMUM_FRAME_LENGTH = 10 * 1024 * 1024
|
10
|
-
|
11
6
|
def initialize_framing
|
12
7
|
@data = ''
|
13
8
|
@application_data_buffer = '' # Used for MORE frames
|
9
|
+
@frame_type = nil
|
14
10
|
end
|
15
11
|
|
16
|
-
def process_data
|
12
|
+
def process_data
|
17
13
|
error = false
|
18
14
|
|
19
15
|
while !error && @data.size > 1
|
@@ -57,9 +53,8 @@ module EventMachine
|
|
57
53
|
length
|
58
54
|
end
|
59
55
|
|
60
|
-
|
61
|
-
|
62
|
-
raise DataError, "Frame length too long (#{payload_length} bytes)"
|
56
|
+
if payload_length > @connection.max_frame_size
|
57
|
+
raise WSMessageTooBigError, "Frame length too long (#{payload_length} bytes)"
|
63
58
|
end
|
64
59
|
|
65
60
|
# Check buffer size
|
@@ -78,7 +73,7 @@ module EventMachine
|
|
78
73
|
frame_type = opcode_to_type(opcode)
|
79
74
|
|
80
75
|
if frame_type == :continuation && !@frame_type
|
81
|
-
raise
|
76
|
+
raise WSProtocolError, 'Continuation frame not expected'
|
82
77
|
end
|
83
78
|
|
84
79
|
if more
|
@@ -156,7 +151,7 @@ module EventMachine
|
|
156
151
|
end
|
157
152
|
|
158
153
|
def opcode_to_type(opcode)
|
159
|
-
FRAME_TYPES_INVERSE[opcode] || raise(
|
154
|
+
FRAME_TYPES_INVERSE[opcode] || raise(WSProtocolError, "Unknown opcode #{opcode}")
|
160
155
|
end
|
161
156
|
|
162
157
|
def data_frame?(type)
|