em-websocket 0.4.0 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +35 -1
  3. data/Gemfile +6 -0
  4. data/LICENCE +7 -0
  5. data/README.md +49 -7
  6. data/em-websocket.gemspec +2 -8
  7. data/examples/test.html +3 -1
  8. data/lib/em-websocket/close03.rb +3 -0
  9. data/lib/em-websocket/close05.rb +3 -0
  10. data/lib/em-websocket/close06.rb +3 -0
  11. data/lib/em-websocket/close75.rb +2 -1
  12. data/lib/em-websocket/connection.rb +118 -26
  13. data/lib/em-websocket/framing03.rb +3 -2
  14. data/lib/em-websocket/framing05.rb +3 -2
  15. data/lib/em-websocket/framing07.rb +16 -4
  16. data/lib/em-websocket/framing76.rb +1 -4
  17. data/lib/em-websocket/handler.rb +31 -3
  18. data/lib/em-websocket/handler76.rb +2 -0
  19. data/lib/em-websocket/handshake.rb +23 -4
  20. data/lib/em-websocket/handshake04.rb +10 -6
  21. data/lib/em-websocket/handshake75.rb +11 -1
  22. data/lib/em-websocket/handshake76.rb +4 -0
  23. data/lib/em-websocket/masking04.rb +1 -5
  24. data/lib/em-websocket/message_processor_03.rb +6 -3
  25. data/lib/em-websocket/message_processor_06.rb +30 -9
  26. data/lib/em-websocket/version.rb +1 -1
  27. data/lib/em-websocket/websocket.rb +7 -0
  28. data/spec/helper.rb +67 -52
  29. data/spec/integration/common_spec.rb +49 -32
  30. data/spec/integration/draft03_spec.rb +83 -57
  31. data/spec/integration/draft05_spec.rb +14 -12
  32. data/spec/integration/draft06_spec.rb +67 -7
  33. data/spec/integration/draft13_spec.rb +29 -20
  34. data/spec/integration/draft75_spec.rb +44 -40
  35. data/spec/integration/draft76_spec.rb +58 -46
  36. data/spec/integration/gte_03_examples.rb +42 -0
  37. data/spec/integration/shared_examples.rb +93 -0
  38. data/spec/unit/framing_spec.rb +24 -4
  39. data/spec/unit/handshake_spec.rb +24 -1
  40. data/spec/unit/masking_spec.rb +2 -0
  41. metadata +18 -107
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1cd5fdedbceef9a4d9c5e9a4eef17dd18ab5f6aee6d2d67c65ccc35e2cdadc2a
4
+ data.tar.gz: 820c1ade3a0a9ac81e6828d8187553dc2c27a6b709d25853dc0e19a277eb4141
5
+ SHA512:
6
+ metadata.gz: ed42c46f3d9166206516e6180a76eaf6e47b86e8b30d9037aae5237214cbfe19307dd7e07e61e689412eba3ed8604e71d7a5e067132ec51200817459c4db3b36
7
+ data.tar.gz: 6f89807d825e4fef41d910f7e3d3e5c609f73b0abfaea5267e84e735c441dd8227a4c9585ff42ef7a63598d05b2bd652541081f611426e79332d7270f8b51b9a
data/CHANGELOG.rdoc CHANGED
@@ -1,6 +1,40 @@
1
1
  = Changelog
2
2
 
