em-websocket 0.3.7 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +52 -0
  3. data/Gemfile +6 -0
  4. data/LICENCE +7 -0
  5. data/README.md +105 -40
  6. data/examples/echo.rb +22 -6
  7. data/examples/test.html +5 -6
  8. data/lib/em-websocket.rb +2 -1
  9. data/lib/em-websocket/close03.rb +3 -0
  10. data/lib/em-websocket/close05.rb +3 -0
  11. data/lib/em-websocket/close06.rb +3 -0
  12. data/lib/em-websocket/close75.rb +2 -1
  13. data/lib/em-websocket/connection.rb +154 -48
  14. data/lib/em-websocket/framing03.rb +3 -2
  15. data/lib/em-websocket/framing05.rb +3 -2
  16. data/lib/em-websocket/framing07.rb +16 -4
  17. data/lib/em-websocket/framing76.rb +1 -4
  18. data/lib/em-websocket/handler.rb +61 -15
  19. data/lib/em-websocket/handler03.rb +0 -1
  20. data/lib/em-websocket/handler05.rb +0 -1
  21. data/lib/em-websocket/handler06.rb +0 -1
  22. data/lib/em-websocket/handler07.rb +0 -1
  23. data/lib/em-websocket/handler08.rb +0 -1
  24. data/lib/em-websocket/handler13.rb +0 -1
  25. data/lib/em-websocket/handler76.rb +2 -0
  26. data/lib/em-websocket/handshake.rb +156 -0
  27. data/lib/em-websocket/handshake04.rb +18 -16
  28. data/lib/em-websocket/handshake75.rb +15 -8
  29. data/lib/em-websocket/handshake76.rb +15 -14
  30. data/lib/em-websocket/masking04.rb +3 -6
  31. data/lib/em-websocket/message_processor_03.rb +6 -3
  32. data/lib/em-websocket/message_processor_06.rb +30 -9
  33. data/lib/em-websocket/version.rb +1 -1
  34. data/lib/em-websocket/websocket.rb +24 -15
  35. data/spec/helper.rb +84 -51
  36. data/spec/integration/common_spec.rb +89 -69
  37. data/spec/integration/draft03_spec.rb +84 -56
  38. data/spec/integration/draft05_spec.rb +14 -12
  39. data/spec/integration/draft06_spec.rb +67 -7
  40. data/spec/integration/draft13_spec.rb +30 -19
  41. data/spec/integration/draft75_spec.rb +46 -40
  42. data/spec/integration/draft76_spec.rb +59 -45
  43. data/spec/integration/gte_03_examples.rb +42 -0
  44. data/spec/integration/shared_examples.rb +119 -0
  45. data/spec/unit/framing_spec.rb +24 -4
  46. data/spec/unit/handshake_spec.rb +216 -0
  47. data/spec/unit/masking_spec.rb +2 -0
  48. metadata +32 -86
  49. data/examples/flash_policy_file_server.rb +0 -21
  50. data/examples/js/FABridge.js +0 -604
  51. data/examples/js/WebSocketMain.swf +0 -0
  52. data/examples/js/swfobject.js +0 -4
  53. data/examples/js/web_socket.js +0 -312
  54. data/lib/em-websocket/handler_factory.rb +0 -109
  55. data/spec/unit/handler_spec.rb +0 -159
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b5ae1d6db2d88e23c98fce247af5c18d9af7343d196cc85224d2d62e6f39cf6b
4
+ data.tar.gz: eff929c27eb3949705cf0e5438af33c7b05612c5e5deedaf2ea39a81f0308559
5
+ SHA512:
6
+ metadata.gz: 301e06c551ea5414cd463a606fc709676932094bdf92743555cf5ecccb20fb0c0d7abe14c19a61942337ea8c60053bd59d7b31841178df9f05dbae3c6e66232f
7
+ data.tar.gz: 5020a0dd90126780266b55c197acadcbabc40044e5513b4461f4f4d9500d9ace38646c931aeb19fd638b852a68346b5cb44c48b9cc5e5b5f2e96e3a80ff2cbf4
@@ -1,5 +1,57 @@
1
1
  = Changelog
