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