3
- == 0.4.0 / ?
3
+ == 0.5.1 / 2014-04-23
4
+
5
+ - new features:
6
+ - Support for receiving binary messages
7
+
8
+ - changed:
9
+ - Allow additional close codes to be sent by apps
10
+ - Raise better errors on missing Sec-WebSocket-Key2
11
+ - Updated http_parser.rb dependency to 0.6.0
12
+
13
+ - bug fixes:
14
+ - Abort if HTTP request URI is invalid
15
+ - Force close connections that have been sent a close handshake after a timeout
16
+
17
+ - improved spec compliance on:
18
+ - Missing continuation frames
19
+ - Fragmented control frames
20
+ - Close behaviour after protocol errors
21
+
22
+ == 0.5.0 / 2013-03-05
23
+
24
+ - new features:
25
+ - onclose handler is now passed a hash containing was_clean (set to true in drafts 03 and above when a connection is closed with a closing handshake, either by the server or the client), the close code, and reason (drafts 06 and above). Close code 1005 indicates that no code was supplied, and 1006 that the connection was closed abnormally.
26
+ - use Connection#support_close_codes? to easily check whether close codes are supported by the WebSocket protocol (drafts 06 and above)
27
+ - closes connection with 1007 close code if text frame contains invalid UTF8
28
+ - added Handshake#secure? for checking whether the connection is secure (either ssl or behind an ssl proxy)
29
+
30
+ - changed:
31
+ - Defaults to sending no close code rather than 1000 (consistent with browsers)
32
+ - Allows sending a 3xxx close code
33
+ - Renamed Connection#close_websocket to Connection#close (again for consistency with browsers). Old method is available till 0.6.
34
+ - Sends reasons with internally generated closure (previously only sent code)
35
+ - Echos close code when replying to close handshake
36
+
37
+ == 0.4.0 / 2013-01-22
4
38
 
5
39
  - new features:
6
40
  - on_open handler is now passed a handshake object which exposes the request headers, path, and query parameters
data/Gemfile CHANGED
@@ -1,3 +1,9 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gemspec
4
+
5
+ gem "em-websocket-client", git: "git@github.com:movitto/em-websocket-client.git", branch: "expose-websocket-api"
6
+ gem "em-spec", "~> 0.2.6"
7
+ gem "em-http-request", "~> 1.1.1"
8
+ gem "rspec", "~> 3.5.0"
9
+ gem "rake"
data/LICENCE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2009-2014 Ilya Grigorik, Martyn Loughran
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # EM-WebSocket
2
2
 
