em-websocket 0.3.8 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,18 @@
1
1
  = Changelog
2
2
 
3
+ == 0.4.0 / ?
4
+
5
+ - new features:
6
+ - on_open handler is now passed a handshake object which exposes the request headers, path, and query parameters
7
+ - Easily access the protocol version via Handshake#protocol_version
8
+ - Easily access the origin via Handshake#origin
9
+
10
+ - changed:
11
+ - Removed Connection#request - change to using handshake passed to on_open
12
+
13
+ - internals:
14
+ - Uses the http_parser.rb gem
15
+
3
16
  == 0.3.8 / 2012-07-12
4
17
 
5
18
  - bug fixes:
data/README.md CHANGED
@@ -7,66 +7,93 @@ EventMachine based, async, Ruby WebSocket server. Take a look at examples direct
7
7
  ## Simple server example
8
8
 
9
9
  ```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
10
+ require 'em-websocket'
11
+
12
+ EM.run {
13
+ EM::WebSocket.run(:host => "0.0.0.0", :port => 8080) do |ws|
14
+ ws.onopen { |handshake|
15
+ puts "WebSocket connection open"
16
+
17
+ # Access properties on the EM::WebSocket::Handshake object, e.g.
18
+ # path, query_string, origin, headers
19
+
20
+ # Publish message to the client
21
+ ws.send "Hello Client, you connected to #{handshake.path}"
22
+ }
23
+
24
+ ws.onclose { puts "Connection closed" }
25
+
26
+ ws.onmessage { |msg|
27
+ puts "Recieved message: #{msg}"
28
+ ws.send "Pong: #{msg}"
29
+ }
30
+ end
26
31
  }
27
32
  ```
28
33
 
29
34
  ## Secure server
30
35
 
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
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
32
37
 
33
- For example,
38
+ **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
39
 
35
40
  ```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
- }
41
+ EM::WebSocket.start({
42
+ :host => "0.0.0.0",
43
+ :port => 443,
44
+ :secure => true,
45
+ :tls_options => {
46
+ :private_key_file => "/private/key",
47
+ :cert_chain_file => "/ssl/certificate"
48
+ }
49
+ }) do |ws|
50
+ # ...
51
+ end
52
+ ```
53
+
54
+ ## Running behind an SSL Proxy/Terminator, like Stunnel
55
+
56
+ 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.
57
+
58
+ 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).
59
+
60
+ ```ruby
61
+ EM::WebSocket.start({
62
+ :host => "0.0.0.0",
63
+ :port => 8080,
64
+ :secure_proxy => true
44
65
  }) do |ws|
45
- ...
66
+ # ...
46
67
  end
47
68
  ```
48
69
 
49
70
  ## Handling errors
50
71
 
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:
72
+ There are two kinds of errors that need to be handled -- WebSocket protocol errors and errors in application code.
52
73
 
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.
74
+ 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
75
 
55
76
  ```ruby
56
77
  ws.onerror { |error|
57
- if e.kind_of?(EM::WebSocket::WebSocketError)
58
- ...
78
+ if error.kind_of?(EM::WebSocket::WebSocketError)
79
+ # ...
59
80
  end
60
81
  }
61
82
  ```
62
83
 
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`.
84
+ 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`.
85
+
86
+ It is also possible to log all errors when developing by including the `:debug => true` option when initialising the WebSocket server.
87
+
88
+ ## Emulating WebSockets in older browsers
89
+
90
+ 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.
64
91
 
65
- It is also possible to log all errors when developing by including the `:debug => true` option when initialising the WebSocket connection.
92
+ 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
93
 
67
94
  ## Examples & Projects using em-websocket
68
95
 
