websocket_parser 0.0.6 → 0.1.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.
- data/README.md +10 -10
- data/lib/websocket/client_handshake.rb +0 -5
- data/lib/websocket/parser.rb +36 -6
- data/lib/websocket/version.rb +1 -1
- data/lib/websocket_parser.rb +2 -0
- data/spec/websocket/parser_spec.rb +61 -20
- metadata +4 -4
data/README.md
CHANGED
@@ -21,10 +21,10 @@ Or install it yourself as:
|
|
21
21
|
|
22
22
|
## Usage. TMTOWTDI.
|
23
23
|
|
24
|
-
###
|
24
|
+
### Return values
|
25
25
|
|
26
26
|
The simplest way to use the websocket parser is to create a new one, fetch
|
27
|
-
it with data and
|
27
|
+
it with data and query it for new messages.
|
28
28
|
|
29
29
|
```ruby
|
30
30
|
require 'websocket_parser'
|
@@ -33,13 +33,17 @@ parser = WebSocket::Parser.new
|
|
33
33
|
|
34
34
|
parser.append data
|
35
35
|
|
36
|
-
parser.
|
37
|
-
parser.
|
36
|
+
parser.next_message # return next message or nil
|
37
|
+
parser.next_messages # return an array with all parsed messages
|
38
|
+
|
39
|
+
# To send a message:
|
40
|
+
|
41
|
+
socket << WebSocket::Message.new('Hi there!').to_data
|
38
42
|
|
39
43
|
```
|
40
44
|
|
41
|
-
|
42
|
-
|
45
|
+
Only text or binary messages are returned on the parse methods. To intercept
|
46
|
+
control frames use the parser's callbacks.
|
43
47
|
|
44
48
|
### Use callbacks
|
45
49
|
|
@@ -78,10 +82,6 @@ end
|
|
78
82
|
|
79
83
|
parser << socket.read(4096)
|
80
84
|
|
81
|
-
# To send a message:
|
82
|
-
|
83
|
-
socket << WebSocket::Message.new('Hi there!').to_data
|
84
|
-
|
85
85
|
```
|
86
86
|
|
87
87
|
## Status
|
@@ -13,11 +13,6 @@ module WebSocket
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def valid?
|
16
|
-
if headers['Connection'].downcase != 'upgrade'
|
17
|
-
errors << 'Not connection upgrade'
|
18
|
-
return false
|
19
|
-
end
|
20
|
-
|
21
16
|
if headers['Upgrade'].downcase != 'websocket'
|
22
17
|
errors << 'Connection upgrade is not for websocket'
|
23
18
|
return false
|
data/lib/websocket/parser.rb
CHANGED
@@ -81,6 +81,13 @@ module WebSocket
|
|
81
81
|
read_payload if @state == :payload
|
82
82
|
|
83
83
|
@state == :complete ? process_frame! : nil
|
84
|
+
|
85
|
+
rescue StandardError => ex
|
86
|
+
if @on_error
|
87
|
+
@on_error.call(ex.message)
|
88
|
+
else
|
89
|
+
raise ex
|
90
|
+
end
|
84
91
|
end
|
85
92
|
|
86
93
|
private
|
@@ -106,9 +113,13 @@ module WebSocket
|
|
106
113
|
|
107
114
|
def read_extended_payload_length
|
108
115
|
if message_size == :medium && @data.size >= 2
|
109
|
-
unpack_bytes(2,'S<')
|
116
|
+
size = unpack_bytes(2,'S<')
|
117
|
+
ParserError.new("Wrong payload length. Expected to be greater than 125") unless size > 125
|
118
|
+
size
|
110
119
|
elsif message_size == :large && @data.size >= 4
|
111
|
-
unpack_bytes(8,'Q<')
|
120
|
+
size = unpack_bytes(8,'Q<')
|
121
|
+
ParserError.new("Wrong payload length. Expected to be greater than 65535") unless size > 65_535
|
122
|
+
size
|
112
123
|
end
|
113
124
|
end
|
114
125
|
|
@@ -137,6 +148,10 @@ module WebSocket
|
|
137
148
|
@data.slice!(0,num).unpack(format).first
|
138
149
|
end
|
139
150
|
|
151
|
+
def message_frame?
|
152
|
+
[:text, :binary].include?(opcode)
|
153
|
+
end
|
154
|
+
|
140
155
|
def control_frame?
|
141
156
|
[:close, :ping, :pong].include?(opcode)
|
142
157
|
end
|
@@ -148,9 +163,13 @@ module WebSocket
|
|
148
163
|
@current_message = @payload
|
149
164
|
end
|
150
165
|
|
151
|
-
completed_message = fin? ?
|
166
|
+
completed_message = if fin? && message_frame?
|
167
|
+
@current_message
|
168
|
+
else
|
169
|
+
nil
|
170
|
+
end
|
152
171
|
|
153
|
-
process_message!
|
172
|
+
fin? ? process_message! : opcode # store the opcode
|
154
173
|
|
155
174
|
reset_frame!
|
156
175
|
|
@@ -160,7 +179,10 @@ module WebSocket
|
|
160
179
|
def process_message!
|
161
180
|
case opcode
|
162
181
|
when :text
|
163
|
-
@
|
182
|
+
msg = @current_message.force_encoding("UTF-8")
|
183
|
+
raise ParserError.new('Payload data is not valid UTF-8') unless msg.valid_encoding?
|
184
|
+
|
185
|
+
@on_message.call(msg) if @on_message
|
164
186
|
when :binary
|
165
187
|
@on_message.call(@current_message) if @on_message
|
166
188
|
when :ping
|
@@ -173,6 +195,9 @@ module WebSocket
|
|
173
195
|
|
174
196
|
@on_close.call(status, message) if @on_close
|
175
197
|
end
|
198
|
+
|
199
|
+
# Reset message
|
200
|
+
@opcode = nil
|
176
201
|
@current_message = nil
|
177
202
|
end
|
178
203
|
|
@@ -183,7 +208,12 @@ module WebSocket
|
|
183
208
|
end
|
184
209
|
|
185
210
|
def opcode
|
186
|
-
@opcode ||=
|
211
|
+
@opcode ||= begin
|
212
|
+
num = @first_byte & 0b00001111
|
213
|
+
opcode = OPCODES[num]
|
214
|
+
raise ParserError.new("Unknown opcode #{num}") unless opcode
|
215
|
+
opcode
|
216
|
+
end
|
187
217
|
end
|
188
218
|
|
189
219
|
def masked?
|
data/lib/websocket/version.rb
CHANGED
data/lib/websocket_parser.rb
CHANGED
@@ -39,6 +39,13 @@ describe WebSocket::Parser do
|
|
39
39
|
messages[1].should == 'Made glorious summer by this sun of York'
|
40
40
|
end
|
41
41
|
|
42
|
+
it "does not return control frames" do
|
43
|
+
msg = WebSocket::Message.close(1001, 'Goodbye!').to_data
|
44
|
+
|
45
|
+
messages = parser << msg
|
46
|
+
messages.should be_empty
|
47
|
+
end
|
48
|
+
|
42
49
|
it "can receive a message in parts" do
|
43
50
|
data = WebSocket::Message.new('Once upon a time').to_data
|
44
51
|
parser << data.slice!(0, 5)
|
@@ -85,26 +92,6 @@ describe WebSocket::Parser do
|
|
85
92
|
received_messages.first.should == text
|
86
93
|
end
|
87
94
|
|
88
|
-
it "can receive multiframe messages" do
|
89
|
-
frame1 = WebSocket::Message.new("It is a truth universally acknowledged,", :continuation)
|
90
|
-
frame2 = WebSocket::Message.new("that a single man in possession of a good fortune,", :continuation)
|
91
|
-
frame3 = WebSocket::Message.new("must be in want of a wife.")
|
92
|
-
|
93
|
-
parser << frame1.to_data
|
94
|
-
|
95
|
-
received_messages.should be_empty
|
96
|
-
|
97
|
-
parser << frame2.to_data
|
98
|
-
|
99
|
-
received_messages.should be_empty
|
100
|
-
|
101
|
-
parser << frame3.to_data
|
102
|
-
|
103
|
-
received_messages.first.should == "It is a truth universally acknowledged," +
|
104
|
-
"that a single man in possession of a good fortune," +
|
105
|
-
"must be in want of a wife."
|
106
|
-
end
|
107
|
-
|
108
95
|
it "recognizes a ping message" do
|
109
96
|
parser << WebSocket::Message.ping.to_data
|
110
97
|
|
@@ -141,4 +128,58 @@ describe WebSocket::Parser do
|
|
141
128
|
|
142
129
|
received_messages.first.should == 'Once upon a time'
|
143
130
|
end
|
131
|
+
|
132
|
+
context "examples from the spec" do
|
133
|
+
# These are literal examples from the spec
|
134
|
+
|
135
|
+
it "recognizes single-frame unmasked text message" do
|
136
|
+
parser << [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f].pack('C*')
|
137
|
+
|
138
|
+
received_messages.first.should == 'Hello'
|
139
|
+
end
|
140
|
+
|
141
|
+
it "recognizes single-frame masked text message" do
|
142
|
+
parser << [0x81, 0x85, 0x37, 0xfa, 0x21, 0x3d, 0x7f, 0x9f, 0x4d, 0x51, 0x58].pack('C*')
|
143
|
+
|
144
|
+
received_messages.first.should == 'Hello'
|
145
|
+
end
|
146
|
+
|
147
|
+
it "recognizes a fragmented unmasked text message" do
|
148
|
+
parser << [0x01, 0x03, 0x48, 0x65, 0x6c].pack('C*') # contains "Hel"
|
149
|
+
|
150
|
+
received_messages.should be_empty
|
151
|
+
|
152
|
+
parser << [0x80, 0x02, 0x6c, 0x6f].pack('C*') # contains "lo"
|
153
|
+
|
154
|
+
received_messages.first.should == 'Hello'
|
155
|
+
end
|
156
|
+
|
157
|
+
it "recognizes an unnmasked ping request" do
|
158
|
+
parser << [0x89, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f].pack('C*')
|
159
|
+
|
160
|
+
received_pings.size.should == 1
|
161
|
+
end
|
162
|
+
|
163
|
+
it "recognizes a masked pong response" do
|
164
|
+
parser << [0x8a, 0x85, 0x37, 0xfa, 0x21, 0x3d, 0x7f, 0x9f, 0x4d, 0x51, 0x58].pack('C*')
|
165
|
+
|
166
|
+
received_pongs.size.should == 1
|
167
|
+
end
|
168
|
+
|
169
|
+
it "recognizes 256 bytes binary message in a single unmasked frame" do
|
170
|
+
data = Array.new(256) { rand(256) }.pack('c*')
|
171
|
+
parser << [0x82, 0x7E, 0x0100].pack('CCS<') + data
|
172
|
+
|
173
|
+
received_messages.first.should == data
|
174
|
+
end
|
175
|
+
|
176
|
+
it "recoginzes 64KiB binary message in a single unmasked frame" do
|
177
|
+
data = Array.new(65536) { rand(256) }.pack('c*')
|
178
|
+
parser << [0x82, 0x7F, 0x0000000000010000].pack('CCQ<') + data
|
179
|
+
|
180
|
+
received_messages.first.should == data
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
|
144
185
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: websocket_parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-08-
|
12
|
+
date: 2012-08-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: http
|
@@ -97,7 +97,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
97
97
|
version: '0'
|
98
98
|
segments:
|
99
99
|
- 0
|
100
|
-
hash:
|
100
|
+
hash: -2916391695898748825
|
101
101
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
102
|
none: false
|
103
103
|
requirements:
|
@@ -106,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
106
|
version: '0'
|
107
107
|
segments:
|
108
108
|
- 0
|
109
|
-
hash:
|
109
|
+
hash: -2916391695898748825
|
110
110
|
requirements: []
|
111
111
|
rubyforge_project:
|
112
112
|
rubygems_version: 1.8.23
|