3
- EventMachine based, async, Ruby WebSocket server. Take a look at examples directory, or check out the blog post below:
3
+ [![Gem Version](https://badge.fury.io/rb/em-websocket.png)](http://rubygems.org/gems/em-websocket)
4
+ [![Analytics](https://ga-beacon.appspot.com/UA-71196-10/em-websocket/readme)](https://github.com/igrigorik/ga-beacon)
4
5
 
5
- * [Ruby & Websockets: TCP for the Web](http://www.igvita.com/2009/12/22/ruby-websockets-tcp-for-the-browser/)
6
+ EventMachine based, async, Ruby WebSocket server. Take a look at examples directory, or check out the blog post: [Ruby & Websockets: TCP for the Web](http://www.igvita.com/2009/12/22/ruby-websockets-tcp-for-the-browser/).
6
7
 
7
8
  ## Simple server example
8
9
 
@@ -31,9 +32,52 @@ EM.run {
31
32
  }
32
33
  ```
33
34
 
35
+ ## Protocols supported, and protocol specific functionality
36
+
37
+ Supports all WebSocket protocols in use in the wild (and a few that are not): drafts 75, 76, 1-17, rfc.
38
+
39
+ While some of the changes between protocols are unimportant from the point of view of application developers, a few drafts did introduce new functionality. It's possible to easily test for this functionality by using
40
+
41
+ ### Ping & pong supported
42
+
43
+ Call `ws.pingable?` to check whether ping & pong is supported by the protocol in use.
44
+
45
+ It's possible to send a ping frame (`ws.ping(body = '')`), which the client must respond to with a pong, or the server can send an unsolicited pong frame (`ws.pong(body = '')`) which the client should not respond to. These methods can be used regardless of protocol version; they return true if the protocol supports ping&pong or false otherwise.
46
+
47
+ When receiving a ping, the server will automatically respond with a pong as the spec requires (so you should _not_ write an onping handler that replies with a pong), however it is possible to bind to ping & pong events if desired by using the `onping` and `onpong` methods.
48
+
49
+ ### Healthchecks
50
+
51
+ It's possible to send a regular `HTTP GET` request to the `/healthcheck` endpoint and receive a `200` response from the server.
52
+
53
+ ### Close codes and reasons
54
+
55
+ A WebSocket connection can be closed cleanly, regardless of protocol, by calling `ws.close(code = nil, body = nil)`.
56
+
57
+ Early protocols just close the TCP connection, draft 3 introduced a close handshake, and draft 6 added close codes and reasons to the close handshake. Call `ws.supports_close_codes?` to check whether close codes are supported (i.e. the protocol version is 6 or above).
58
+
59
+ The `onclose` callback is passed a hash which may contain following keys (depending on the protocol version):
60
+
61
+ * `was_clean`: boolean indicating whether the connection was closed via the close handshake.
62
+ * `code`: the close code. There are two special close codes which the server may set (as defined in the WebSocket spec):
63
+ * 1005: no code was supplied
64
+ * 1006: abnormal closure (the same as `was_clean: false`)
65
+ * `reason`: the close reason
66
+
67
+ Acceptable close codes are defined in the WebSocket rfc (<http://tools.ietf.org/html/rfc6455#section-7.4>). The following codes can be supplies when calling `ws.close(code)`:
68
+
69
+ * 1000: a generic normal close
70
+ * range 3xxx: reserved for libraries, frameworks, and applications (and can be registered with IANA)
71
+ * range 4xxx: for private use
72
+
73
+ If unsure use a code in the 4xxx range. em-websocket may also close a connection with one of the following close codes:
74
+
75
+ * 1002: WebSocket protocol error.
76
+ * 1009: Message too big to process. By default em-websocket will accept frames up to 10MB in size. If a frame is larger than this the connection will be closed without reading the frame data. The limit can be overriden globally (`EM::WebSocket.max_frame_size = bytes`) or on a specific connection (`ws.max_frame_size = bytes`).
77
+
34
78
  ## Secure server
35
79
 
36
- It is possible to accept secure `wss://` connections by passing `:secure => true` when opening the connection. Pass a `:tls_options` hash containing keys as described in http://eventmachine.rubyforge.org/EventMachine/Connection.html#start_tls-instance_method
80
+ It is possible to accept secure `wss://` connections by passing `:secure => true` when opening the connection. Pass a `:tls_options` hash containing keys as described in http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Connection:start_tls
37
81
 
38
82
  **Warning**: Safari 5 does not currently support prompting on untrusted SSL certificates therefore using a self signed certificate may leave you scratching your head.
39
83
 
@@ -51,6 +95,8 @@ EM::WebSocket.start({
51
95
  end
52
96
  ```
53
97
 
98
+ It's possible to check whether an incoming connection is secure by reading `handshake.secure?` in the onopen callback.
99
+
54
100
  ## Running behind an SSL Proxy/Terminator, like Stunnel
55
101
 
56
102
  The `:secure_proxy => true` option makes it possible to use em-websocket behind a secure SSL proxy/terminator like [Stunnel](http://www.stunnel.org/) which does the actual encryption & decryption.
@@ -98,7 +144,3 @@ Using flash emulation does require some minimal support from em-websocket which
98
144
  * [Twitter AMQP WebSocket Example](http://github.com/rubenfonseca/twitter-amqp-websocket-example)
99
145
  * examples/multicast.rb - broadcast all ruby tweets to all subscribers
100
146
  * examples/echo.rb - server <> client exchange via a websocket
101
-
102
- # License
103
-
104
- The MIT License - Copyright (c) 2009 Ilya Grigorik
data/em-websocket.gemspec CHANGED
@@ -11,8 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.homepage = "http://github.com/igrigorik/em-websocket"
12
12
  s.summary = %q{EventMachine based WebSocket server}
13
13
  s.description = %q{EventMachine based WebSocket server}
14
-
15
- s.rubyforge_project = "em-websocket"
14
+ s.license = 'MIT'
16
15
 
17
16
  s.files = `git ls-files`.split("\n")
18
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -20,10 +19,5 @@ Gem::Specification.new do |s|
20
19
  s.require_paths = ["lib"]
21
20
 
22
21
  s.add_dependency("eventmachine", ">= 0.12.9")
23
- s.add_dependency("http_parser.rb", '~> 0.5.3')
24
- s.add_development_dependency('em-spec', '~> 0.2.6')
25
- s.add_development_dependency("eventmachine")
26
- s.add_development_dependency('em-http-request', '~> 0.2.6')
27
- s.add_development_dependency('rspec', "~> 2.12.0")
28
- s.add_development_dependency('rake')
22
+ s.add_dependency("http_parser.rb", '~> 0')
29
23
  end
data/examples/test.html CHANGED
@@ -12,7 +12,9 @@
12
12
  var Socket = "MozWebSocket" in window ? MozWebSocket : WebSocket;
13
13
  var ws = new Socket("ws://localhost:8080/foo/bar?hello=world");
14
14
  ws.onmessage = function(evt) { debug("Received: " + evt.data); };
15
- ws.onclose = function() { debug("socket closed"); };
15
+ ws.onclose = function(event) {
16
+ debug("Closed - code: " + event.code + ", reason: " + event.reason + ", wasClean: " + event.wasClean);
17
+ };
16
18
  ws.onopen = function() {
17
19
  debug("connected...");
18
20
  ws.send("hello server");
@@ -5,7 +5,10 @@ module EventMachine
5
5
  # TODO: Ideally send body data and check that it matches in ack
6
6
  send_frame(:close, '')
7
7
  @state = :closing
8
+ start_close_timeout
8
9
  end
10
+
11
+ def supports_close_codes?; false; end
9
12
  end
10
13
  end
11
14
  end
@@ -5,7 +5,10 @@ module EventMachine
5
5
  # TODO: Ideally send body data and check that it matches in ack
6
6
  send_frame(:close, "\x53")
7
7
  @state = :closing
8
+ start_close_timeout
8
9
  end
10
+
11
+ def supports_close_codes?; false; end
9
12
  end
10
13
  end
11
14
  end
@@ -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
@@ -10,26 +10,30 @@ module EventMachine
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
13
14
  def onping(&blk); @onping = blk; end
14
15
  def onpong(&blk); @onpong = blk; end
15
16
 
16
17
  def trigger_on_message(msg)
17
- @onmessage.call(msg) if @onmessage
18
+ @onmessage.call(msg) if defined? @onmessage
19
+ end
20
+ def trigger_on_binary(msg)
21
+ @onbinary.call(msg) if defined? @onbinary
18
22
  end
19
23
  def trigger_on_open(handshake)
20
- @onopen.call(handshake) if @onopen
24
+ @onopen.call(handshake) if defined? @onopen
21
25
  end
22
- def trigger_on_close
23
- @onclose.call if @onclose
26
+ def trigger_on_close(event = {})
27
+ @onclose.call(event) if defined? @onclose
24
28
  end
25
29
  def trigger_on_ping(data)
26
- @onping.call(data) if @onping
30
+ @onping.call(data) if defined? @onping
27
31
  end
28
32
  def trigger_on_pong(data)
29
- @onpong.call(data) if @onpong
33
+ @onpong.call(data) if defined? @onpong
30
34
  end
31
35
  def trigger_on_error(reason)
32
- return false unless @onerror
36
+ return false unless defined? @onerror
33
37
  @onerror.call(reason)
34
38
  true
35
39
  end
@@ -40,6 +44,10 @@ module EventMachine
40
44
  @secure = options[:secure] || false
41
45
  @secure_proxy = options[:secure_proxy] || false
42
46
  @tls_options = options[:tls_options] || {}
47
+ @close_timeout = options[:close_timeout]
48
+ @outbound_limit = options[:outbound_limit] || 0
49
+
50
+ @handler = nil
43
51
 
44
52
  debug [:initialize]
45
53
  end
@@ -47,17 +55,17 @@ module EventMachine
47
55
  # Use this method to close the websocket connection cleanly
48
56
  # This sends a close frame and waits for acknowlegement before closing
49
57
  # the connection
50
- def close_websocket(code = nil, body = nil)
51
- if code && !(4000..4999).include?(code)
52
- 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"
53
61
  end
54
62
 
55
- # If code not defined then set to 1000 (normal closure)
56
- code ||= 1000
57
-
58
63
  close_websocket_private(code, body)
59
64
  end
60
65
 
66
+ # Deprecated, to be removed in version 0.6
67
+ alias :close_websocket :close
68
+
61
69
  def post_init
62
70
  start_tls(@tls_options) if @secure
63
71
  end
@@ -70,17 +78,25 @@ module EventMachine
70
78
  else
71
79
  dispatch(data)
72
80
  end
73
- rescue WSProtocolError => e
74
- debug [:error, e]
75
- trigger_on_error(e)
76
- close_websocket_private(e.code)
77
81
  rescue => e
78
82
  debug [:error, e]
79
- # These are application errors - raise unless onerror defined
80
- trigger_on_error(e) || raise(e)
83
+
81
84
  # There is no code defined for application errors, so use 3000
82
85
  # (which is reserved for frameworks)
83
- 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)
84
100
  end
85
101
 
86
102
  def unbind
@@ -94,7 +110,9 @@ module EventMachine
94
110
  end
95
111
 
96
112
  def dispatch(data)
97
- if data.match(/\A<policy-file-request\s*\/>/)
113
+ if data.match(%r|^GET /healthcheck|)
114
+ send_healthcheck_response
115
+ elsif data.match(/\A<policy-file-request\s*\/>/)
98
116
  send_flash_cross_domain_file
99
117
  else
100
118
  @handshake ||= begin
@@ -113,7 +131,7 @@ module EventMachine
113
131
  debug [:error, e]
114
132
  trigger_on_error(e)
115
133
  # Handshake errors require the connection to be aborted
116
- abort
134
+ abort(:handshake_error)
117
135
  }
118
136
 
119
137
  handshake
@@ -123,6 +141,23 @@ module EventMachine
123
141
  end
124
142
  end
125
143
 
144
+ def send_healthcheck_response
145
+ debug [:healthcheck, 'OK']
146
+
147
+ healthcheck_res = ["HTTP/1.1 200 OK"]
148
+ healthcheck_res << "Content-Type: text/plain"
149
+ healthcheck_res << "Content-Length: 2"
150
+
151
+ healthcheck_res = healthcheck_res.join("\r\n") + "\r\n\r\nOK"
152
+
153
+ send_data healthcheck_res
154
+
155
+ # handle the healthcheck request transparently
156
+ # no need to notify the user about this connection
157
+ @onclose = nil
158
+ close_connection_after_writing
159
+ end
160
+
126
161
  def send_flash_cross_domain_file
127
162
  file = '<?xml version="1.0"?><cross-domain-policy><allow-access-from domain="*" to-ports="*"/></cross-domain-policy>'
128
163
  debug [:cross_domain, file]
@@ -219,10 +254,23 @@ module EventMachine
219
254
  end
220
255
  end
221
256
 
257
+ def supports_close_codes?
258
+ if @handler
259
+ @handler.supports_close_codes?
260
+ else
261
+ raise WebSocketError, "Cannot test before onopen callback"
262
+ end
263
+ end
264
+
222
265
  def state
223
266
  @handler ? @handler.state : :handshake
224
267
  end
225
268
 
269
+ # Returns the IP address for the remote peer
270
+ def remote_ip
271
+ get_peername[2,6].unpack('nC4')[1..4].join('.')
272
+ end
273
+
226
274
  # Returns the maximum frame size which this connection is configured to
227
275
  # accept. This can be set globally or on a per connection basis, and
228
276
  # defaults to a value of 10MB if not set.
@@ -232,24 +280,68 @@ module EventMachine
232
280
  # correct close code (1009) immediately after receiving the frame header
233
281
  #
234
282
  def max_frame_size
235
- @max_frame_size || WebSocket.max_frame_size
283
+ defined?(@max_frame_size) ? @max_frame_size : WebSocket.max_frame_size
284
+ end
285
+
286
+ def close_timeout
287
+ @close_timeout || WebSocket.close_timeout
236
288
  end
237
289
 
238
290
  private
239
291
 
240
292
  # As definited in draft 06 7.2.2, some failures require that the server
241
293
  # abort the websocket connection rather than close cleanly
242
- def abort
294
+ def abort(reason)
295
+ debug [:abort, reason]
243
296
  close_connection
244
297
  end
245
298
 
246
- def close_websocket_private(code, body = nil)
299
+ def close_websocket_private(code, body)
247
300
  if @handler
248
301
  debug [:closing, code]
249
302
  @handler.close_websocket(code, body)
250
303
  else
251
304
  # The handshake hasn't completed - should be safe to terminate
252
- abort
305
+ abort(:handshake_incomplete)
306
+ end
307
+ end
308
+
309
+ # Allow applications to close with 1000, 1003, 1008, 1011, 3xxx or 4xxx.
310
+ #
311
+ # em-websocket uses a few other codes internally which should not be
312
+ # used by applications
313
+ #
314
+ # Browsers generally allow connections to be closed with code 1000,
315
+ # 3xxx, and 4xxx. em-websocket allows closing with a few other codes
316
+ # which seem reasonable (for discussion see
317
+ # https://github.com/igrigorik/em-websocket/issues/98)
318
+ #
319
+ # Usage from the rfc:
320
+ #
321
+ # 1000 indicates a normal closure
322
+ #
323
+ # 1003 indicates that an endpoint is terminating the connection
324
+ # because it has received a type of data it cannot accept
325
+ #
326
+ # 1008 indicates that an endpoint is terminating the connection because
327
+ # it has received a message that violates its policy
328
+ #
329
+ # 1011 indicates that a server is terminating the connection because it
330
+ # encountered an unexpected condition that prevented it from fulfilling
331
+ # the request
332
+ #
333
+ # Status codes in the range 3000-3999 are reserved for use by libraries,
334
+ # frameworks, and applications
335
+ #
336
+ # Status codes in the range 4000-4999 are reserved for private use and
337
+ # thus can't be registered
338
+ #
339
+ def acceptable_close_code?(code)
340
+ case code
341
+ when 1000, 1003, 1008, 1011, (3000..4999)
342
+ true
343
+ else
344
+ false
253
345
  end
254
346
  end
255
347
  end
@@ -6,9 +6,10 @@ module EventMachine
6
6
  def initialize_framing
7
7
  @data = ''
8
8
  @application_data_buffer = '' # Used for MORE frames
9
+ @frame_type = nil
9
10
  end
10
11
 
11
- def process_data(newdata)
12
+ def process_data
12
13
  error = false
13
14
 
14
15
  while !error && @data.size > 1
@@ -150,7 +151,7 @@ module EventMachine
150
151
  end
151
152
 
152
153
  def opcode_to_type(opcode)
153
- FRAME_TYPES_INVERSE[opcode] || raise(WSProtocolError, "Unknown opcode")
154
+ FRAME_TYPES_INVERSE[opcode] || raise(WSProtocolError, "Unknown opcode #{opcode}")
154
155
  end
155
156
 
156
157
  def data_frame?(type)
@@ -6,9 +6,10 @@ module EventMachine
6
6
  def initialize_framing
7
7
  @data = MaskedString.new
8
8
  @application_data_buffer = '' # Used for MORE frames
9
+ @frame_type = nil
9
10
  end
10
11
 
11
- def process_data(newdata)
12
+ def process_data
12
13
  error = false
13
14
 
14
15
  while !error && @data.size > 5 # mask plus first byte present
@@ -151,7 +152,7 @@ module EventMachine
151
152
  end
152
153
 
153
154
  def opcode_to_type(opcode)
154
- FRAME_TYPES_INVERSE[opcode] || raise(WSProtocolError, "Unknown opcode")
155
+ FRAME_TYPES_INVERSE[opcode] || raise(WSProtocolError, "Unknown opcode #{opcode}")
155
156
  end
156
157
 
157
158
  def data_frame?(type)
@@ -7,9 +7,10 @@ module EventMachine
7
7
  def initialize_framing
8
8
  @data = MaskedString.new
9
9
  @application_data_buffer = '' # Used for MORE frames
10
+ @frame_type = nil
10
11
  end
11
12
 
12
- def process_data(newdata)
13
+ def process_data
13
14
  error = false
14
15
 
15
16
  while !error && @data.size >= 2
@@ -86,8 +87,19 @@ module EventMachine
86
87
 
87
88
  frame_type = opcode_to_type(opcode)
88
89
 
89
- if frame_type == :continuation && !@frame_type
90
- raise WSProtocolError, 'Continuation frame not expected'
90
+ if frame_type == :continuation
91
+ if !@frame_type
92
+ raise WSProtocolError, 'Continuation frame not expected'
93
+ end
94
+ else # Not a continuation frame
95
+ if @frame_type && data_frame?(frame_type)
96
+ raise WSProtocolError, "Continuation frame expected"
97
+ end
98
+ end
99
+
100
+ # Validate that control frames are not fragmented
101
+ if !fin && !data_frame?(frame_type)
102
+ raise WSProtocolError, 'Control frames must not be fragmented'
91
103
  end
92
104
 
93
105
  if !fin
@@ -162,7 +174,7 @@ module EventMachine
162
174
  end
163
175
 
164
176
  def opcode_to_type(opcode)
165
- FRAME_TYPES_INVERSE[opcode] || raise(WSProtocolError, "Unknown opcode")
177
+ FRAME_TYPES_INVERSE[opcode] || raise(WSProtocolError, "Unknown opcode #{opcode}")
166
178
  end
167
179
 
168
180
  def data_frame?(type)
@@ -7,7 +7,7 @@ module EventMachine
7
7
  @data = ''
8
8
  end
9
9
 
10
- def process_data(newdata)
10
+ def process_data
11
11
  debug [:message, @data]
12
12
 
13
13
  # This algorithm comes straight from the spec
@@ -71,9 +71,6 @@ module EventMachine
71
71
  raise WSMessageTooBigError, "Frame length too long (#{@data.size} bytes)"
72
72
  end
73
73
 
74
- # Optimization to avoid calling slice! unnecessarily
75
- error = true and next unless newdata =~ /\xff/
76
-
77
74
  msg = @data.slice!(/\A\x00[^\xff]*\xff/)
78
75
  if msg
79
76
  msg.gsub!(/\A\x00|\xff\z/, '')
@@ -2,7 +2,7 @@ module EventMachine
2
2
  module WebSocket
3
3
  class Handler
4
4
  def self.klass_factory(version)
5
- handler_klass = case version
5
+ case version
6
6
  when 75
7
7
  Handler75
8
8
  when 76
@@ -38,21 +38,49 @@ module EventMachine
38
38
  @connection = connection
39
39
  @debug = debug
40
40
  @state = :connected
41
+ @close_timer = nil
41
42
  initialize_framing
42
43
  end
43
44
 
44
45
  def receive_data(data)
45
46
  @data << data
46
- process_data(data)
47
+ process_data
48
+ rescue WSProtocolError => e
49
+ fail_websocket(e)
47
50
  end
48
51
 
49
52
  def close_websocket(code, body)
50
53
  # Implemented in subclass
51
54
  end
52
55
 
56
+ # Used to avoid un-acked and unclosed remaining open indefinitely
57
+ def start_close_timeout
58
+ @close_timer = EM::Timer.new(@connection.close_timeout) {
59
+ @connection.close_connection
60
+ e = WSProtocolError.new("Close handshake un-acked after #{@connection.close_timeout}s, closing tcp connection")
61
+ @connection.trigger_on_error(e)
62
+ }
63
+ end
64
+
65
+ # This corresponds to "Fail the WebSocket Connection" in the spec.
66
+ def fail_websocket(e)
67
+ debug [:error, e]
68
+ close_websocket(e.code, e.message)
69
+ @connection.close_connection_after_writing
70
+ @connection.trigger_on_error(e)
71
+ end
72
+
53
73
  def unbind
54
74
  @state = :closed
55
- @connection.trigger_on_close
75
+
76
+ @close_timer.cancel if @close_timer
77
+
78
+ @close_info = defined?(@close_info) ? @close_info : {
79
+ :code => 1006,
80
+ :was_clean => false,
81
+ }
82
+
83
+ @connection.trigger_on_close(@close_info)
56
84
  end
57
85
 
58
86
  def ping
@@ -1,3 +1,5 @@
1
+ # encoding: BINARY
2
+
1
3
  module EventMachine
2
4
  module WebSocket
3
5
  class Handler76 < Handler