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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +69 -0
  3. data/Gemfile +6 -0
  4. data/LICENCE +7 -0
  5. data/README.md +100 -56
  6. data/README.md.BACKUP.14928.md +195 -0
  7. data/README.md.BASE.14928.md +77 -0
  8. data/README.md.LOCAL.14928.md +98 -0
  9. data/README.md.REMOTE.14928.md +142 -0
  10. data/examples/echo.rb +23 -7
  11. data/examples/ping.rb +24 -0
  12. data/examples/test.html +5 -6
  13. data/lib/em-websocket.rb +4 -2
  14. data/lib/em-websocket/close03.rb +3 -0
  15. data/lib/em-websocket/close05.rb +3 -0
  16. data/lib/em-websocket/close06.rb +3 -0
  17. data/lib/em-websocket/close75.rb +2 -1
  18. data/lib/em-websocket/connection.rb +219 -73
  19. data/lib/em-websocket/framing03.rb +6 -11
  20. data/lib/em-websocket/framing05.rb +6 -11
  21. data/lib/em-websocket/framing07.rb +25 -20
  22. data/lib/em-websocket/framing76.rb +6 -15
  23. data/lib/em-websocket/handler.rb +69 -28
  24. data/lib/em-websocket/handler03.rb +0 -1
  25. data/lib/em-websocket/handler05.rb +0 -1
  26. data/lib/em-websocket/handler06.rb +0 -1
  27. data/lib/em-websocket/handler07.rb +0 -1
  28. data/lib/em-websocket/handler08.rb +0 -1
  29. data/lib/em-websocket/handler13.rb +0 -1
  30. data/lib/em-websocket/handler76.rb +2 -0
  31. data/lib/em-websocket/handshake.rb +156 -0
  32. data/lib/em-websocket/handshake04.rb +18 -56
  33. data/lib/em-websocket/handshake75.rb +15 -8
  34. data/lib/em-websocket/handshake76.rb +15 -14
  35. data/lib/em-websocket/masking04.rb +4 -30
  36. data/lib/em-websocket/message_processor_03.rb +13 -4
  37. data/lib/em-websocket/message_processor_06.rb +25 -13
  38. data/lib/em-websocket/version.rb +1 -1
  39. data/lib/em-websocket/websocket.rb +35 -24
  40. data/spec/helper.rb +82 -55
  41. data/spec/integration/common_spec.rb +90 -70
  42. data/spec/integration/draft03_spec.rb +84 -56
  43. data/spec/integration/draft05_spec.rb +14 -12
  44. data/spec/integration/draft06_spec.rb +66 -9
  45. data/spec/integration/draft13_spec.rb +59 -29
  46. data/spec/integration/draft75_spec.rb +46 -40
  47. data/spec/integration/draft76_spec.rb +113 -109
  48. data/spec/integration/gte_03_examples.rb +42 -0
  49. data/spec/integration/shared_examples.rb +174 -0
  50. data/spec/unit/framing_spec.rb +83 -110
  51. data/spec/unit/handshake_spec.rb +216 -0
  52. data/spec/unit/masking_spec.rb +2 -0
  53. metadata +31 -71
  54. data/examples/flash_policy_file_server.rb +0 -21
  55. data/examples/js/FABridge.js +0 -604
  56. data/examples/js/WebSocketMain.swf +0 -0
  57. data/examples/js/swfobject.js +0 -4
  58. data/examples/js/web_socket.js +0 -312
  59. data/lib/em-websocket/handler_factory.rb +0 -107
  60. data/spec/unit/handler_spec.rb +0 -147
@@ -10,7 +10,10 @@ module EventMachine
10
10
  send_frame(:close, '')
11
11
  end
12
12
  @state = :closing
13
+ start_close_timeout
13
14
  end
15
+
16
+ def supports_close_codes?; true; end
14
17
  end
15
18
  end
16
19
  end
@@ -2,9 +2,10 @@ module EventMachine
2
2
  module WebSocket
3
3
  module Close75
4
4
  def close_websocket(code, body)
5
- @state = :closed
6
5
  @connection.close_connection_after_writing
7
6
  end
7
+
8
+ def supports_close_codes?; false; end
8
9
  end
9
10
  end
10
11
  end
@@ -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, type=:text)
15
- if @onmessage
16
- if @onmessage.arity == 2
17
- @onmessage.call msg, type
18
- else
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
- @data = ''
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 close_websocket(code = nil, body = nil)
49
- if code && !(4000..4999).include?(code)
50
- raise "Application code may only use codes in the range 4000-4999"
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 and state != :handshake #TODO a different way to catch client sent handshake, is waiting state
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
- # These are application errors - raise unless onerror defined
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
- debug [:inbound_headers, data]
105
- @data << data
106
- @handler = HandlerFactory.build(self, @data, @secure, @debug)
107
- unless @handler
108
- # The whole header has not been received yet.
109
- return false
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
- @data = nil
112
- @handler.run_server
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
- def send(data, type=:text)
129
- if type == :text
130
- # If we're using Ruby 1.9, be pedantic about encodings
131
- if data.respond_to?(:force_encoding)
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
- if @handler
144
- @handler.send_text_frame(data)
145
- else
146
- raise WebSocketError, "Cannot send data before onopen callback"
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
- if @handler
150
- @handler.send_frame(type, data)
151
- else
152
- raise WebSocketError, "Cannot send data before onopen callback"
153
- end
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 request
158
- @handler ? @handler.request : {}
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 = nil)
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(newdata)
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
- # Addition to the spec to protect against malicious requests
61
- if payload_length > MAXIMUM_FRAME_LENGTH
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 WebSocketError, 'Continuation frame not expected'
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(DataError, "Unknown opcode")
154
+ FRAME_TYPES_INVERSE[opcode] || raise(WSProtocolError, "Unknown opcode #{opcode}")
160
155
  end
161
156
 
162
157
  def data_frame?(type)