2
2
 
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
38
+
39
+ - new features:
40
+ - on_open handler is now passed a handshake object which exposes the request headers, path, and query parameters
41
+ - Easily access the protocol version via Handshake#protocol_version
42
+ - Easily access the origin via Handshake#origin
43
+
44
+ - changed:
45
+ - Removed Connection#request - change to using handshake passed to on_open
46
+
47
+ - internals:
48
+ - Uses the http_parser.rb gem
49
+
50
+ == 0.3.8 / 2012-07-12
51
+
52
+ - bug fixes:
53
+ - Fixed support for Ruby 1.8.7 which was broken in 0.3.7
54
+
3
55
  == 0.3.7 / 2012-07-11
4
56
 
5
57
  - new features:
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,77 +1,142 @@
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
 
9
10
  ```ruby
10
- EventMachine.run {
11
-
12
- EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws|
13
- ws.onopen {
14
- puts "WebSocket connection open"
15
-
16
- # publish message to the client
17
- ws.send "Hello Client"
18
- }
19
-
20
- ws.onclose { puts "Connection closed" }
21
- ws.onmessage { |msg|
22
- puts "Recieved message: #{msg}"
23
- ws.send "Pong: #{msg}"
24
- }
25
- end
11
+ require 'em-websocket'
12
+
13
+ EM.run {
14
+ EM::WebSocket.run(:host => "0.0.0.0", :port => 8080) do |ws|
15
+ ws.onopen { |handshake|
16
+ puts "WebSocket connection open"
17
+
18
+ # Access properties on the EM::WebSocket::Handshake object, e.g.
19
+ # path, query_string, origin, headers
20
+
21
+ # Publish message to the client
22
+ ws.send "Hello Client, you connected to #{handshake.path}"
23
+ }
24
+
25
+ ws.onclose { puts "Connection closed" }
26
+
27
+ ws.onmessage { |msg|
28
+ puts "Recieved message: #{msg}"
29
+ ws.send "Pong: #{msg}"
30
+ }
31
+ end
26
32
  }
27
33
  ```
28
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
+ ### Close codes and reasons
50
+
51
+ A WebSocket connection can be closed cleanly, regardless of protocol, by calling `ws.close(code = nil, body = nil)`.
52
+
53
+ 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).
54
+
55
+ The `onclose` callback is passed a hash which may contain following keys (depending on the protocol version):
56
+
57
+ * `was_clean`: boolean indicating whether the connection was closed via the close handshake.
58
+ * `code`: the close code. There are two special close codes which the server may set (as defined in the WebSocket spec):
59
+ * 1005: no code was supplied
60
+ * 1006: abnormal closure (the same as `was_clean: false`)
61
+ * `reason`: the close reason
62
+
63
+ 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)`:
64
+
65
+ * 1000: a generic normal close
66
+ * range 3xxx: reserved for libraries, frameworks, and applications (and can be registered with IANA)
67
+ * range 4xxx: for private use
68
+
69
+ If unsure use a code in the 4xxx range. em-websocket may also close a connection with one of the following close codes:
70
+
71
+ * 1002: WebSocket protocol error.
72
+ * 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`).
73
+
29
74
  ## Secure server
30
75
 
31
- It is possible to accept secure wss:// connections by passing :secure => true when opening the connection. Safari 5 does not currently support prompting on untrusted SSL certificates therefore using signed certificates is highly recommended. Pass a :tls_options hash containing keys as described in http://eventmachine.rubyforge.org/EventMachine/Connection.html#M000296
76
+ 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
32
77
 
33
- For example,
78
+ **Warning**: Safari 5 does not currently support prompting on untrusted SSL certificates therefore using a self signed certificate may leave you scratching your head.
34
79
 
