em-websocket 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. data/CHANGELOG.rdoc +10 -0
  2. data/README.md +16 -0
  3. data/examples/test.html +11 -8
  4. data/lib/em-websocket.rb +6 -3
  5. data/lib/em-websocket/close03.rb +11 -0
  6. data/lib/em-websocket/close05.rb +11 -0
  7. data/lib/em-websocket/close06.rb +16 -0
  8. data/lib/em-websocket/close75.rb +10 -0
  9. data/lib/em-websocket/connection.rb +58 -32
  10. data/lib/em-websocket/framing03.rb +9 -30
  11. data/lib/em-websocket/framing04.rb +15 -0
  12. data/lib/em-websocket/framing05.rb +157 -0
  13. data/lib/em-websocket/framing76.rb +5 -6
  14. data/lib/em-websocket/handler.rb +2 -4
  15. data/lib/em-websocket/handler03.rb +2 -6
  16. data/lib/em-websocket/handler05.rb +10 -0
  17. data/lib/em-websocket/handler06.rb +10 -0
  18. data/lib/em-websocket/handler75.rb +1 -0
  19. data/lib/em-websocket/handler76.rb +1 -0
  20. data/lib/em-websocket/handler_factory.rb +41 -22
  21. data/lib/em-websocket/handshake04.rb +35 -0
  22. data/lib/em-websocket/handshake75.rb +4 -4
  23. data/lib/em-websocket/handshake76.rb +8 -8
  24. data/lib/em-websocket/masking04.rb +27 -0
  25. data/lib/em-websocket/message_processor_03.rb +33 -0
  26. data/lib/em-websocket/message_processor_06.rb +46 -0
  27. data/lib/em-websocket/version.rb +1 -1
  28. data/spec/helper.rb +54 -2
  29. data/spec/integration/common_spec.rb +115 -0
  30. data/spec/integration/draft03_spec.rb +26 -11
  31. data/spec/integration/draft05_spec.rb +45 -0
  32. data/spec/integration/draft06_spec.rb +79 -0
  33. data/spec/integration/draft75_spec.rb +115 -0
  34. data/spec/integration/draft76_spec.rb +25 -10
  35. data/spec/integration/shared_examples.rb +62 -0
  36. data/spec/unit/framing_spec.rb +55 -0
  37. data/spec/unit/masking_spec.rb +18 -0
  38. metadata +29 -33
  39. data/spec/websocket_spec.rb +0 -210
@@ -1,4 +1,5 @@
1
1
  require 'helper'
2
+ require 'integration/shared_examples'
2
3
 
3
4
  describe "WebSocket server draft76" do
4
5
  before :each do
@@ -30,6 +31,20 @@ describe "WebSocket server draft76" do
30
31
  }
31
32
  end
32
33
 
34
+ it_behaves_like "a websocket server" do
35
+ def start_server
36
+ EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
37
+ yield ws
38
+ }
39
+ end
40
+
41
+ def start_client
42
+ client = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
43
+ client.send_data(format_request(@request))
44
+ yield client if block_given?
45
+ end
46
+ end
47
+
33
48
  it "should send back the correct handshake response" do
34
49
  EM.run do
35
50
  EM.add_timer(0.1) do
@@ -39,7 +54,7 @@ describe "WebSocket server draft76" do
39
54
  connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
40
55
  connection.send_data(format_request(@request))
41
56
 
