em-websocket 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc CHANGED
@@ -1,5 +1,10 @@
1
1
  = Changelog
2
2
 
3
+ == 0.3.1 / 2011-07-28
4
+
5
+ - new features:
6
+ - Support WebSocket drafts 07 & 08
7
+
3
8
  == 0.3.0 / 2011-05-06
4
9
 
5
10
  - new features:
data/README.md CHANGED
@@ -6,23 +6,25 @@ EventMachine based, async, Ruby WebSocket server. Take a look at examples direct
6
6
 
7
7
  ## Simple server example
8
8
 
9
- EventMachine.run {
10
-
11
- EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws|
12
- ws.onopen {
13
- puts "WebSocket connection open"
14
-
15
- # publish message to the client
16
- ws.send "Hello Client"
17
- }
18
-
19
- ws.onclose { puts "Connection closed" }
20
- ws.onmessage { |msg|
21
- puts "Recieved message: #{msg}"
22
- ws.send "Pong: #{msg}"
23
- }
24
- end
25
- }
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
26
+ }
27
+ ```
26
28
 
27
29
  ## Secure server
28
30
 
@@ -30,17 +32,19 @@ It is possible to accept secure wss:// connections by passing :secure => true wh
30
32
 
31
33
  For example,
32
34
 
33
- EventMachine::WebSocket.start({
34
- :host => "0.0.0.0",
35
- :port => 443
36
- :secure => true,
37
- :tls_options => {
38
- :private_key_file => "/private/key",
39
- :cert_chain_file => "/ssl/certificate"
40
- }
41
- }) do |ws|
42
- ...
43
- end
35
+ ```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
+ }
44
+ }) do |ws|
45
+ ...
46
+ end
47
+ ```
44
48
 
45
49
  ## Handling errors
46
50
 
@@ -48,11 +52,13 @@ There are two kinds of errors that need to be handled - errors caused by incompa
48
52
 
49
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.
50
54
 
51
- ws.onerror { |error|
52
- if e.kind_of?(EM::WebSocket::WebSocketError)
53
- ...
54
- end
55
- }
55
+ ```ruby
56
+ ws.onerror { |error|
57
+ if e.kind_of?(EM::WebSocket::WebSocketError)
58
+ ...
59
+ end
60
+ }
61
+ ```
56
62
 
57
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`.
58
64
 