35
80
  ```ruby
36
- EventMachine::WebSocket.start({
37
- :host => "0.0.0.0",
38
- :port => 443
39
- :secure => true,
40
- :tls_options => {
41
- :private_key_file => "/private/key",
42
- :cert_chain_file => "/ssl/certificate"
43
- }
81
+ EM::WebSocket.start({
82
+ :host => "0.0.0.0",
83
+ :port => 443,
84
+ :secure => true,
85
+ :tls_options => {
86
+ :private_key_file => "/private/key",
87
+ :cert_chain_file => "/ssl/certificate"
88
+ }
44
89
  }) do |ws|
45
- ...
90
+ # ...
91
+ end
92
+ ```
93
+
94
+ It's possible to check whether an incoming connection is secure by reading `handshake.secure?` in the onopen callback.
95
+
96
+ ## Running behind an SSL Proxy/Terminator, like Stunnel
97
+
98
+ 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.
99
+
100
+ Note that this option is only required to support drafts 75 & 76 correctly (e.g. Safari 5.1.x & earlier, and Safari on iOS 5.x & earlier).
101
+
102
+ ```ruby
103
+ EM::WebSocket.start({
104
+ :host => "0.0.0.0",
105
+ :port => 8080,
106
+ :secure_proxy => true
107
+ }) do |ws|
108
+ # ...
46
109
  end
47
110
  ```
48
111
 
49
112
  ## Handling errors
50
113
 
51
- There are two kinds of errors that need to be handled - errors caused by incompatible WebSocket clients sending invalid data and errors in application code. They are handled as follows:
114
+ There are two kinds of errors that need to be handled -- WebSocket protocol errors and errors in application code.
52
115
 
53
- Errors caused by invalid WebSocket data (for example invalid errors in the WebSocket handshake or invalid message frames) raise errors which descend from `EventMachine::WebSocket::WebSocketError`. Such errors are rescued internally and the WebSocket connection will be closed immediately or an error code sent to the browser in accordance to the WebSocket specification. However it is possible to be notified in application code on such errors by including an `onerror` callback.
116
+ WebSocket protocol errors (for example invalid data in the handshake or invalid message frames) raise errors which descend from `EM::WebSocket::WebSocketError`. Such errors are rescued internally and the WebSocket connection will be closed immediately or an error code sent to the browser in accordance to the WebSocket specification. It is possible to be notified in application code of such errors by including an `onerror` callback.
54
117
 
55
118
  ```ruby
56
119
  ws.onerror { |error|
57
- if e.kind_of?(EM::WebSocket::WebSocketError)
58
- ...
120
+ if error.kind_of?(EM::WebSocket::WebSocketError)
121
+ # ...
59
122
  end
60
123
  }
61
124
  ```
62
125
 
63
- Application errors are treated differently. If no `onerror` callback has been defined these errors will propagate to the EventMachine reactor, typically causing your program to terminate. If you wish to handle exceptions, simply supply an `onerror callback` and check for exceptions which are not decendant from `EventMachine::WebSocket::WebSocketError`.
126
+ Application errors are treated differently. If no `onerror` callback has been defined these errors will propagate to the EventMachine reactor, typically causing your program to terminate. If you wish to handle exceptions, simply supply an `onerror callback` and check for exceptions which are not descendant from `EM::WebSocket::WebSocketError`.
127
+
128
+ It is also possible to log all errors when developing by including the `:debug => true` option when initialising the WebSocket server.
64
129
 