42
- connection.onopen = lambda {
57
+ connection.onopen {
43
58
  connection.handshake_response.lines.sort.
44
59
  should == format_response(@response).lines.sort
45
60
  EM.stop
@@ -58,13 +73,13 @@ describe "WebSocket server draft76" do
58
73
  connection.send_data(format_request(@request))
59
74
 
60
75
  # Send closing frame after handshake complete
61
- connection.onopen = lambda {
76
+ connection.onopen {
62
77
  connection.send_data(EM::WebSocket::Handler76::TERMINATE_STRING)
63
78
  }
64
79
 
65
80
  # Check that this causes a termination string to be returned and the
66
81
  # connection close
67
- connection.onclose = lambda {
82
+ connection.onclose {
68
83
  connection.packets[0].should ==
69
84
  EM::WebSocket::Handler76::TERMINATE_STRING
70
85
  EM.stop
@@ -88,12 +103,12 @@ describe "WebSocket server draft76" do
88
103
  connection.send_data(format_request(@request))
89
104
 
90
105
  # Send closing frame after handshake complete, followed by another msg
91
- connection.onopen = lambda {
106
+ connection.onopen {
92
107
  connection.send_data(EM::WebSocket::Handler76::TERMINATE_STRING)
93
108
  connection.send('foobar')
94
109
  }
95
110
 
96
- connection.onclose = lambda {
111
+ connection.onclose {
97
112
  EM.stop
98
113
  }
99
114
  end
@@ -114,12 +129,12 @@ describe "WebSocket server draft76" do
114
129
  connection.send_data(format_request(@request))
115
130
 
116
131
  # Send closing frame after handshake complete
117
- connection.onopen = lambda {
132
+ connection.onopen {
118
133
  connection.send_data("\000\n\000\377")
119
134
  connection.send_data(EM::WebSocket::Handler76::TERMINATE_STRING)
120
135
  }
121
136
 
122
- connection.onclose = lambda {
137
+ connection.onclose {
123
138
  EM.stop
124
139
  }
125
140
  end
@@ -144,7 +159,7 @@ describe "WebSocket server draft76" do
144
159
  # 1180591620717411303296 bytes. Such a message would previously cause
145
160
  # a "bignum too big to convert into `long'" error.
146
161
  # However it is clearly unreasonable and should be rejected.
147
- client.onopen = lambda {
162
+ client.onopen {
148
163
  client.send_data("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00")
149
164
  }
150
165
  end
@@ -164,7 +179,7 @@ describe "WebSocket server draft76" do
164
179
  client = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
165
180
  client.send_data(format_request(@request))
166
181
 
167
- client.onopen = lambda {
182
+ client.onopen {
168
183
  client.send_data("foobar") # Does not start with \x00 or \xff
169
184
  }
170
185
  end
@@ -196,7 +211,7 @@ describe "WebSocket server draft76" do
196
211
  # Sends first half of the request
197
212
  connection.send_data(data[0...(data.length / 2)])
198
213
 
199
- connection.onopen = lambda {
214
+ connection.onopen {
200
215
  connection.handshake_response.lines.sort.
201
216
  should == format_response(@response).lines.sort
202
217
  EM.stop
@@ -0,0 +1,62 @@
1
+ shared_examples_for "a websocket server" do
2
+ it "should call onerror if an application error raised in onopen" do
3
+ EM.run {
4
+ start_server { |ws|
5
+ ws.onopen {
6
+ raise "application error"
7
+ }
8
+
9
+ ws.onerror { |e|
10
+ e.message.should == "application error"
11
+ EM.stop
12
+ }
13
+ }
14
+
15
+ start_client
16
+ }
17
+ end
18
+
19
+ it "should call onerror if an application error raised in onmessage" do
20
+ EM.run {
21
+ start_server { |server|
22
+ server.onmessage {
23
+ raise "application error"
24
+ }
25
+
26
+ server.onerror { |e|
27
+ e.message.should == "application error"
28
+ EM.stop
29
+ }
30
+ }
31
+
32
+ start_client { |client|
33
+ client.onopen {
34
+ client.send('a message')
35
+ }
36
+ }
37
+ }
38
+ end
39
+
40
+ it "should call onerror in an application error raised in onclose" do
41
+ EM.run {
42
+ start_server { |server|
43
+ server.onclose {
44
+ raise "application error"
45
+ }
46
+
47
+ server.onerror { |e|
48
+ e.message.should == "application error"
49
+ EM.stop
50
+ }
51
+ }
52
+
53
+ start_client { |client|
54
+ client.onopen {
55
+ EM.add_timer(0.1) {
56
+ client.close_connection
57
+ }
58
+ }
59
+ }
60
+ }
61
+ end
62
+ end
@@ -106,3 +106,58 @@ describe EM::WebSocket::Framing03 do
106
106
  end
107
107
  end
108
108
  end
109
+
110
+ # These examples are straight from the spec
111
+ # http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-03#section-4.6
112
+ describe EM::WebSocket::Framing04 do
113
+ class FramingContainer04
114
+ include EM::WebSocket::Framing04
115
+
116
+ def <<(data)
117
+ @data << data
118
+ process_data(data)
119
+ end
120
+
121
+ def debug(*args); end
122
+ end
123
+
124
+ before :each do
125
+ @f = FramingContainer04.new
126
+ @f.initialize_framing
127
+ end
128
+
129
+ describe "examples from the spec" do
130
+ it "a single-frame text message" do
131
+ @f.should_receive(:message).with(:text, '', 'Hello')
132
+ @f << "\x84\x05\x48\x65\x6c\x6c\x6f" # "\x84\x05Hello"
133
+ end
134
+
135
+ it "a fragmented text message" do
136
+ @f.should_receive(:message).with(:text, '', 'Hello')
137
+ @f << "\x04\x03Hel"
138
+ @f << "\x80\x02lo"
139
+ end
140
+
141
+ it "Ping request" do
142
+ @f.should_receive(:message).with(:ping, '', 'Hello')
143
+ @f << "\x82\x05Hello"
144
+ end
145
+
146
+ it "a pong response" do
147
+ @f.should_receive(:message).with(:pong, '', 'Hello')
148
+ @f << "\x83\x05Hello"
149
+ end
150
+
151
+ it "256 bytes binary message in a single frame" do
152
+ data = "a"*256
153
+ @f.should_receive(:message).with(:binary, '', data)
154
+ @f << "\x85\x7E\x01\x00" + data
155
+ end
156
+
157
+ it "64KiB binary message in a single frame" do
158
+ data = "a"*65536
159
+ @f.should_receive(:message).with(:binary, '', data)
160
+ @f << "\x85\x7F\x00\x00\x00\x00\x00\x01\x00\x00" + data
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,18 @@
1
+ require 'helper'
2
+
3
+ describe EM::WebSocket::MaskedString do
4
+ it "should allow reading 4 byte mask and unmasking byte / bytes" do
5
+ t = EM::WebSocket::MaskedString.new("\x00\x00\x00\x01\x00\x01\x00\x01")
6
+ t.read_mask
7
+ t.getbyte(3).should == 0x00
8
+ t.getbytes(0, 4).should == "\x00\x01\x00\x00"
9
+ t.getbytes(1, 3).should == "\x01\x00\x00"
10
+ end
11
+
12
+ it "should return nil from getbyte if index requested is out of range" do
13
+ t = EM::WebSocket::MaskedString.new("\x00\x00\x00\x00\x53")
14
+ t.read_mask
15
+ t.getbyte(0).should == 0x53
16
+ t.getbyte(1).should == nil
17
+ end
18
+ end
metadata CHANGED
@@ -1,12 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: em-websocket
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 2
8
- - 1
9
- version: 0.2.1
4
+ prerelease:
5
+ version: 0.3.0
10
6
  platform: ruby
11
7
  authors:
12
8
  - Ilya Grigorik
@@ -14,7 +10,7 @@ autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
12
 
17
- date: 2011-03-01 00:00:00 +00:00
13
+ date: 2011-05-06 00:00:00 +01:00
18
14
  default_executable:
19
15
  dependencies:
20
16
  - !ruby/object:Gem::Dependency
@@ -25,10 +21,6 @@ dependencies:
25
21
  requirements:
26
22
  - - ">="
27
23
  - !ruby/object:Gem::Version
28
- segments:
29
- - 0
30
- - 12
31
- - 9
32
24
  version: 0.12.9
33
25
  type: :runtime
34
26
  version_requirements: *id001
@@ -40,10 +32,6 @@ dependencies:
40
32
  requirements:
41
33
  - - ">="
42
34
  - !ruby/object:Gem::Version
43
- segments:
44
- - 2
45
- - 1
46
- - 1
47
35
  version: 2.1.1
48
36
  type: :runtime
49
37
  version_requirements: *id002
@@ -55,10 +43,6 @@ dependencies:
55
43
  requirements:
56
44
  - - ~>
57
45
  - !ruby/object:Gem::Version
58
- segments:
59
- - 0
60
- - 2
61
- - 6
62
46
  version: 0.2.6
63
47
  type: :development
64
48
  version_requirements: *id003
@@ -70,11 +54,7 @@ dependencies:
70
54
  requirements:
71
55
  - - ~>
72
56
  - !ruby/object:Gem::Version
73
- segments:
74
- - 2
75
- - 0
76
- - 0
77
- version: 2.0.0
57
+ version: 2.5.0
78
58
  type: :development
79
59
  version_requirements: *id004
80
60
  - !ruby/object:Gem::Dependency
@@ -85,8 +65,6 @@ dependencies:
85
65
  requirements:
86
66
  - - ">="
87
67
  - !ruby/object:Gem::Version
88
- segments:
89
- - 0
90
68
  version: "0"
91
69
  type: :development
92
70
  version_requirements: *id005
@@ -114,25 +92,42 @@ files:
114
92
  - examples/multicast.rb
115
93
  - examples/test.html
116
94
  - lib/em-websocket.rb
95
+ - lib/em-websocket/close03.rb
96
+ - lib/em-websocket/close05.rb
97
+ - lib/em-websocket/close06.rb
98
+ - lib/em-websocket/close75.rb
117
99
  - lib/em-websocket/connection.rb
118
100
  - lib/em-websocket/debugger.rb
119
101
  - lib/em-websocket/framing03.rb
102
+ - lib/em-websocket/framing04.rb
103
+ - lib/em-websocket/framing05.rb
120
104
  - lib/em-websocket/framing76.rb
121
105
  - lib/em-websocket/handler.rb
122
106
  - lib/em-websocket/handler03.rb
107
+ - lib/em-websocket/handler05.rb
108
+ - lib/em-websocket/handler06.rb
123
109
  - lib/em-websocket/handler75.rb
124
110
  - lib/em-websocket/handler76.rb
125
111
  - lib/em-websocket/handler_factory.rb
112
+ - lib/em-websocket/handshake04.rb
126
113
  - lib/em-websocket/handshake75.rb
127
114
  - lib/em-websocket/handshake76.rb
115
+ - lib/em-websocket/masking04.rb
116
+ - lib/em-websocket/message_processor_03.rb
117
+ - lib/em-websocket/message_processor_06.rb
128
118
  - lib/em-websocket/version.rb
129
119
  - lib/em-websocket/websocket.rb
130
120
  - spec/helper.rb
121
+ - spec/integration/common_spec.rb
131
122
  - spec/integration/draft03_spec.rb
123
+ - spec/integration/draft05_spec.rb
124
+ - spec/integration/draft06_spec.rb
125
+ - spec/integration/draft75_spec.rb
132
126
  - spec/integration/draft76_spec.rb
127
+ - spec/integration/shared_examples.rb
133
128
  - spec/unit/framing_spec.rb
134
129
  - spec/unit/handler_spec.rb
135
- - spec/websocket_spec.rb
130
+ - spec/unit/masking_spec.rb
136
131
  has_rdoc: true
137
132
  homepage: http://github.com/igrigorik/em-websocket
138
133
  licenses: []
@@ -147,28 +142,29 @@ required_ruby_version: !ruby/object:Gem::Requirement
147
142
  requirements:
148
143
  - - ">="
149
144
  - !ruby/object:Gem::Version
150
- segments:
151
- - 0
152
145
  version: "0"
153
146
  required_rubygems_version: !ruby/object:Gem::Requirement
154
147
  none: false
155
148
  requirements:
156
149
  - - ">="
157
150
  - !ruby/object:Gem::Version
158
- segments:
159
- - 0
160
151
  version: "0"
161
152
  requirements: []
162
153
 
163
154
  rubyforge_project: em-websocket
164
- rubygems_version: 1.3.7
155
+ rubygems_version: 1.6.2
165
156
  signing_key:
166
157
  specification_version: 3
167
158
  summary: EventMachine based WebSocket server
168
159
  test_files:
169
160
  - spec/helper.rb
161
+ - spec/integration/common_spec.rb
170
162
  - spec/integration/draft03_spec.rb
163
+ - spec/integration/draft05_spec.rb
164
+ - spec/integration/draft06_spec.rb
165
+ - spec/integration/draft75_spec.rb
171
166
  - spec/integration/draft76_spec.rb
167
+ - spec/integration/shared_examples.rb
172
168
  - spec/unit/framing_spec.rb
173
169
  - spec/unit/handler_spec.rb
174
- - spec/websocket_spec.rb
170
+ - spec/unit/masking_spec.rb
@@ -1,210 +0,0 @@
1
- require 'helper'
2
-
3
- describe EventMachine::WebSocket do
4
-
5
- it "should automatically complete WebSocket handshake" do
6
- EM.run do
7
- MSG = "Hello World!"
8
- EventMachine.add_timer(0.1) do
9
- http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get :timeout => 0
10
- http.errback { failed }
11
- http.callback { http.response_header.status.should == 101 }
12
-
13
- http.stream { |msg|
14
- msg.should == MSG
15
- EventMachine.stop
16
- }
17
- end
18
-
19
- EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
20
- ws.onopen {
21
- ws.send MSG
22
- }
23
- end
24
- end
25
- end
26
-
27
- it "should fail on non WebSocket requests" do
28
- EM.run do
29
- EventMachine.add_timer(0.1) do
30
- http = EventMachine::HttpRequest.new('http://127.0.0.1:12345/').get :timeout => 0
31
- http.errback { failed }
32
- http.callback {
33
- http.response_header.status.should == 400
34
- EventMachine.stop
35
- }
36
- end
37
-
38
- EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) {}
39
- end
40
- end
41
-
42
- it "should split multiple messages into separate callbacks" do
43
- EM.run do
44
- messages = %w[1 2]
45
- received = []
46
-
47
- EventMachine.add_timer(0.1) do
48
- http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get :timeout => 0
49
- http.errback { failed }
50
- http.stream {|msg|}
51
- http.callback {
52
- http.response_header.status.should == 101
53
- http.send messages[0]
54
- http.send messages[1]
55
- }
56
- end
57
-
58
- EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
59
- ws.onopen {}
60
- ws.onclose {}
61
- ws.onmessage {|msg|
62
- msg.should == messages[received.size]
63
- received.push msg
64
-
65
- EventMachine.stop if received.size == messages.size
66
- }
67
- end
68
- end
69
- end
70
-
71
- it "should call onclose callback when client closes connection" do
72
- EM.run do
73
- EventMachine.add_timer(0.1) do
74
- http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get :timeout => 0
75
- http.errback { failed }
76
- http.callback {
77
- http.response_header.status.should == 101
78
- http.close_connection
79
- }
80
- http.stream{|msg|}
81
- end
82
-
83
- EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
84
- ws.onopen {}
85
- ws.onclose {
86
- ws.state.should == :closed
87
- EventMachine.stop
88
- }
89
- end
90
- end
91
- end
92
-
93
- it "should call onerror callback with raised exception and close connection on bad handshake" do
94
- EM.run do
95
- EventMachine.add_timer(0.1) do
96
- http = EventMachine::HttpRequest.new('http://127.0.0.1:12345/').get :timeout => 0
97
- http.errback { http.response_header.status.should == 0 }
98
- http.callback { failed }
99
- end
100
-
101
- EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
102
- ws.onopen { failed }
103
- ws.onclose { EventMachine.stop }
104
- ws.onerror {|e|
105
- e.should be_an_instance_of EventMachine::WebSocket::HandshakeError
106
- e.message.should match('Connection and Upgrade headers required')
107
- EventMachine.stop
108
- }
109
- end
110
- end
111
- end
112
-
113
- it "should populate ws.request with appropriate headers" do
114
- EM.run do
115
- EventMachine.add_timer(0.1) do
116
- http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get :timeout => 0
117
- http.errback { failed }
118
- http.callback {
119
- http.response_header.status.should == 101
120
- http.close_connection
121
- }
122
- http.stream { |msg| }
123
- end
124
-
125
- EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
126
- ws.onopen {
127
- ws.request["User-Agent"].should == "EventMachine HttpClient"
128
- ws.request["Connection"].should == "Upgrade"
129
- ws.request["Upgrade"].should == "WebSocket"
130
- ws.request["Path"].should == "/"
131
- ws.request["Origin"].should == "127.0.0.1"
132
- ws.request["Host"].to_s.should == "ws://127.0.0.1:12345"
133
- }
134
- ws.onclose {
135
- ws.state.should == :closed
136
- EventMachine.stop
137
- }
138
- end
139
- end
140
- end
141
-
142
- it "should allow sending and retrieving query string args passed in on the connection request." do
143
- EM.run do
144
- EventMachine.add_timer(0.1) do
145
- http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get(:query => {'foo' => 'bar', 'baz' => 'qux'}, :timeout => 0)
146
- http.errback { failed }
147
- http.callback {
148
- http.response_header.status.should == 101
149
- http.close_connection
150
- }
151
- http.stream { |msg| }
152
- end
153
-
154
- EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
155
- ws.onopen {
156
- path, query = ws.request["Path"].split('?')
157
- path.should == '/'
158
- Hash[*query.split(/&|=/)].should == {"foo"=>"bar", "baz"=>"qux"}
159
- ws.request["Query"]["foo"].should == "bar"
160
- ws.request["Query"]["baz"].should == "qux"
161
- }
162
- ws.onclose {
163
- ws.state.should == :closed
164
- EventMachine.stop
165
- }
166
- end
167
- end
168
- end
169
-
170
- it "should ws.response['Query'] to empty hash when no query string params passed in connection URI" do
171
- EM.run do
172
- EventMachine.add_timer(0.1) do
173
- http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get(:timeout => 0)
174
- http.errback { failed }
175
- http.callback {
176
- http.response_header.status.should == 101
177
- http.close_connection
178
- }
179
- http.stream { |msg| }
180
- end
181
-
182
- EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
183
- ws.onopen {
184
- ws.request["Path"].should == "/"
185
- ws.request["Query"].should == {}
186
- }
187
- ws.onclose {
188
- ws.state.should == :closed
189
- EventMachine.stop
190
- }
191
- end
192
- end
193
- end
194
-
195
- it "should raise an exception if frame sent before handshake complete" do
196
- EM.run {
197
- EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |c|
198
- # We're not using a real client so the handshake will not be sent
199
- EM.add_timer(0.1) {
200
- lambda {
201
- c.send('early message')
202
- }.should raise_error('Cannot send data before onopen callback')
203
- EM.stop
204
- }
205
- }
206
-
207
- client = EM.connect('0.0.0.0', 12345, EM::Connection)
208
- }
209
- end
210
- end