69
- * [Pusher](http://pusherapp.com) - Realtime client push
96
+ * [Pusher](http://pusher.com) - Realtime Messaging Service
70
97
  * [Livereload](https://github.com/mockko/livereload) - LiveReload applies CSS/JS changes to Safari or Chrome w/o reloading
71
98
  * [Twitter AMQP WebSocket Example](http://github.com/rubenfonseca/twitter-amqp-websocket-example)
72
99
  * examples/multicast.rb - broadcast all ruby tweets to all subscribers
@@ -74,4 +101,4 @@ It is also possible to log all errors when developing by including the `:debug =
74
101
 
75
102
  # License
76
103
 
77
- The MIT License - Copyright (c) 2009 Ilya Grigorik
104
+ 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,8 +10,8 @@
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); };
13
+ var ws = new Socket("ws://localhost:8080/foo/bar?hello=world");
14
+ ws.onmessage = function(evt) { debug("Received: " + evt.data); };
18
15
  ws.onclose = function() { debug("socket closed"); };
19
16
  ws.onopen = function() {
20
17
  debug("connected...");
@@ -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
@@ -1,5 +1,3 @@
1
- require 'addressable/uri'
2
-
3
1
  module EventMachine
4
2
  module WebSocket
5
3
  class Connection < EventMachine::Connection
@@ -18,8 +16,8 @@ module EventMachine
18
16
  def trigger_on_message(msg)
19
17
  @onmessage.call(msg) if @onmessage
20
18
  end
21
- def trigger_on_open
22
- @onopen.call if @onopen
19
+ def trigger_on_open(handshake)
20
+ @onopen.call(handshake) if @onopen
23
21
  end
24
22
  def trigger_on_close
25
23
  @onclose.call if @onclose
@@ -40,8 +38,8 @@ module EventMachine
40
38
  @options = options
41
39
  @debug = options[:debug] || false
42
40
  @secure = options[:secure] || false
41
+ @secure_proxy = options[:secure_proxy] || false
43
42
  @tls_options = options[:tls_options] || {}
44
- @data = ''
45
43
 
46
44
  debug [:initialize]
47
45
  end
@@ -72,11 +70,6 @@ module EventMachine
72
70
  else
73
71
  dispatch(data)
74
72
  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
73
  rescue WSProtocolError => e
81
74
  debug [:error, e]
82
75
  trigger_on_error(e)
@@ -103,18 +96,30 @@ module EventMachine
103
96
  def dispatch(data)
104
97
  if data.match(/\A<policy-file-request\s*\/>/)
105
98
  send_flash_cross_domain_file
106
- return false
107
99
  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
100
+ @handshake ||= begin
101
+ handshake = Handshake.new(@secure || @secure_proxy)
102
+
103
+ handshake.callback { |upgrade_response, handler_klass|
104
+ debug [:accepting_ws_version, handshake.protocol_version]
105
+ debug [:upgrade_response, upgrade_response]
106
+ self.send_data(upgrade_response)
107
+ @handler = handler_klass.new(self, @debug)
108
+ @handshake = nil
109
+ trigger_on_open(handshake)
110
+ }
111
+
112
+ handshake.errback { |e|
113
+ debug [:error, e]
114
+ trigger_on_error(e)
115
+ # Handshake errors require the connection to be aborted
116
+ abort
117
+ }
118
+
119
+ handshake
114
120
  end
115
- @data = nil
116
- @handler.run
117
- return true
121
+
122
+ @handshake.receive_data(data)
118
123
  end
119
124
  end
120
125
 
@@ -139,7 +144,7 @@ module EventMachine
139
144
  # A WebSocketError may be raised if the connection is in an opening or a
140
145
  # closing state, or if the passed in data is not valid UTF-8
141
146
  #
142
- def send(data)
147
+ def send_text(data)
143
148
  # If we're using Ruby 1.9, be pedantic about encodings
144
149
  if ENCODING_SUPPORTED
145
150
  # Also accept ascii only data in other encodings for convenience
@@ -165,6 +170,18 @@ module EventMachine
165
170
  return nil
166
171
  end
167
172
 
173
+ alias :send :send_text
174
+
175
+ # Send a WebSocket binary frame.
176
+ #
177
+ def send_binary(data)
178
+ if @handler
179
+ @handler.send_frame(:binary, data)
180
+ else
181
+ raise WebSocketError, "Cannot send binary before onopen callback"
182
+ end
183
+ end
184
+
168
185
  # Send a ping to the client. The client must respond with a pong.
169
186
  #
170
187
  # In the case that the client is running a WebSocket draft < 01, false
@@ -202,10 +219,6 @@ module EventMachine
202
219
  end
203
220
  end
204
221
 
205
- def request
206
- @handler ? @handler.request : {}
207
- end
208
-
209
222
  def state
210
223
  @handler ? @handler.state : :handshake
211
224
  end
@@ -1,26 +1,44 @@
1
1
  module EventMachine
2
2
  module WebSocket
3
3
  class Handler
4
+ def self.klass_factory(version)
5
+ handler_klass = case version
6
+ when 75
7
+ Handler75
8
+ when 76
9
+ Handler76
10
+ when 1..3
11
+ # We'll use handler03 - I believe they're all compatible
12
+ Handler03
13
+ when 5
14
+ Handler05
15
+ when 6
16
+ Handler06
17
+ when 7
18
+ Handler07
19
+ when 8
20
+ # drafts 9, 10, 11 and 12 should never change the version
21
+ # number as they are all the same as version 08.
22
+ Handler08
23
+ when 13
24
+ # drafts 13 to 17 all identify as version 13 as they are
25
+ # only minor changes or text changes.
26
+ Handler13
27
+ else
28
+ # According to spec should abort the connection
29
+ raise HandshakeError, "Protocol version #{version} not supported"
30
+ end
31
+ end
32
+
4
33
  include Debugger
5
34
 
6
35
  attr_reader :request, :state
7
36
 
8
- def initialize(connection, request, debug = false)
9
- @connection, @request = connection, request
37
+ def initialize(connection, debug = false)
38
+ @connection = connection
10
39
  @debug = debug
11
- @state = :handshake
12
- initialize_framing
13
- end
14
-
15
- def run
16
- @connection.send_data handshake
17
40
  @state = :connected
18
- @connection.trigger_on_open
19
- end
20
-
21
- # Handshake response
22
- def handshake
23
- # Implemented in subclass
41
+ initialize_framing
24
42
  end
25
43
 
26
44
  def receive_data(data)
@@ -1,7 +1,6 @@
1
1
  module EventMachine
2
2
  module WebSocket
3
3
  class Handler03 < Handler
4
- include Handshake76
5
4
  include Framing03
6
5
  include MessageProcessor03
7
6
  include Close03
@@ -1,7 +1,6 @@
1
1
  module EventMachine
2
2
  module WebSocket
3
3
  class Handler05 < Handler
4
- include Handshake04
5
4
  include Framing05
6
5
  include MessageProcessor03
7
6
  include Close05
@@ -1,7 +1,6 @@
1
1
  module EventMachine
2
2
  module WebSocket
3
3
  class Handler06 < Handler
4
- include Handshake04
5
4
  include Framing05
6
5
  include MessageProcessor06
7
6
  include Close06
@@ -1,7 +1,6 @@
1
1
  module EventMachine
2
2
  module WebSocket
3
3
  class Handler07 < Handler
4
- include Handshake04
5
4
  include Framing07
6
5
  include MessageProcessor06
7
6
  include Close06
@@ -1,7 +1,6 @@
1
1
  module EventMachine
2
2
  module WebSocket
3
3
  class Handler08 < Handler
4
- include Handshake04
5
4
  include Framing07
6
5
  include MessageProcessor06
7
6
  include Close06
@@ -1,7 +1,6 @@
1
1
  module EventMachine
2
2
  module WebSocket
3
3
  class Handler13 < Handler
4
- include Handshake04
5
4
  include Framing07
6
5
  include MessageProcessor06
7
6
  include Close06