65
- It is also possible to log all errors when developing by including the `:debug => true` option when initialising the WebSocket connection.
130
+ ## Emulating WebSockets in older browsers
131
+
132
+ It is possible to emulate WebSockets in older browsers using flash emulation. For example take a look at the [web-socket-js](https://github.com/gimite/web-socket-js) project.
133
+
134
+ Using flash emulation does require some minimal support from em-websocket which is enabled by default. If flash connects to the WebSocket port and requests a policy file (which it will do if it fails to receive a policy file on port 843 after a timeout), em-websocket will return one. Also see <https://github.com/igrigorik/em-websocket/issues/61> for an example policy file server which you can run on port 843.
66
135
 
67
136
  ## Examples & Projects using em-websocket
68
137
 
69
- * [Pusher](http://pusherapp.com) - Realtime client push
138
+ * [Pusher](http://pusher.com) - Realtime Messaging Service
70
139
  * [Livereload](https://github.com/mockko/livereload) - LiveReload applies CSS/JS changes to Safari or Chrome w/o reloading
71
140
  * [Twitter AMQP WebSocket Example](http://github.com/rubenfonseca/twitter-amqp-websocket-example)
72
141
  * examples/multicast.rb - broadcast all ruby tweets to all subscribers
73
142
  * examples/echo.rb - server <> client exchange via a websocket
74
-
75
- # License
76
-
77
- The MIT License - Copyright (c) 2009 Ilya Grigorik
@@ -1,8 +1,24 @@
1
1
  require File.expand_path('../../lib/em-websocket', __FILE__)
2
2
 
3
- EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080, :debug => true) do |ws|
4
- ws.onopen { ws.send "Hello Client!"}
5
- ws.onmessage { |msg| ws.send "Pong: #{msg}" }
6
- ws.onclose { puts "WebSocket closed" }
7
- ws.onerror { |e| puts "Error: #{e.message}" }
8
- end
3
+ EM.run {
4
+ EM::WebSocket.run(:host => "0.0.0.0", :port => 8080, :debug => false) do |ws|
5
+ ws.onopen { |handshake|
6
+ puts "WebSocket opened #{{
7
+ :path => handshake.path,
8
+ :query => handshake.query,
9
+ :origin => handshake.origin,
10
+ }}"
11
+
12
+ ws.send "Hello Client!"
13
+ }
14
+ ws.onmessage { |msg|
15
+ ws.send "Pong: #{msg}"
16
+ }
17
+ ws.onclose {
18
+ puts "WebSocket closed"
19
+ }
20
+ ws.onerror { |e|
21
+ puts "Error: #{e.message}"
22
+ }
23
+ end
24
+ }
@@ -1,8 +1,5 @@
1
1
  <html>
2
2
  <head>
3
- <script src='js/swfobject.js'></script>
4
- <script src='js/FABridge.js'></script>
5
- <script src='js/web_socket.js'></script>
6
3
  <script>
7
4
  function init() {
8
5
  function debug(string) {
@@ -13,9 +10,11 @@
13
10
  }
14
11
 
15
12
  var Socket = "MozWebSocket" in window ? MozWebSocket : WebSocket;
16
- var ws = new Socket("ws://localhost:8080/");
17
- ws.onmessage = function(evt) { debug("Message: " + evt.data); };
18
- ws.onclose = function() { debug("socket closed"); };
13
+ var ws = new Socket("ws://localhost:8080/foo/bar?hello=world");
14
+ ws.onmessage = function(evt) { debug("Received: " + evt.data); };
15
+ ws.onclose = function(event) {
16
+ debug("Closed - code: " + event.code + ", reason: " + event.reason + ", wasClean: " + event.wasClean);
17
+ };
19
18
  ws.onopen = function() {
20
19
  debug("connected...");
21
20
  ws.send("hello server");
@@ -4,12 +4,13 @@ require "eventmachine"
4
4
 
5
5
  %w[
6
6
  debugger websocket connection
7
+ handshake
7
8
  handshake75 handshake76 handshake04
8
9
  framing76 framing03 framing04 framing05 framing07
9
10
  close75 close03 close05 close06
10
11
  masking04
11
12
  message_processor_03 message_processor_06
12
- handler_factory handler handler75 handler76 handler03 handler05 handler06 handler07 handler08 handler13
13
+ handler handler75 handler76 handler03 handler05 handler06 handler07 handler08 handler13
13
14
  ].each do |file|
14
15
  require "em-websocket/#{file}"
15
16
  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, '')
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
@@ -1,5 +1,3 @@
1
- require 'addressable/uri'
2
-
3
1
  module EventMachine
4
2
  module WebSocket
5
3
  class Connection < EventMachine::Connection
@@ -12,26 +10,30 @@ module EventMachine
12
10
  def onclose(&blk); @onclose = blk; end
13
11
  def onerror(&blk); @onerror = blk; end
14
12
  def onmessage(&blk); @onmessage = blk; end
13
+ def onbinary(&blk); @onbinary = blk; end
15
14
  def onping(&blk); @onping = blk; end
16
15
  def onpong(&blk); @onpong = blk; end
17
16
 
18
17
  def trigger_on_message(msg)
19
- @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
20
22
  end
21
- def trigger_on_open
22
- @onopen.call if @onopen
23
+ def trigger_on_open(handshake)
24
+ @onopen.call(handshake) if defined? @onopen
23
25
  end
24
- def trigger_on_close
25
- @onclose.call if @onclose
26
+ def trigger_on_close(event = {})
27
+ @onclose.call(event) if defined? @onclose
26
28
  end
27
29
  def trigger_on_ping(data)
28
- @onping.call(data) if @onping
30
+ @onping.call(data) if defined? @onping
29
31
  end
30
32
  def trigger_on_pong(data)
31
- @onpong.call(data) if @onpong
33
+ @onpong.call(data) if defined? @onpong
32
34
  end
33
35
  def trigger_on_error(reason)
34
- return false unless @onerror
36
+ return false unless defined? @onerror
35
37
  @onerror.call(reason)
36
38
  true
37
39
  end
@@ -40,8 +42,12 @@ module EventMachine
40
42
  @options = options
41
43
  @debug = options[:debug] || false
42
44
  @secure = options[:secure] || false
45
+ @secure_proxy = options[:secure_proxy] || false
43
46
  @tls_options = options[:tls_options] || {}
44
- @data = ''
47
+ @close_timeout = options[:close_timeout]
48
+ @outbound_limit = options[:outbound_limit] || 0
49
+
50
+ @handler = nil
45
51
 
46
52
  debug [:initialize]
47
53
  end
@@ -49,17 +55,17 @@ module EventMachine
49
55
  # Use this method to close the websocket connection cleanly
50
56
  # This sends a close frame and waits for acknowlegement before closing
51
57
  # the connection
52
- def close_websocket(code = nil, body = nil)
53
- if code && !(4000..4999).include?(code)
54
- 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"
55
61
  end
56
62
 
57
- # If code not defined then set to 1000 (normal closure)
58
- code ||= 1000
59
-
60
63
  close_websocket_private(code, body)
61
64
  end
62
65
 
66
+ # Deprecated, to be removed in version 0.6
67
+ alias :close_websocket :close
68
+
63
69
  def post_init
64
70
  start_tls(@tls_options) if @secure
65
71
  end
@@ -72,22 +78,25 @@ module EventMachine
72
78
  else
73
79
  dispatch(data)
74
80
  end
75
- rescue HandshakeError => e
76
- debug [:error, e]
77
- trigger_on_error(e)
78
- # Errors during the handshake require the connection to be aborted
79
- abort
80
- rescue WSProtocolError => e
81
- debug [:error, e]
82
- trigger_on_error(e)
83
- close_websocket_private(e.code)
84
81
  rescue => e
85
82
  debug [:error, e]
86
- # These are application errors - raise unless onerror defined
87
- trigger_on_error(e) || raise(e)
83
+
88
84
  # There is no code defined for application errors, so use 3000
89
85
  # (which is reserved for frameworks)
90
- 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)
91
100
  end
92
101
 
93
102
  def unbind
@@ -101,23 +110,54 @@ module EventMachine
101
110
  end
102
111
 
103
112
  def dispatch(data)
104
- 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*\/>/)
105
116
  send_flash_cross_domain_file
106
- return false
107
117
  else
108
- debug [:inbound_headers, data]
109
- @data << data
110
- @handler = HandlerFactory.build(self, @data, @secure, @debug)
111
- unless @handler
112
- # The whole header has not been received yet.
113
- return false
118
+ @handshake ||= begin
119
+ handshake = Handshake.new(@secure || @secure_proxy)
120
+
121
+ handshake.callback { |upgrade_response, handler_klass|
122
+ debug [:accepting_ws_version, handshake.protocol_version]
123
+ debug [:upgrade_response, upgrade_response]
124
+ self.send_data(upgrade_response)
125
+ @handler = handler_klass.new(self, @debug)
126
+ @handshake = nil
127
+ trigger_on_open(handshake)
128
+ }
129
+
130
+ handshake.errback { |e|
131
+ debug [:error, e]
132
+ trigger_on_error(e)
133
+ # Handshake errors require the connection to be aborted
134
+ abort(:handshake_error)
135
+ }
136
+
137
+ handshake
114
138
  end
115
- @data = nil
116
- @handler.run
117
- return true
139
+
140
+ @handshake.receive_data(data)
118
141
  end
119
142
  end
120
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
+
121
161
  def send_flash_cross_domain_file
122
162
  file = '<?xml version="1.0"?><cross-domain-policy><allow-access-from domain="*" to-ports="*"/></cross-domain-policy>'
123
163
  debug [:cross_domain, file]
@@ -139,7 +179,7 @@ module EventMachine
139
179
  # A WebSocketError may be raised if the connection is in an opening or a
140
180
  # closing state, or if the passed in data is not valid UTF-8
141
181
  #
142
- def send(data)
182
+ def send_text(data)
143
183
  # If we're using Ruby 1.9, be pedantic about encodings
144
184
  if ENCODING_SUPPORTED
145
185
  # Also accept ascii only data in other encodings for convenience
@@ -161,7 +201,20 @@ module EventMachine
161
201
 
162
202
  # Revert data back to the original encoding (which we assume is UTF-8)
163
203
  # Doing this to avoid duping the string - there may be a better way
164
- data.force_encoding(UTF8)
204
+ data.force_encoding(UTF8) if ENCODING_SUPPORTED
205
+ return nil
206
+ end
207
+
208
+ alias :send :send_text
209
+
210
+ # Send a WebSocket binary frame.
211
+ #
212
+ def send_binary(data)
213
+ if @handler
214
+ @handler.send_frame(:binary, data)
215
+ else
216
+ raise WebSocketError, "Cannot send binary before onopen callback"
217
+ end
165
218
  end
166
219
 
167
220
  # Send a ping to the client. The client must respond with a pong.
@@ -201,14 +254,23 @@ module EventMachine
201
254
  end
202
255
  end
203
256
 
204
- def request
205
- @handler ? @handler.request : {}
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
206
263
  end
207
264
 
208
265
  def state
209
266
  @handler ? @handler.state : :handshake
210
267
  end
211
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
+
212
274
  # Returns the maximum frame size which this connection is configured to
213
275
  # accept. This can be set globally or on a per connection basis, and
214
276
  # defaults to a value of 10MB if not set.
@@ -218,24 +280,68 @@ module EventMachine
218
280
  # correct close code (1009) immediately after receiving the frame header
219
281
  #
220
282
  def max_frame_size
221
- @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
222
288
  end
223
289
 
224
290
  private
225
291
 
226
292
  # As definited in draft 06 7.2.2, some failures require that the server
227
293
  # abort the websocket connection rather than close cleanly
228
- def abort
294
+ def abort(reason)
295
+ debug [:abort, reason]
229
296
  close_connection
230
297
  end
231
298
 
232
- def close_websocket_private(code, body = nil)
299
+ def close_websocket_private(code, body)
233
300
  if @handler
234
301
  debug [:closing, code]
235
302
  @handler.close_websocket(code, body)
236
303
  else
237
304
  # The handshake hasn't completed - should be safe to terminate
238
- 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
239
345
  end
240
346
  end
241
347
  end