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.
- data/CHANGELOG.rdoc +13 -0
- data/README.md +62 -35
- data/examples/echo.rb +22 -6
- data/examples/test.html +2 -5
- data/lib/em-websocket.rb +2 -1
- data/lib/em-websocket/connection.rb +38 -25
- data/lib/em-websocket/handler.rb +32 -14
- data/lib/em-websocket/handler03.rb +0 -1
- data/lib/em-websocket/handler05.rb +0 -1
- data/lib/em-websocket/handler06.rb +0 -1
- data/lib/em-websocket/handler07.rb +0 -1
- data/lib/em-websocket/handler08.rb +0 -1
- data/lib/em-websocket/handler13.rb +0 -1
- data/lib/em-websocket/handshake.rb +137 -0
- data/lib/em-websocket/handshake04.rb +13 -15
- data/lib/em-websocket/handshake75.rb +4 -7
- data/lib/em-websocket/handshake76.rb +11 -14
- data/lib/em-websocket/version.rb +1 -1
- data/lib/em-websocket/websocket.rb +17 -15
- data/spec/helper.rb +21 -3
- data/spec/integration/common_spec.rb +57 -54
- data/spec/integration/draft03_spec.rb +2 -0
- data/spec/integration/draft13_spec.rb +2 -0
- data/spec/integration/draft75_spec.rb +3 -1
- data/spec/integration/draft76_spec.rb +3 -1
- data/spec/integration/shared_examples.rb +26 -0
- data/spec/unit/{handler_spec.rb → handshake_spec.rb} +89 -55
- metadata +112 -127
- data/examples/flash_policy_file_server.rb +0 -21
- data/examples/js/FABridge.js +0 -604
- data/examples/js/WebSocketMain.swf +0 -0
- data/examples/js/swfobject.js +0 -4
- data/examples/js/web_socket.js +0 -312
- data/lib/em-websocket/handler_factory.rb +0 -109
data/CHANGELOG.rdoc
CHANGED
@@ -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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
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
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
72
|
+
There are two kinds of errors that need to be handled -- WebSocket protocol errors and errors in application code.
|
52
73
|
|
53
|
-
|
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
|
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
|
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
|
-
|
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://
|
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
|
data/examples/echo.rb
CHANGED
@@ -1,8 +1,24 @@
|
|
1
1
|
require File.expand_path('../../lib/em-websocket', __FILE__)
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
+
}
|
data/examples/test.html
CHANGED
@@ -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("
|
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...");
|
data/lib/em-websocket.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
116
|
-
@
|
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
|
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
|
data/lib/em-websocket/handler.rb
CHANGED
@@ -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,
|
9
|
-
@connection
|
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
|
-
|
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)
|