@@ -68,25 +74,4 @@ It is also possible to log all errors when developing by including the `:debug =
68
74
 
69
75
  # License
70
76
 
71
- (The MIT License)
72
-
73
- Copyright (c) 2009 Ilya Grigorik
74
-
75
- Permission is hereby granted, free of charge, to any person obtaining
76
- a copy of this software and associated documentation files (the
77
- 'Software'), to deal in the Software without restriction, including
78
- without limitation the rights to use, copy, modify, merge, publish,
79
- distribute, sublicense, and/or sell copies of the Software, and to
80
- permit persons to whom the Software is furnished to do so, subject to
81
- the following conditions:
82
-
83
- The above copyright notice and this permission notice shall be
84
- included in all copies or substantial portions of the Software.
85
-
86
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
87
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
88
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
89
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
90
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
91
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
92
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
77
+ The MIT License - Copyright (c) 2009 Ilya Grigorik
@@ -146,7 +146,7 @@ module EventMachine
146
146
  end
147
147
 
148
148
  def opcode_to_type(opcode)
149
- FRAME_TYPES_INVERSE[opcode] || raise("Unknown opcode")
149
+ FRAME_TYPES_INVERSE[opcode] || raise(DataError, "Unknown opcode")
150
150
  end
151
151
 
152
152
  def data_frame?(type)
@@ -16,6 +16,7 @@ module EventMachine
16
16
  pointer = 0
17
17
 
18
18
  @data.read_mask
19
+ pointer += 4
19
20
 
20
21
  fin = (@data.getbyte(pointer) & 0b10000000) == 0b10000000
21
22
  # Ignoring rsv1-3 for now
@@ -67,7 +68,8 @@ module EventMachine
67
68
  pointer += payload_length
68
69
 
69
70
  # Throw away data up to pointer
70
- @data.slice!(0...(pointer + 4))
71
+ @data.unset_mask
72
+ @data.slice!(0...pointer)
71
73
 
72
74
  frame_type = opcode_to_type(opcode)
73
75
 
@@ -146,7 +148,7 @@ module EventMachine
146
148
  end
147
149
 
148
150
  def opcode_to_type(opcode)
149
- FRAME_TYPES_INVERSE[opcode] || raise("Unknown opcode")
151
+ FRAME_TYPES_INVERSE[opcode] || raise(DataError, "Unknown opcode")
150
152
  end
151
153
 
152
154
  def data_frame?(type)
@@ -0,0 +1,168 @@
1
+ # encoding: BINARY
2
+
3
+ module EventMachine
4
+ module WebSocket
5
+ module Framing07
6
+
7
+ def initialize_framing
8
+ @data = MaskedString.new
9
+ @application_data_buffer = '' # Used for MORE frames
10
+ end
11
+
12
+ def process_data(newdata)
13
+ error = false
14
+
15
+ while !error && @data.size >= 2
16
+ pointer = 0
17
+
18
+ fin = (@data.getbyte(pointer) & 0b10000000) == 0b10000000
19
+ # Ignoring rsv1-3 for now
20
+ opcode = @data.getbyte(pointer) & 0b00001111
21
+ pointer += 1
22
+
23
+ mask = (@data.getbyte(pointer) & 0b10000000) == 0b10000000
24
+ length = @data.getbyte(pointer) & 0b01111111
25
+ pointer += 1
26
+
27
+ # raise WebSocketError, 'Data from client must be masked' unless mask
28
+
29
+ payload_length = case length
30
+ when 127 # Length defined by 8 bytes
31
+ # Check buffer size
32
+ if @data.getbyte(pointer+8-1) == nil
33
+ debug [:buffer_incomplete, @data]
34
+ error = true
35
+ next
36
+ end
37
+
38
+ # Only using the last 4 bytes for now, till I work out how to
39
+ # unpack 8 bytes. I'm sure 4GB frames will do for now :)
40
+ l = @data.getbytes(pointer+4, 4).unpack('N').first
41
+ pointer += 8
42
+ l
43
+ when 126 # Length defined by 2 bytes
44
+ # Check buffer size
45
+ if @data.getbyte(pointer+2-1) == nil
46
+ debug [:buffer_incomplete, @data]
47
+ error = true
48
+ next
49
+ end
50
+
51
+ l = @data.getbytes(pointer, 2).unpack('n').first
52
+ pointer += 2
53
+ l
54
+ else
55
+ length
56
+ end
57
+
58
+ # Compute the expected frame length
59
+ frame_length = pointer + payload_length
60
+ frame_length += 4 if mask
61
+
62
+ # Check buffer size
63
+ if @data.getbyte(frame_length - 1) == nil
64
+ debug [:buffer_incomplete, @data]
65
+ error = true
66
+ next
67
+ end
68
+
69
+ # Remove frame header
70
+ @data.slice!(0...pointer)
71
+ pointer = 0
72
+
73
+ # Read application data (unmasked if required)
74
+ @data.read_mask if mask
75
+ pointer += 4 if mask
76
+ application_data = @data.getbytes(pointer, payload_length)
77
+ pointer += payload_length
78
+ @data.unset_mask if mask
79
+
80
+ # Throw away data up to pointer
81
+ @data.slice!(0...pointer)
82
+
83
+ frame_type = opcode_to_type(opcode)
84
+
85
+ if frame_type == :continuation && !@frame_type
86
+ raise WebSocketError, 'Continuation frame not expected'
87
+ end
88
+
89
+ if !fin
90
+ debug [:moreframe, frame_type, application_data]
91
+ @application_data_buffer << application_data
92
+ @frame_type = frame_type
93
+ else
94
+ # Message is complete
95
+ if frame_type == :continuation
96
+ @application_data_buffer << application_data
97
+ message(@frame_type, '', @application_data_buffer)
98
+ @application_data_buffer = ''
99
+ @frame_type = nil
100
+ else
101
+ message(frame_type, '', application_data)
102
+ end
103
+ end
104
+ end # end while
105
+ end
106
+
107
+ def send_frame(frame_type, application_data)
108
+ debug [:sending_frame, frame_type, application_data]
109
+
110
+ if @state == :closing && data_frame?(frame_type)
111
+ raise WebSocketError, "Cannot send data frame since connection is closing"
112
+ end
113
+
114
+ frame = ''
115
+
116
+ opcode = type_to_opcode(frame_type)
117
+ byte1 = opcode | 0b10000000 # fin bit set, rsv1-3 are 0
118
+ frame << byte1
119
+
120
+ length = application_data.size
121
+ if length <= 125
122
+ byte2 = length # since rsv4 is 0
123
+ frame << byte2
124
+ elsif length < 65536 # write 2 byte length
125
+ frame << 126
126
+ frame << [length].pack('n')
127
+ else # write 8 byte length
128
+ frame << 127
129
+ frame << [length >> 32, length & 0xFFFFFFFF].pack("NN")
130
+ end
131
+
132
+ frame << application_data
133
+
134
+ @connection.send_data(frame)
135
+ end
136
+
137
+ def send_text_frame(data)
138
+ send_frame(:text, data)
139
+ end
140
+
141
+ private
142
+
143
+ FRAME_TYPES = {
144
+ :continuation => 0,
145
+ :text => 1,
146
+ :binary => 2,
147
+ :close => 8,
148
+ :ping => 9,
149
+ :pong => 10,
150
+ }
151
+ FRAME_TYPES_INVERSE = FRAME_TYPES.invert
152
+ # Frames are either data frames or control frames
153
+ DATA_FRAMES = [:text, :binary, :continuation]
154
+
155
+ def type_to_opcode(frame_type)
156
+ FRAME_TYPES[frame_type] || raise("Unknown frame type")
157
+ end
158
+
159
+ def opcode_to_type(opcode)
160
+ FRAME_TYPES_INVERSE[opcode] || raise(DataError, "Unknown opcode")
161
+ end
162
+
163
+ def data_frame?(type)
164
+ DATA_FRAMES.include?(type)
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,10 @@
1
+ module EventMachine
2
+ module WebSocket
3
+ class Handler07 < Handler
4
+ include Handshake04
5
+ include Framing07
6
+ include MessageProcessor06
7
+ include Close06
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module EventMachine
2
+ module WebSocket
3
+ class Handler08 < Handler
4
+ include Handshake04
5
+ include Framing07
6
+ include MessageProcessor06
7
+ include Close06
8
+ end
9
+ end
10
+ end
@@ -87,6 +87,10 @@ module EventMachine
87
87
  Handler05.new(connection, request, debug)
88
88
  when 6
89
89
  Handler06.new(connection, request, debug)
90
+ when 7
91
+ Handler07.new(connection, request, debug)
92
+ when 8
93
+ Handler08.new(connection, request, debug)
90
94
  else
91
95
  # According to spec should abort the connection
92
96
  raise WebSocketError, "Protocol version #{version} not supported"
@@ -1,18 +1,31 @@
1
1
  module EventMachine
2
2
  module WebSocket
3
3
  class MaskedString < String
4
+ # Read a 4 bit XOR mask - further requested bytes will be unmasked
4
5
  def read_mask
6
+ if respond_to?(:encoding) && encoding.name != "ASCII-8BIT"
7
+ raise "MaskedString only operates on BINARY strings"
8
+ end
5
9
  raise "Too short" if bytesize < 4 # TODO - change
6
10
  @masking_key = String.new(self[0..3])
7
11
  end
8
12
 
13
+ # Removes the mask, behaves like a normal string again
14
+ def unset_mask
15
+ @masking_key = nil
16
+ end
17
+
9
18
  def slice_mask
10
19
  slice!(0, 4)
11
20
  end
12
21
 
13
22
  def getbyte(index)
14
- masked_char = super(index + 4)
15
- masked_char ? masked_char ^ @masking_key.getbyte(index % 4) : nil
23
+ if @masking_key
24
+ masked_char = super
25
+ masked_char ? masked_char ^ @masking_key.getbyte(index % 4) : nil
26
+ else
27
+ super
28
+ end
16
29
  end
17
30
 
18
31
  def getbytes(start_index, count)
@@ -1,5 +1,5 @@
1
1
  module EventMachine
2
2
  module Websocket
3
- VERSION = "0.3.0"
3
+ VERSION = "0.3.1"
4
4
  end
5
5
  end
data/lib/em-websocket.rb CHANGED
@@ -5,11 +5,11 @@ require "eventmachine"
5
5
  %w[
6
6
  debugger websocket connection
7
7
  handshake75 handshake76 handshake04
8
- framing76 framing03 framing04 framing05
8
+ framing76 framing03 framing04 framing05 framing07
9
9
  close75 close03 close05 close06
10
10
  masking04
11
11
  message_processor_03 message_processor_06
12
- handler_factory handler handler75 handler76 handler03 handler05 handler06
12
+ handler_factory handler handler75 handler76 handler03 handler05 handler06 handler07 handler08
13
13
  ].each do |file|
14
14
  require "em-websocket/#{file}"
15
15
  end
@@ -161,3 +161,72 @@ describe EM::WebSocket::Framing04 do
161
161
  end
162
162
  end
163
163
  end
164
+
165
+ describe EM::WebSocket::Framing07 do
166
+ class FramingContainer07
167
+ include EM::WebSocket::Framing07
168
+
169
+ def <<(data)
170
+ @data << data
171
+ process_data(data)
172
+ end
173
+
174
+ def debug(*args); end
175
+ end
176
+
177
+ before :each do
178
+ @f = FramingContainer07.new
179
+ @f.initialize_framing
180
+ end
181
+
182
+ # These examples are straight from the spec
183
+ # http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07#section-4.6
184
+ describe "examples from the spec" do
185
+ it "a single-frame unmakedtext message" do
186
+ @f.should_receive(:message).with(:text, '', 'Hello')
187
+ @f << "\x81\x05\x48\x65\x6c\x6c\x6f" # "\x84\x05Hello"
188
+ end
189
+
190
+ it "a single-frame masked text message" do
191
+ @f.should_receive(:message).with(:text, '', 'Hello')
192
+ @f << "\x81\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58" # "\x84\x05Hello"
193
+ end
194
+
195
+ it "a fragmented unmasked text message" do
196
+ @f.should_receive(:message).with(:text, '', 'Hello')
197
+ @f << "\x01\x03Hel"
198
+ @f << "\x80\x02lo"
199
+ end
200
+
201
+ it "Ping request" do
202
+ @f.should_receive(:message).with(:ping, '', 'Hello')
203
+ @f << "\x89\x05Hello"
204
+ end
205
+
206
+ it "a pong response" do
207
+ @f.should_receive(:message).with(:pong, '', 'Hello')
208
+ @f << "\x8a\x05Hello"
209
+ end
210
+
211
+ it "256 bytes binary message in a single unmasked frame" do
212
+ data = "a"*256
213
+ @f.should_receive(:message).with(:binary, '', data)
214
+ @f << "\x82\x7E\x01\x00" + data
215
+ end
216
+
217
+ it "64KiB binary message in a single unmasked frame" do
218
+ data = "a"*65536
219
+ @f.should_receive(:message).with(:binary, '', data)
220
+ @f << "\x82\x7F\x00\x00\x00\x00\x00\x01\x00\x00" + data
221
+ end
222
+ end
223
+
224
+ describe "other tests" do
225
+ it "should raise a DataError if an invalid frame type is requested" do
226
+ lambda {
227
+ # Opcode 3 is not supported by this draft
228
+ @f << "\x83\x05Hello"
229
+ }.should raise_error(EventMachine::WebSocket::DataError, "Unknown opcode")
230
+ end
231
+ end
232
+ end
@@ -1,3 +1,5 @@
1
+ # encoding: BINARY
2
+
1
3
  require 'helper'
2
4
 
3
5
  describe EM::WebSocket::MaskedString do
@@ -5,14 +7,21 @@ describe EM::WebSocket::MaskedString do
5
7
  t = EM::WebSocket::MaskedString.new("\x00\x00\x00\x01\x00\x01\x00\x01")
6
8
  t.read_mask
7
9
  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
+ t.getbytes(4, 4).should == "\x00\x01\x00\x00"
11
+ t.getbytes(5, 3).should == "\x01\x00\x00"
10
12
  end
11
13
 
12
14
  it "should return nil from getbyte if index requested is out of range" do
13
15
  t = EM::WebSocket::MaskedString.new("\x00\x00\x00\x00\x53")
14
16
  t.read_mask
15
- t.getbyte(0).should == 0x53
16
- t.getbyte(1).should == nil
17
+ t.getbyte(4).should == 0x53
18
+ t.getbyte(5).should == nil
19
+ end
20
+
21
+ it "should allow switching masking on and off" do
22
+ t = EM::WebSocket::MaskedString.new("\x02\x00\x00\x00\x03")
23
+ t.getbyte(4).should == 0x03
24
+ t.read_mask
25
+ t.getbyte(4).should == 0x01
17
26
  end
18
27
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: em-websocket
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.3.0
5
+ version: 0.3.1
6
6
  platform: ruby
7
7
  authors:
8
8
  - Ilya Grigorik
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-05-06 00:00:00 +01:00
13
+ date: 2011-07-28 00:00:00 +01:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -101,11 +101,14 @@ files:
101
101
  - lib/em-websocket/framing03.rb
102
102
  - lib/em-websocket/framing04.rb
103
103
  - lib/em-websocket/framing05.rb
104
+ - lib/em-websocket/framing07.rb
104
105
  - lib/em-websocket/framing76.rb
105
106
  - lib/em-websocket/handler.rb
106
107
  - lib/em-websocket/handler03.rb
107
108
  - lib/em-websocket/handler05.rb
108
109
  - lib/em-websocket/handler06.rb
110
+ - lib/em-websocket/handler07.rb
111
+ - lib/em-websocket/handler08.rb
109
112
  - lib/em-websocket/handler75.rb
110
113
  - lib/em-websocket/handler76.rb
111
114
  - lib/em-websocket/handler_factory.rb