em-websocket 0.3.5 → 0.3.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +6 -0
- data/examples/echo.rb +2 -2
- data/examples/ping.rb +24 -0
- data/lib/em-websocket/connection.rb +45 -0
- data/lib/em-websocket/handler.rb +10 -0
- data/lib/em-websocket/message_processor_03.rb +7 -1
- data/lib/em-websocket/message_processor_06.rb +7 -1
- data/lib/em-websocket/version.rb +1 -1
- data/spec/helper.rb +0 -5
- data/spec/integration/draft13_spec.rb +40 -11
- data/spec/integration/draft76_spec.rb +81 -91
- data/spec/unit/handler_spec.rb +5 -0
- metadata +18 -17
data/CHANGELOG.rdoc
CHANGED
data/examples/echo.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
require 'lib/em-websocket'
|
1
|
+
require File.expand_path('../../lib/em-websocket', __FILE__)
|
2
2
|
|
3
3
|
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080, :debug => true) do |ws|
|
4
4
|
ws.onopen { ws.send "Hello Client!"}
|
5
5
|
ws.onmessage { |msg| ws.send "Pong: #{msg}" }
|
6
6
|
ws.onclose { puts "WebSocket closed" }
|
7
7
|
ws.onerror { |e| puts "Error: #{e.message}" }
|
8
|
-
end
|
8
|
+
end
|
data/examples/ping.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.expand_path('../../lib/em-websocket', __FILE__)
|
2
|
+
|
3
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080, :debug => false) do |ws|
|
4
|
+
timer = nil
|
5
|
+
ws.onopen {
|
6
|
+
puts "Ping supported: #{ws.pingable?}"
|
7
|
+
timer = EM.add_periodic_timer(1) {
|
8
|
+
p ["Sent ping", ws.ping('hello')]
|
9
|
+
}
|
10
|
+
}
|
11
|
+
ws.onpong { |value|
|
12
|
+
puts "Received pong: #{value}"
|
13
|
+
}
|
14
|
+
ws.onping { |value|
|
15
|
+
puts "Received ping: #{value}"
|
16
|
+
}
|
17
|
+
ws.onclose {
|
18
|
+
EM.cancel_timer(timer)
|
19
|
+
puts "WebSocket closed"
|
20
|
+
}
|
21
|
+
ws.onerror { |e|
|
22
|
+
puts "Error: #{e.message}"
|
23
|
+
}
|
24
|
+
end
|
@@ -10,6 +10,8 @@ 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 onping(&blk); @onping = blk; end
|
14
|
+
def onpong(&blk); @onpong = blk; end
|
13
15
|
|
14
16
|
def trigger_on_message(msg)
|
15
17
|
@onmessage.call(msg) if @onmessage
|
@@ -20,6 +22,12 @@ module EventMachine
|
|
20
22
|
def trigger_on_close
|
21
23
|
@onclose.call if @onclose
|
22
24
|
end
|
25
|
+
def trigger_on_ping(data)
|
26
|
+
@onping.call(data) if @onping
|
27
|
+
end
|
28
|
+
def trigger_on_pong(data)
|
29
|
+
@onpong.call(data) if @onpong
|
30
|
+
end
|
23
31
|
def trigger_on_error(reason)
|
24
32
|
return false unless @onerror
|
25
33
|
@onerror.call(reason)
|
@@ -140,6 +148,43 @@ module EventMachine
|
|
140
148
|
end
|
141
149
|
end
|
142
150
|
|
151
|
+
# Send a ping to the client. The client must respond with a pong.
|
152
|
+
#
|
153
|
+
# In the case that the client is running a WebSocket draft < 01, false
|
154
|
+
# is returned since ping & pong are not supported
|
155
|
+
#
|
156
|
+
def ping(body = '')
|
157
|
+
if @handler
|
158
|
+
@handler.pingable? ? @handler.send_frame(:ping, body) && true : false
|
159
|
+
else
|
160
|
+
raise WebSocketError, "Cannot ping before onopen callback"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Send an unsolicited pong message, as allowed by the protocol. The
|
165
|
+
# client is not expected to respond to this message.
|
166
|
+
#
|
167
|
+
# em-websocket automatically takes care of sending pong replies to
|
168
|
+
# incoming ping messages, as the protocol demands.
|
169
|
+
#
|
170
|
+
def pong(body = '')
|
171
|
+
if @handler
|
172
|
+
@handler.pingable? ? @handler.send_frame(:pong, body) && true : false
|
173
|
+
else
|
174
|
+
raise WebSocketError, "Cannot ping before onopen callback"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Test whether the connection is pingable (i.e. the WebSocket draft in
|
179
|
+
# use is >= 01)
|
180
|
+
def pingable?
|
181
|
+
if @handler
|
182
|
+
@handler.pingable?
|
183
|
+
else
|
184
|
+
raise WebSocketError, "Cannot test whether pingable before onopen callback"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
143
188
|
def request
|
144
189
|
@handler ? @handler.request : {}
|
145
190
|
end
|
data/lib/em-websocket/handler.rb
CHANGED
@@ -22,8 +22,9 @@ module EventMachine
|
|
22
22
|
when :ping
|
23
23
|
# Pong back the same data
|
24
24
|
send_frame(:pong, application_data)
|
25
|
+
@connection.trigger_on_ping(application_data)
|
25
26
|
when :pong
|
26
|
-
|
27
|
+
@connection.trigger_on_pong(application_data)
|
27
28
|
when :text
|
28
29
|
if application_data.respond_to?(:force_encoding)
|
29
30
|
application_data.force_encoding("UTF-8")
|
@@ -33,6 +34,11 @@ module EventMachine
|
|
33
34
|
@connection.trigger_on_message(application_data)
|
34
35
|
end
|
35
36
|
end
|
37
|
+
|
38
|
+
# Ping & Pong supported
|
39
|
+
def pingable?
|
40
|
+
true
|
41
|
+
end
|
36
42
|
end
|
37
43
|
end
|
38
44
|
end
|
@@ -35,8 +35,9 @@ module EventMachine
|
|
35
35
|
when :ping
|
36
36
|
# Pong back the same data
|
37
37
|
send_frame(:pong, application_data)
|
38
|
+
@connection.trigger_on_ping(application_data)
|
38
39
|
when :pong
|
39
|
-
|
40
|
+
@connection.trigger_on_pong(application_data)
|
40
41
|
when :text
|
41
42
|
if application_data.respond_to?(:force_encoding)
|
42
43
|
application_data.force_encoding("UTF-8")
|
@@ -46,6 +47,11 @@ module EventMachine
|
|
46
47
|
@connection.trigger_on_message(application_data)
|
47
48
|
end
|
48
49
|
end
|
50
|
+
|
51
|
+
# Ping & Pong supported
|
52
|
+
def pingable?
|
53
|
+
true
|
54
|
+
end
|
49
55
|
end
|
50
56
|
end
|
51
57
|
end
|
data/lib/em-websocket/version.rb
CHANGED
data/spec/helper.rb
CHANGED
@@ -132,11 +132,6 @@ def format_response(r)
|
|
132
132
|
data
|
133
133
|
end
|
134
134
|
|
135
|
-
def handler(request, secure = false)
|
136
|
-
connection = Object.new
|
137
|
-
EM::WebSocket::HandlerFactory.build(connection, format_request(request), secure)
|
138
|
-
end
|
139
|
-
|
140
135
|
RSpec::Matchers.define :send_handshake do |response|
|
141
136
|
match do |actual|
|
142
137
|
actual.handshake.lines.sort == format_response(response).lines.sort
|
@@ -47,19 +47,48 @@ describe "draft13" do
|
|
47
47
|
|
48
48
|
it "should send back the correct handshake response" do
|
49
49
|
em {
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
connection.
|
58
|
-
|
59
|
-
|
50
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { }
|
51
|
+
|
52
|
+
# Create a fake client which sends draft 13 handshake
|
53
|
+
connection = EM.connect('0.0.0.0', 12345, Draft07FakeWebSocketClient)
|
54
|
+
connection.send_data(format_request(@request))
|
55
|
+
|
56
|
+
connection.onopen {
|
57
|
+
connection.handshake_response.lines.sort.
|
58
|
+
should == format_response(@response).lines.sort
|
59
|
+
done
|
60
|
+
}
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
# TODO: This test would be much nicer with a real websocket client...
|
65
|
+
it "should support sending pings and binding to onpong" do
|
66
|
+
em {
|
67
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
68
|
+
ws.onopen {
|
69
|
+
ws.should be_pingable
|
70
|
+
EM.next_tick {
|
71
|
+
ws.ping('hello').should == true
|
72
|
+
}
|
73
|
+
|
74
|
+
}
|
75
|
+
ws.onpong { |data|
|
76
|
+
data.should == 'hello'
|
60
77
|
done
|
61
78
|
}
|
62
|
-
|
79
|
+
}
|
80
|
+
|
81
|
+
# Create a fake client which sends draft 13 handshake
|
82
|
+
connection = EM.connect('0.0.0.0', 12345, Draft07FakeWebSocketClient)
|
83
|
+
connection.send_data(format_request(@request))
|
84
|
+
|
85
|
+
# Confusing, fake onmessage means any data after the handshake
|
86
|
+
connection.onmessage { |data|
|
87
|
+
# This is what a ping looks like
|
88
|
+
data.should == "\x89\x05hello"
|
89
|
+
# This is what a pong looks like
|
90
|
+
connection.send_data("\x8a\x05hello")
|
91
|
+
}
|
63
92
|
}
|
64
93
|
end
|
65
94
|
end
|
@@ -50,97 +50,89 @@ describe "WebSocket server draft76" do
|
|
50
50
|
|
51
51
|
it "should send back the correct handshake response" do
|
52
52
|
em {
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
connection.
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
}
|
65
|
-
end
|
53
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { }
|
54
|
+
|
55
|
+
# Create a fake client which sends draft 76 handshake
|
56
|
+
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
57
|
+
connection.send_data(format_request(@request))
|
58
|
+
|
59
|
+
connection.onopen {
|
60
|
+
connection.handshake_response.lines.sort.
|
61
|
+
should == format_response(@response).lines.sort
|
62
|
+
done
|
63
|
+
}
|
66
64
|
}
|
67
65
|
end
|
68
66
|
|
69
67
|
it "should send closing frame back and close the connection after recieving closing frame" do
|
70
68
|
em {
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
connection.
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
connection.
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
}
|
90
|
-
end
|
69
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { }
|
70
|
+
|
71
|
+
# Create a fake client which sends draft 76 handshake
|
72
|
+
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
73
|
+
connection.send_data(format_request(@request))
|
74
|
+
|
75
|
+
# Send closing frame after handshake complete
|
76
|
+
connection.onopen {
|
77
|
+
connection.send_data(EM::WebSocket::Handler76::TERMINATE_STRING)
|
78
|
+
}
|
79
|
+
|
80
|
+
# Check that this causes a termination string to be returned and the
|
81
|
+
# connection close
|
82
|
+
connection.onclose {
|
83
|
+
connection.packets[0].should ==
|
84
|
+
EM::WebSocket::Handler76::TERMINATE_STRING
|
85
|
+
done
|
86
|
+
}
|
91
87
|
}
|
92
88
|
end
|
93
89
|
|
94
90
|
it "should ignore any data received after the closing frame" do
|
95
91
|
em {
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
fail
|
101
|
-
}
|
102
|
-
}
|
103
|
-
|
104
|
-
# Create a fake client which sends draft 76 handshake
|
105
|
-
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
106
|
-
connection.send_data(format_request(@request))
|
107
|
-
|
108
|
-
# Send closing frame after handshake complete, followed by another msg
|
109
|
-
connection.onopen {
|
110
|
-
connection.send_data(EM::WebSocket::Handler76::TERMINATE_STRING)
|
111
|
-
connection.send('foobar')
|
92
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
93
|
+
# Fail if foobar message is received
|
94
|
+
ws.onmessage { |msg|
|
95
|
+
fail
|
112
96
|
}
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
97
|
+
}
|
98
|
+
|
99
|
+
# Create a fake client which sends draft 76 handshake
|
100
|
+
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
101
|
+
connection.send_data(format_request(@request))
|
102
|
+
|
103
|
+
# Send closing frame after handshake complete, followed by another msg
|
104
|
+
connection.onopen {
|
105
|
+
connection.send_data(EM::WebSocket::Handler76::TERMINATE_STRING)
|
106
|
+
connection.send('foobar')
|
107
|
+
}
|
108
|
+
|
109
|
+
connection.onclose {
|
110
|
+
done
|
111
|
+
}
|
118
112
|
}
|
119
113
|
end
|
120
114
|
|
121
115
|
it "should accept null bytes within the frame after a line return" do
|
122
116
|
em {
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
msg.should == "\n\000"
|
127
|
-
}
|
117
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
118
|
+
ws.onmessage { |msg|
|
119
|
+
msg.should == "\n\000"
|
128
120
|
}
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
121
|
+
}
|
122
|
+
|
123
|
+
# Create a fake client which sends draft 76 handshake
|
124
|
+
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
125
|
+
connection.send_data(format_request(@request))
|
126
|
+
|
127
|
+
# Send closing frame after handshake complete
|
128
|
+
connection.onopen {
|
129
|
+
connection.send_data("\000\n\000\377")
|
130
|
+
connection.send_data(EM::WebSocket::Handler76::TERMINATE_STRING)
|
131
|
+
}
|
132
|
+
|
133
|
+
connection.onclose {
|
134
|
+
done
|
135
|
+
}
|
144
136
|
}
|
145
137
|
end
|
146
138
|
|
@@ -205,25 +197,23 @@ describe "WebSocket server draft76" do
|
|
205
197
|
|
206
198
|
it "should handle handshake request split into two TCP packets" do
|
207
199
|
em {
|
208
|
-
|
209
|
-
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { }
|
210
|
-
|
211
|
-
# Create a fake client which sends draft 76 handshake
|
212
|
-
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
213
|
-
data = format_request(@request)
|
214
|
-
# Sends first half of the request
|
215
|
-
connection.send_data(data[0...(data.length / 2)])
|
200
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { }
|
216
201
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
202
|
+
# Create a fake client which sends draft 76 handshake
|
203
|
+
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
204
|
+
data = format_request(@request)
|
205
|
+
# Sends first half of the request
|
206
|
+
connection.send_data(data[0...(data.length / 2)])
|
207
|
+
|
208
|
+
connection.onopen {
|
209
|
+
connection.handshake_response.lines.sort.
|
210
|
+
should == format_response(@response).lines.sort
|
211
|
+
done
|
212
|
+
}
|
222
213
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
end
|
214
|
+
EM.add_timer(0.1) do
|
215
|
+
# Sends second half of the request
|
216
|
+
connection.send_data(data[(data.length / 2)..-1])
|
227
217
|
end
|
228
218
|
}
|
229
219
|
end
|
data/spec/unit/handler_spec.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
describe "EventMachine::WebSocket::Handler" do
|
4
|
+
def handler(request, secure = false)
|
5
|
+
connection = Object.new
|
6
|
+
EM::WebSocket::HandlerFactory.build(connection, format_request(request), secure)
|
7
|
+
end
|
8
|
+
|
4
9
|
before :each do
|
5
10
|
@request = {
|
6
11
|
:port => 80,
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: em-websocket
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,11 +10,11 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2011-
|
13
|
+
date: 2011-12-23 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: eventmachine
|
17
|
-
requirement: &
|
17
|
+
requirement: &70364656209880 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: 0.12.9
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *70364656209880
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: addressable
|
28
|
-
requirement: &
|
28
|
+
requirement: &70364656231520 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ! '>='
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: 2.1.1
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *70364656231520
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: em-spec
|
39
|
-
requirement: &
|
39
|
+
requirement: &70364656231060 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ~>
|
@@ -44,10 +44,10 @@ dependencies:
|
|
44
44
|
version: 0.2.5
|
45
45
|
type: :development
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *70364656231060
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: eventmachine
|
50
|
-
requirement: &
|
50
|
+
requirement: &70364656230580 !ruby/object:Gem::Requirement
|
51
51
|
none: false
|
52
52
|
requirements:
|
53
53
|
- - ~>
|
@@ -55,10 +55,10 @@ dependencies:
|
|
55
55
|
version: 0.12.10
|
56
56
|
type: :development
|
57
57
|
prerelease: false
|
58
|
-
version_requirements: *
|
58
|
+
version_requirements: *70364656230580
|
59
59
|
- !ruby/object:Gem::Dependency
|
60
60
|
name: em-http-request
|
61
|
-
requirement: &
|
61
|
+
requirement: &70364656230100 !ruby/object:Gem::Requirement
|
62
62
|
none: false
|
63
63
|
requirements:
|
64
64
|
- - ~>
|
@@ -66,10 +66,10 @@ dependencies:
|
|
66
66
|
version: 0.2.6
|
67
67
|
type: :development
|
68
68
|
prerelease: false
|
69
|
-
version_requirements: *
|
69
|
+
version_requirements: *70364656230100
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: rspec
|
72
|
-
requirement: &
|
72
|
+
requirement: &70364656229640 !ruby/object:Gem::Requirement
|
73
73
|
none: false
|
74
74
|
requirements:
|
75
75
|
- - ~>
|
@@ -77,10 +77,10 @@ dependencies:
|
|
77
77
|
version: 2.6.0
|
78
78
|
type: :development
|
79
79
|
prerelease: false
|
80
|
-
version_requirements: *
|
80
|
+
version_requirements: *70364656229640
|
81
81
|
- !ruby/object:Gem::Dependency
|
82
82
|
name: rake
|
83
|
-
requirement: &
|
83
|
+
requirement: &70364656229260 !ruby/object:Gem::Requirement
|
84
84
|
none: false
|
85
85
|
requirements:
|
86
86
|
- - ! '>='
|
@@ -88,7 +88,7 @@ dependencies:
|
|
88
88
|
version: '0'
|
89
89
|
type: :development
|
90
90
|
prerelease: false
|
91
|
-
version_requirements: *
|
91
|
+
version_requirements: *70364656229260
|
92
92
|
description: EventMachine based WebSocket server
|
93
93
|
email:
|
94
94
|
- ilya@igvita.com
|
@@ -110,6 +110,7 @@ files:
|
|
110
110
|
- examples/js/swfobject.js
|
111
111
|
- examples/js/web_socket.js
|
112
112
|
- examples/multicast.rb
|
113
|
+
- examples/ping.rb
|
113
114
|
- examples/test.html
|
114
115
|
- lib/em-websocket.rb
|
115
116
|
- lib/em-websocket/close03.rb
|
@@ -173,7 +174,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
173
174
|
version: '0'
|
174
175
|
requirements: []
|
175
176
|
rubyforge_project: em-websocket
|
176
|
-
rubygems_version: 1.8.
|
177
|
+
rubygems_version: 1.8.10
|
177
178
|
signing_key:
|
178
179
|
specification_version: 3
|
179
180
|
summary: EventMachine based WebSocket server
|