spdy 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.md +20 -19
- data/examples/spdy_server.rb +6 -5
- data/lib/spdy.rb +2 -1
- data/lib/spdy/compat.rb +17 -0
- data/lib/spdy/compressor.rb +41 -30
- data/lib/spdy/parser.rb +10 -5
- data/lib/spdy/protocol.rb +128 -2
- data/lib/spdy/version.rb +1 -1
- data/spec/compressor_spec.rb +15 -4
- data/spec/helper.rb +17 -1
- data/spec/protocol_spec.rb +364 -69
- metadata +46 -54
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -4,39 +4,40 @@ SPDY was developed at Google as part of the "let's make the web faster" initiati
|
|
4
4
|
|
5
5
|
Today, SPDY is built into Chrome + Google web-server infrastructure and is currently serving over 90% of all the SSL traffic. Yes, you read that right.. If you're using Chrome, and you're using Google products, chances are, you are fetching the content from Google servers over SPDY, not HTTP.
|
6
6
|
|
7
|
-
|
7
|
+
* [Life beyond HTTP 1.1: Google's SPDY](http://www.igvita.com/2011/04/07/life-beyond-http-11-googles-spdy)
|
8
|
+
* [How to set up ruby, eventmachine and spdy to use NPN](https://gist.github.com/944386)
|
8
9
|
|
9
10
|
## Protocol Parser
|
10
11
|
|
11
12
|
SPDY specification (Draft 2) defines its own framing and message exchange protocol which is layered on top of a raw TCP/SSL connection. This gem implements a basic, pure Ruby parser for the SPDY protocol:
|
12
13
|
|
13
|
-
|
14
|
+
```ruby
|
15
|
+
s = SPDY::Parser.new
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
17
|
+
s.on_headers_complete { |stream_id, associated_stream, priority, headers| ... }
|
18
|
+
s.on_body { |stream_id, data| ... }
|
19
|
+
s.on_message_complete { |stream_id| ... }
|
18
20
|
|
19
|
-
|
21
|
+
s << recieved_data
|
22
|
+
```
|
20
23
|
|
21
24
|
However, parsing the data is not enough, to do the full exchange you also have to respond to a SPDY client with appropriate 'control' and 'data' frames:
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
```ruby
|
27
|
+
sr = SPDY::Protocol::Control::SynReply.new
|
28
|
+
headers = {'Content-Type' => 'text/plain', 'status' => '200 OK', 'version' => 'HTTP/1.1'}
|
29
|
+
sr.create(:stream_id => 1, :headers => headers)
|
30
|
+
send_data sr.to_binary_s
|
27
31
|
|
28
|
-
|
32
|
+
# or, to send a data frame
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
34
|
+
d = SPDY::Protocol::Data::Frame.new
|
35
|
+
d.create(:stream_id => 1, :data => "This is SPDY.")
|
36
|
+
send_data d.to_binary_s
|
37
|
+
```
|
33
38
|
|
34
39
|
See example eventmachine server in *examples/spdy_server.rb* for a minimal SPDY "hello world" server.
|
35
40
|
|
36
|
-
## Todo:
|
37
|
-
|
38
|
-
- implement support for all [control frames](https://sites.google.com/a/chromium.org/dev/spdy/spdy-protocol/spdy-protocol-draft2#TOC-Control-frames1)
|
39
|
-
|
40
41
|
### License
|
41
42
|
|
42
|
-
|
43
|
+
MIT License - Copyright (c) 2011 Ilya Grigorik
|
data/examples/spdy_server.rb
CHANGED
@@ -9,21 +9,21 @@ class SPDYHandler < EM::Connection
|
|
9
9
|
@parser.on_headers_complete do |stream_id, associated_stream, priority, headers|
|
10
10
|
p [:SPDY_HEADERS, headers]
|
11
11
|
|
12
|
-
sr = SPDY::Protocol::Control::SynReply.new
|
12
|
+
sr = SPDY::Protocol::Control::SynReply.new({:zlib_session => @parser.zlib_session})
|
13
13
|
h = {'Content-Type' => 'text/plain', 'status' => '200 OK', 'version' => 'HTTP/1.1'}
|
14
|
-
sr.create(:stream_id =>
|
14
|
+
sr.create({:stream_id => stream_id, :headers => h})
|
15
15
|
send_data sr.to_binary_s
|
16
16
|
|
17
17
|
p [:SPDY, :sent, :SYN_REPLY]
|
18
18
|
|
19
19
|
d = SPDY::Protocol::Data::Frame.new
|
20
|
-
d.create(:stream_id =>
|
20
|
+
d.create(:stream_id => stream_id, :data => "This is SPDY.")
|
21
21
|
send_data d.to_binary_s
|
22
22
|
|
23
23
|
p [:SPDY, :sent, :DATA]
|
24
24
|
|
25
25
|
d = SPDY::Protocol::Data::Frame.new
|
26
|
-
d.create(:stream_id =>
|
26
|
+
d.create(:stream_id => stream_id, :flags => 1)
|
27
27
|
send_data d.to_binary_s
|
28
28
|
|
29
29
|
p [:SPDY, :sent, :DATA_FIN]
|
@@ -38,6 +38,7 @@ class SPDYHandler < EM::Connection
|
|
38
38
|
|
39
39
|
def unbind
|
40
40
|
p [:SPDY, :connection_closed]
|
41
|
+
@parser.zlib_session.reset
|
41
42
|
end
|
42
43
|
end
|
43
44
|
|
@@ -51,4 +52,4 @@ end
|
|
51
52
|
# (2) start Chrome and force it to use SPDY over SSL.. on OSX:
|
52
53
|
# > /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --use-spdy=ssl
|
53
54
|
#
|
54
|
-
# (3) visit https://localhost:
|
55
|
+
# (3) visit https://localhost:10000/
|
data/lib/spdy.rb
CHANGED
data/lib/spdy/compat.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
if Object.instance_methods.include? "type"
|
2
|
+
class BinData::DSLMixin::DSLParser
|
3
|
+
def name_is_reserved_in_1_8?(name)
|
4
|
+
return false if name == "type"
|
5
|
+
name_is_reserved_in_1_9?(name)
|
6
|
+
end
|
7
|
+
alias_method :name_is_reserved_in_1_9?, :name_is_reserved?
|
8
|
+
alias_method :name_is_reserved?, :name_is_reserved_in_1_8?
|
9
|
+
|
10
|
+
def name_shadows_method_in_1_8?(name)
|
11
|
+
return false if name == "type"
|
12
|
+
name_shadows_method_in_1_9?(name)
|
13
|
+
end
|
14
|
+
alias_method :name_shadows_method_in_1_9?, :name_shadows_method?
|
15
|
+
alias_method :name_shadows_method?, :name_shadows_method_in_1_8?
|
16
|
+
end
|
17
|
+
end
|
data/lib/spdy/compressor.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module SPDY
|
2
|
-
|
2
|
+
class Zlib
|
3
3
|
|
4
4
|
DICT = \
|
5
5
|
"optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" \
|
@@ -18,51 +18,62 @@ module SPDY
|
|
18
18
|
|
19
19
|
CHUNK = 10*1024 # this is silly, but it'll do for now
|
20
20
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
zstream = FFI::Zlib::Z_stream.new
|
26
|
-
zstream[:avail_in] = in_buf.size
|
27
|
-
zstream[:avail_out] = CHUNK
|
28
|
-
zstream[:next_in] = in_buf
|
29
|
-
zstream[:next_out] = out_buf
|
30
|
-
|
31
|
-
result = FFI::Zlib.inflateInit(zstream)
|
21
|
+
def initialize
|
22
|
+
@inflate_zstream = FFI::Zlib::Z_stream.new
|
23
|
+
result = FFI::Zlib.inflateInit(@inflate_zstream)
|
32
24
|
raise "invalid stream" if result != FFI::Zlib::Z_OK
|
33
25
|
|
34
|
-
|
35
|
-
|
26
|
+
@deflate_zstream = FFI::Zlib::Z_stream.new
|
27
|
+
result = FFI::Zlib.deflateInit(@deflate_zstream, FFI::Zlib::Z_DEFAULT_COMPRESSION)
|
28
|
+
raise "invalid stream" if result != FFI::Zlib::Z_OK
|
36
29
|
|
37
|
-
result = FFI::Zlib.
|
30
|
+
result = FFI::Zlib.deflateSetDictionary(@deflate_zstream, DICT, DICT.size)
|
38
31
|
raise "invalid dictionary" if result != FFI::Zlib::Z_OK
|
32
|
+
end
|
39
33
|
|
40
|
-
|
41
|
-
|
34
|
+
def reset
|
35
|
+
result = FFI::Zlib.inflateReset(@inflate_zstream)
|
36
|
+
raise "invalid stream" if result != FFI::Zlib::Z_OK
|
42
37
|
|
43
|
-
|
38
|
+
result = FFI::Zlib.deflateReset(@deflate_zstream)
|
39
|
+
raise "invalid stream" if result != FFI::Zlib::Z_OK
|
44
40
|
end
|
45
41
|
|
46
|
-
def
|
42
|
+
def inflate(data)
|
47
43
|
in_buf = FFI::MemoryPointer.from_string(data)
|
48
44
|
out_buf = FFI::MemoryPointer.new(CHUNK)
|
49
45
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
zstream[:next_out] = out_buf
|
46
|
+
@inflate_zstream[:avail_in] = in_buf.size-1
|
47
|
+
@inflate_zstream[:avail_out] = CHUNK
|
48
|
+
@inflate_zstream[:next_in] = in_buf
|
49
|
+
@inflate_zstream[:next_out] = out_buf
|
55
50
|
|
56
|
-
result = FFI::Zlib.
|
57
|
-
|
51
|
+
result = FFI::Zlib.inflate(@inflate_zstream, FFI::Zlib::Z_SYNC_FLUSH)
|
52
|
+
if result == FFI::Zlib::Z_NEED_DICT
|
53
|
+
result = FFI::Zlib.inflateSetDictionary(@inflate_zstream, DICT, DICT.size)
|
54
|
+
raise "invalid dictionary" if result != FFI::Zlib::Z_OK
|
55
|
+
|
56
|
+
result = FFI::Zlib.inflate(@inflate_zstream, FFI::Zlib::Z_SYNC_FLUSH)
|
57
|
+
end
|
58
58
|
|
59
|
-
result
|
60
|
-
|
59
|
+
raise "cannot inflate" if result != FFI::Zlib::Z_OK && result != FFI::Zlib::Z_STREAM_END
|
60
|
+
|
61
|
+
out_buf.get_bytes(0, CHUNK - @inflate_zstream[:avail_out])
|
62
|
+
end
|
63
|
+
|
64
|
+
def deflate(data)
|
65
|
+
in_buf = FFI::MemoryPointer.from_string(data)
|
66
|
+
out_buf = FFI::MemoryPointer.new(CHUNK)
|
67
|
+
|
68
|
+
@deflate_zstream[:avail_in] = in_buf.size-1
|
69
|
+
@deflate_zstream[:avail_out] = CHUNK
|
70
|
+
@deflate_zstream[:next_in] = in_buf
|
71
|
+
@deflate_zstream[:next_out] = out_buf
|
61
72
|
|
62
|
-
result = FFI::Zlib.deflate(
|
73
|
+
result = FFI::Zlib.deflate(@deflate_zstream, FFI::Zlib::Z_SYNC_FLUSH)
|
63
74
|
raise "cannot deflate" if result != FFI::Zlib::Z_OK
|
64
75
|
|
65
|
-
out_buf.get_bytes(0,
|
76
|
+
out_buf.get_bytes(0, CHUNK - @deflate_zstream[:avail_out])
|
66
77
|
end
|
67
78
|
end
|
68
79
|
end
|
data/lib/spdy/parser.rb
CHANGED
@@ -2,8 +2,11 @@ module SPDY
|
|
2
2
|
class Parser
|
3
3
|
include Protocol
|
4
4
|
|
5
|
+
attr :zlib_session
|
6
|
+
|
5
7
|
def initialize
|
6
8
|
@buffer = ''
|
9
|
+
@zlib_session = Zlib.new
|
7
10
|
end
|
8
11
|
|
9
12
|
def <<(data)
|
@@ -30,7 +33,7 @@ module SPDY
|
|
30
33
|
|
31
34
|
headers = {}
|
32
35
|
if pckt.data.size > 0
|
33
|
-
data =
|
36
|
+
data = @zlib_session.inflate(pckt.data.to_s)
|
34
37
|
headers = NV.new.read(data).to_h
|
35
38
|
end
|
36
39
|
|
@@ -53,19 +56,21 @@ module SPDY
|
|
53
56
|
|
54
57
|
case pckt.type.to_i
|
55
58
|
when 1 then # SYN_STREAM
|
56
|
-
pckt = Control::SynStream.new
|
59
|
+
pckt = Control::SynStream.new({:zlib_session => @zlib_session})
|
57
60
|
unpack_control(pckt, @buffer)
|
58
61
|
|
62
|
+
@on_message_complete.call(pckt.header.stream_id) if @on_message_complete && fin?(pckt.header)
|
63
|
+
|
59
64
|
when 2 then # SYN_REPLY
|
60
|
-
pckt = Control::SynReply.new
|
65
|
+
pckt = Control::SynReply.new({:zlib_session => @zlib_session})
|
61
66
|
unpack_control(pckt, @buffer)
|
62
67
|
|
68
|
+
@on_message_complete.call(pckt.header.stream_id) if @on_message_complete && fin?(pckt.header)
|
69
|
+
|
63
70
|
else
|
64
71
|
raise 'invalid control frame'
|
65
72
|
end
|
66
73
|
|
67
|
-
@on_message_complete.call(pckt.header.stream_id) if @on_message_complete && fin?(pckt.header)
|
68
|
-
|
69
74
|
when DATA_BIT
|
70
75
|
return if @buffer.size < 8
|
71
76
|
|
data/lib/spdy/protocol.rb
CHANGED
@@ -5,13 +5,24 @@ module SPDY
|
|
5
5
|
DATA_BIT = 0
|
6
6
|
VERSION = 2
|
7
7
|
|
8
|
+
SETTINGS_UPLOAD_BANDWIDTH = 1
|
9
|
+
SETTINGS_DOWNLOAD_BANDWIDTH = 2
|
10
|
+
SETTINGS_ROUND_TRIP_TIME = 3
|
11
|
+
SETTINGS_MAX_CONCURRENT_STREAMS = 4
|
12
|
+
SETTINGS_CURRENT_CWND = 5
|
13
|
+
|
8
14
|
module Control
|
9
15
|
module Helpers
|
16
|
+
def initialize_instance
|
17
|
+
super
|
18
|
+
@zlib_session = @params[:zlib_session]
|
19
|
+
end
|
20
|
+
|
10
21
|
def parse(chunk)
|
11
22
|
head = Control::Header.new.read(chunk)
|
12
23
|
self.read(chunk)
|
13
24
|
|
14
|
-
data =
|
25
|
+
data = @zlib_session.inflate(self.data.to_s)
|
15
26
|
self.uncompressed_data = NV.new.read(data)
|
16
27
|
self
|
17
28
|
end
|
@@ -26,7 +37,7 @@ module SPDY
|
|
26
37
|
nv = SPDY::Protocol::NV.new
|
27
38
|
nv.create(opts[:headers])
|
28
39
|
|
29
|
-
nv =
|
40
|
+
nv = @zlib_session.deflate(nv.to_binary_s)
|
30
41
|
self.header.len = self.header.len.to_i + nv.size
|
31
42
|
|
32
43
|
self.data = nv
|
@@ -82,6 +93,121 @@ module SPDY
|
|
82
93
|
build({:type => 2, :len => 6}.merge(opts))
|
83
94
|
end
|
84
95
|
end
|
96
|
+
|
97
|
+
class RstStream < BinData::Record
|
98
|
+
hide :u1
|
99
|
+
|
100
|
+
bit1 :frame, :initial_value => CONTROL_BIT
|
101
|
+
bit15 :version, :initial_value => VERSION
|
102
|
+
bit16 :type, :value => 3
|
103
|
+
|
104
|
+
bit8 :flags, :value => 0
|
105
|
+
bit24 :len, :value => 8
|
106
|
+
|
107
|
+
bit1 :u1
|
108
|
+
bit31 :stream_id
|
109
|
+
|
110
|
+
bit32 :status_code
|
111
|
+
|
112
|
+
def parse(chunk)
|
113
|
+
self.read(chunk)
|
114
|
+
self
|
115
|
+
end
|
116
|
+
|
117
|
+
def create(opts = {})
|
118
|
+
self.stream_id = opts.fetch(:stream_id, 1)
|
119
|
+
self.status_code = opts.fetch(:status_code, 5)
|
120
|
+
self
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class Settings < BinData::Record
|
125
|
+
bit1 :frame, :initial_value => CONTROL_BIT
|
126
|
+
bit15 :version, :initial_value => VERSION
|
127
|
+
bit16 :type, :value => 4
|
128
|
+
|
129
|
+
bit8 :flags
|
130
|
+
bit24 :len, :value => lambda { pairs * 8 }
|
131
|
+
bit32 :pairs
|
132
|
+
|
133
|
+
array :headers, :initial_length => :pairs do
|
134
|
+
bit32 :id_data
|
135
|
+
bit32 :value_data
|
136
|
+
end
|
137
|
+
|
138
|
+
def parse(chunk)
|
139
|
+
self.read(chunk)
|
140
|
+
self
|
141
|
+
end
|
142
|
+
|
143
|
+
def create(opts = {})
|
144
|
+
opts.each do |k, v|
|
145
|
+
key = SPDY::Protocol.const_get(k.to_s.upcase)
|
146
|
+
self.headers << { :id_data => key , :value_data => v }
|
147
|
+
end
|
148
|
+
self.pairs = opts.size
|
149
|
+
self
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class Ping < BinData::Record
|
154
|
+
bit1 :frame, :initial_value => CONTROL_BIT
|
155
|
+
bit15 :version, :initial_value => VERSION
|
156
|
+
bit16 :type, :value => 6
|
157
|
+
|
158
|
+
bit8 :flags, :value => 0
|
159
|
+
bit24 :len, :value => 4
|
160
|
+
|
161
|
+
bit32 :ping_id
|
162
|
+
|
163
|
+
def parse(chunk)
|
164
|
+
self.read(chunk)
|
165
|
+
self
|
166
|
+
end
|
167
|
+
|
168
|
+
def create(opts = {})
|
169
|
+
self.ping_id = opts.fetch(:ping_id, 1)
|
170
|
+
self
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class Goaway < BinData::Record
|
175
|
+
hide :u1
|
176
|
+
|
177
|
+
bit1 :frame, :initial_value => CONTROL_BIT
|
178
|
+
bit15 :version, :initial_value => VERSION
|
179
|
+
bit16 :type, :value => 7
|
180
|
+
|
181
|
+
bit8 :flags, :value => 0
|
182
|
+
bit24 :len, :value => 4
|
183
|
+
|
184
|
+
bit1 :u1
|
185
|
+
bit31 :stream_id
|
186
|
+
|
187
|
+
def parse(chunk)
|
188
|
+
self.read(chunk)
|
189
|
+
self
|
190
|
+
end
|
191
|
+
|
192
|
+
def create(opts = {})
|
193
|
+
self.stream_id = opts.fetch(:stream_id, 1)
|
194
|
+
self
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
class Headers < BinData::Record
|
200
|
+
attr_accessor :uncompressed_data
|
201
|
+
include Helpers
|
202
|
+
|
203
|
+
header :header
|
204
|
+
bit16 :unused
|
205
|
+
string :data, :read_length => lambda { header.len - 6 }
|
206
|
+
|
207
|
+
def create(opts = {})
|
208
|
+
build({:type => 8, :len => 6}.merge(opts))
|
209
|
+
end
|
210
|
+
end
|
85
211
|
end
|
86
212
|
|
87
213
|
module Data
|
data/lib/spdy/version.rb
CHANGED
data/spec/compressor_spec.rb
CHANGED
@@ -2,13 +2,24 @@ require 'helper'
|
|
2
2
|
|
3
3
|
describe SPDY::Zlib do
|
4
4
|
it "should inflate header with custom dictionary" do
|
5
|
-
SPDY::Zlib.
|
5
|
+
zlib_session = SPDY::Zlib.new
|
6
|
+
zlib_session.inflate(COMPRESSED_HEADER).should match('HTTP/1.1')
|
6
7
|
end
|
7
8
|
|
8
9
|
it "should deflate header with custom dictionary" do
|
9
|
-
|
10
|
-
rinse = SPDY::Zlib.inflate(SPDY::Zlib.deflate(orig))
|
10
|
+
zlib_session = SPDY::Zlib.new
|
11
11
|
|
12
|
+
orig = zlib_session.inflate(COMPRESSED_HEADER)
|
13
|
+
zlib_session.reset
|
14
|
+
|
15
|
+
rinse = zlib_session.inflate(zlib_session.deflate(orig))
|
12
16
|
orig.should == rinse
|
13
17
|
end
|
14
|
-
|
18
|
+
|
19
|
+
it "can deflate multiple packets" do
|
20
|
+
zlib_session = SPDY::Zlib.new
|
21
|
+
|
22
|
+
zlib_session.inflate(COMPRESSED_HEADER_1)
|
23
|
+
zlib_session.inflate(COMPRESSED_HEADER_2).should == UNCOMPRESSED_HEADER_2
|
24
|
+
end
|
25
|
+
end
|
data/spec/helper.rb
CHANGED
@@ -3,13 +3,29 @@ require 'spdy'
|
|
3
3
|
|
4
4
|
COMPRESSED_HEADER = "8\xEA\xDF\xA2Q\xB2b\xE0f`\x83\xA4\x17\x86(4\xDBu0\xAC\xD7\x06\x89\xC2\xFDa]hk\xA0g\xA9\x83p\x13X\xC0B\a\xEE?\x1D-}-\xB0\x98)\x03\x1Fjff\x90\xF3\f\xF6\x87:U\a\xECV\xB0:s\x1D\x88zc\x06~\xB4\xEC\xCE \b\xF2\x8C\x0E\xD47:\xC5)\xC9\x19p5\xB0\x14\xC2\xC0\x97\x9A\xA7\e\x1A\xAC\x93\nu\b\x03\e$V\x19Z\x8A\x8D\x8C\xD3\xD2u\x8D\v\x8C\xCC\x8D\x92\v,\xCB\r\xE2\x8Bl\xCD\xAD\x15\f\xB3\xCD\v\xCDu3\rR\xCC\xD3\x8B\v\r-\xCC\x81\xA2\x06\xD6\n\xF1 '\x96$\xA5&\x96\x18\x01d[\x9Cj\x9CUQ\x92dT\x99e\x9C\x9A\x93\x93j\f\x94\x8D//)\x8F/\xCB,\x8E\afy[k\x85\xB2\xC4\xBC\xCC\x92\xCA\xF8\xCC\x14\xDB4c#\x8B\xE4$3\x13c\x93d`\xFEM12N154OI32O\x03\x16\x04\xA6\x96I\f,\xA0\xC2\x88\x81\x0F\x94bs@L+K\x03\x03\x03\x06\xB6\\`!\x98\x9F\xC2\xC0\xEC\xEE\x1A\xC2\xC0V\f\xCC7\xB9\xA9\f\xAC\x19%%\x05\xC5\f\xCC\xA0\bb\xD4g\xE0B\x94*\fe\xBE\xF9U\x9999\x89\xFA\xA6z\x06\x00)h\xF8&&g\xE6\x95\xE4\x17gX+x\x02\x13z\x8E\x02P@\xC1?X!B\xC1\xD0 \xDE,\xDE\\S\xC1\x11\x18\x87\xA9\xE1\xA9I\xDE\x99%\xFA\xA6\xC6&zF&\n\x1A\xDE\x1E!\xBE>:\n9\x99\xD9\xA9\n\xEE\xA9\xC9\xD9\xF9\x9A\n\xCE\x19\xC0\xD22U\xDF\xD0P\xCF@\xCF\xCC\xD2L\xCF\xC8B!81-\xB1(\x13\xAA\x89\x81\x1D\x9Af\x188`I\t\x00\x00\x00\xFF\xFF"
|
5
5
|
|
6
|
+
COMPRESSED_HEADER_1 = "8\xEA\xDF\xA2Q\xB2b\xE0b`\x83\xA4\x17\x06{\xB8\vu0,\xD6\xAE@\x17\xCD\xCD\xB1.\xB45\xD0\xB3\xD4\xD1\xD2\xD7\x02\xB3,\x18\xF8Ps,\x83\x9Cg\xB0?\xD4=:`\a\x81\xD5\x99\xEB@\xD4\e3\xF0\xA3\xE5i\x06A\x90\x8Bu\xA0N\xD6)NI\xCE\x80\xAB\x81%\x03\x06\xBE\xD4<\xDD\xD0`\x9D\xD4<\xA8\xA5,\xA0<\xCE\xC0\x0FJ\b9 \xA6\x150\xE3\x19\x180\xB0\xE5\x02\v\x97\xFC\x14\x06fw\xD7\x10\x06\xB6b`z\xCCMe`\xCD())(f`\x06y\x9CQ\x9F\x81\v\x91[\x19\xD2}\xF3\xAB2sr\x12\xF5M\xF5\f\x144\x00\x8A04\xB4V\xF0\xC9\xCC+\xADP\xA8\xB00\x8B73\xD1Tp\x04z>5<5\xC9;\xB3D\xDF\xD4\xD8D\xCF\x18\xA8\xCC\xDB#\xC4\xD7GG!'3;U\xC1=59;_S\xC19\x03X\xEC\xA4\xEA\e\x1A\xE9\x01}jb\x04R\x16\x9C\x98\x96X\x94\t\xD5\xC4\xC0\x0E\r|\x06\x0EX\x9C\x00\x00\x00\x00\xFF\xFF"
|
7
|
+
|
8
|
+
COMPRESSED_HEADER_2 = "B\x8A\x02f``\x0E\xAD`\xE4\xD1OK,\xCB\x04f3= 1XB\x14\x00\x00\x00\xFF\xFF"
|
9
|
+
|
10
|
+
UNCOMPRESSED_HEADER_1 = "\x00\n\x00\x06accept\x00?text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\x00\x0eaccept-charset\x00\x1eISO-8859-1,utf-8;q=0.7,*;q=0.3\x00\x0faccept-encoding\x00\x11gzip,deflate,sdch\x00\x0faccept-language\x00\x0een-US,en;q=0.8\x00\x04host\x00\x0flocalhost:10000\x00\x06method\x00\x03GET\x00\x06scheme\x00\x05https\x00\x03url\x00\x01/\x00\nuser-agent\x00gMozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.30 Safari/534.30\x00\x07version\x00\x08HTTP/1.1"
|
11
|
+
|
12
|
+
UNCOMPRESSED_HEADER_2 = "\x00\n\x00\x06accept\x00\x03*/*\x00\x0eaccept-charset\x00\x1eISO-8859-1,utf-8;q=0.7,*;q=0.3\x00\x0faccept-encoding\x00\x11gzip,deflate,sdch\x00\x0faccept-language\x00\x0een-US,en;q=0.8\x00\x04host\x00\x0flocalhost:10000\x00\x06method\x00\x03GET\x00\x06scheme\x00\x05https\x00\x03url\x00\x0c/favicon.ico\x00\nuser-agent\x00gMozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.30 Safari/534.30\x00\x07version\x00\x08HTTP/1.1"
|
13
|
+
|
6
14
|
SYN_STREAM = "\x80\x02\x00\x01\x01\x00\x01E\x00\x00\x00\x01\x00\x00\x00\x00\x00\x008\xEA\xDF\xA2Q\xB2b\xE0f`\x83\xA4\x17\x86(4\xDBu0\xAC\xD7\x06\x89\xC2\xFDa]hk\xA0g\xA9\x83p\x13X\xC0B\a\xEE?\x1D-}-\xB0\x98)\x03\x1Fjff\x90\xF3\f\xF6\x87:U\a\xECV\xB0:s\x1D\x88zc\x06~\xB4\xEC\xCE \b\xF2\x8C\x0E\xD47:\xC5)\xC9\x19p5\xB0\x14\xC2\xC0\x97\x9A\xA7\e\x1A\xAC\x93\nu\b\x03/J:d\xE0\x84\x86\x96\xAD\x01\x03\v\xA8``\xE0342\xD73\x00BC+K\x03\x03\x03\x06\xB6\\`\x81\x94\x9F\xC2\xC0\xEC\xEE\x1A\xC2\xC0V\f\xD4\x9B\x9B\xCA\xC0\x9AQRRP\xCC\xC0\f\n,\x11}{\x80\x80a\x9Do\x9B\xA8\x06,\x10\x80\xC5\x86mVq~\x1E\x03\x17\"\xD33\x94\xF9\xE6We\xE6\xE4$\xEA\x9B\xEA\x19(h\xF8&&g\xE6\x95\xE4\x17gX+x\x02\xD3a\x8E\x02P@\xC1?X!B\xC1\xD0 \xDE,\xDE\\S\xC1\x11\x18\xC4\xA9\xE1\xA9I\xDE\x99%\xFA\xA6\xC6&zF&\n\x1A\xDE\x1E!\xBE>:\n9\x99\xD9\xA9\n\xEE\xA9\xC9\xD9\xF9\x9A\n\xCE\x19\xC0\xC2,U\xDF\xD0\x10\xE8X3K3=#\v\x85\xE0\xC4\xB4\xC4\xA2L\xA8&\x06vh\x942p\xC0b\x1A\x00\x00\x00\xFF\xFF"
|
7
15
|
|
8
16
|
SYN_REPLY = "\x80\x02\x00\x02\x00\x00\x005\x00\x00\x00\x01\x00\x00x\xbb\xdf\xa2Q\xb2b`f\xe0q\x86\x06R\x080\x90\x18\xb8\x10v0\xb0A\x943\xb0\x01\x93\xb1\x82\xbf7\x03;T#\x03\x07\xcc<\x00\x00\x00\x00\xff\xff"
|
9
17
|
|
10
18
|
RST_STREAM = "\x80\x02\x00\x03\x00\x00\x00\b\x00\x00\x00\x01\x00\x00\x00\x01"
|
11
19
|
|
20
|
+
SETTINGS = "\x80\x02\x00\x04\x00\x00\x00\b\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x01,"
|
21
|
+
|
12
22
|
DATA = "\x00\x00\x00\x01\x00\x00\x00\rThis is SPDY."
|
13
23
|
DATA_FIN = "\x00\x00\x00\x01\x01\x00\x00\x00"
|
14
24
|
|
15
|
-
NV = "\x00\x03\x00\x0cContent-Type\x00\ntext/plain\x00\x06status\x00\x06200 OK\x00\x07version\x00\x08HTTP/1.1"
|
25
|
+
NV = "\x00\x03\x00\x0cContent-Type\x00\ntext/plain\x00\x06status\x00\x06200 OK\x00\x07version\x00\x08HTTP/1.1"
|
26
|
+
|
27
|
+
PING = "\x80\x02\x00\x06\x00\x00\x00\x04\x00\x00\x00\x01"
|
28
|
+
|
29
|
+
GOAWAY = "\x80\x02\x00\x07\x00\x00\x00\x04\x00\x00\x00\x01"
|
30
|
+
|
31
|
+
HEADERS = "\x80\x02\x00\x08\x01\x00\x01A\x00\x00\x00\x01\x00\x008\xEA\xDF\xA2Q\xB2b\xE0f`\x83\xA4\x17\x86(4\xDBu0\xAC\xD7\x06\x89\xC2\xFDa]hk\xA0g\xA9\x83p\x13X\xC0B\a\xEE?\x1D-}-\xB0\x98)\x03\x1Fjff\x90\xF3\f\xF6\x87:U\a\xECV\xB0:s\x1D\x88zc\x06~\xB4\xEC\xCE \b\xF2\x8C\x0E\xD47:\xC5)\xC9\x19p5\xB0\x14\xC2\xC0\x97\x9A\xA7\e\x1A\xAC\x93\nu\b\x03/J:d\xE0\x84\x86\x96\xAD\x01\x03\v\xA8``\xE0342\xD73\x00BC+K\x03\x03\x03\x06\xB6\\`\x81\x94\x9F\xC2\xC0\xEC\xEE\x1A\xC2\xC0V\f\xD4\x9B\x9B\xCA\xC0\x9AQRRP\xCC\xC0\f\n,\x11}{\x80\x80a\x9Do\x9B\xA8\x06,\x10\x80\xC5\x86mVq~\x1E\x03\x17\"\xD33\x94\xF9\xE6We\xE6\xE4$\xEA\x9B\xEA\x19(h\xF8&&g\xE6\x95\xE4\x17gX+x\x02\xD3a\x8E\x02P@\xC1?X!B\xC1\xD0 \xDE,\xDE\\S\xC1\x11\x18\xC4\xA9\xE1\xA9I\xDE\x99%\xFA\xA6\xC6&zF&\n\x1A\xDE\x1E!\xBE>:\n9\x99\xD9\xA9\n\xEE\xA9\xC9\xD9\xF9\x9A\n\xCE\x19\xC0\xC2,U\xDF\xD0\x10\xE8X3K3=#\v\x85\xE0\xC4\xB4\xC4\xA2L\xA8&\x06vh\x942p\xC0b\x1A\x00\x00\x00\xFF\xFF"
|
data/spec/protocol_spec.rb
CHANGED
@@ -1,109 +1,404 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
describe SPDY::Protocol do
|
4
|
+
context "data frames" do
|
5
|
+
describe "DATA" do
|
6
|
+
it "should create a data frame" do
|
7
|
+
data = "This is SPDY."
|
4
8
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
+
d = SPDY::Protocol::Data::Frame.new
|
10
|
+
d.create(:stream_id => 1, :data => data)
|
11
|
+
|
12
|
+
d.to_binary_s.should == DATA
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should create a FIN data frame" do
|
16
|
+
d = SPDY::Protocol::Data::Frame.new
|
17
|
+
d.create(:stream_id => 1, :flags => 1)
|
18
|
+
|
19
|
+
d.to_binary_s.should == DATA_FIN
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should read a FIN data frame" do
|
23
|
+
d = SPDY::Protocol::Data::Frame.new
|
24
|
+
d.create(:stream_id => 1, :flags => 1)
|
25
|
+
|
26
|
+
d.to_binary_s.should == DATA_FIN
|
27
|
+
pckt = SPDY::Protocol::Data::Frame.new.read(d.to_binary_s)
|
28
|
+
pckt.flags.should == 1
|
29
|
+
end
|
9
30
|
|
10
|
-
nv.to_binary_s.should == NV
|
11
31
|
end
|
12
32
|
end
|
13
33
|
|
14
|
-
context "
|
15
|
-
|
16
|
-
|
34
|
+
context "control frames" do
|
35
|
+
describe "SYN_STREAM" do
|
36
|
+
it "should create a SYN_STREAM packet" do
|
37
|
+
zlib_session = SPDY::Zlib.new
|
17
38
|
|
18
|
-
|
19
|
-
"accept"=>"application/xml", "host"=>"127.0.0.1:9000",
|
20
|
-
"method"=>"GET", "scheme"=>"https",
|
21
|
-
"url"=>"/?echo=a&format=json","version"=>"HTTP/1.1"
|
22
|
-
}
|
39
|
+
sr = SPDY::Protocol::Control::SynStream.new({:zlib_session => zlib_session})
|
23
40
|
|
24
|
-
|
25
|
-
|
26
|
-
|
41
|
+
headers = {
|
42
|
+
"accept"=>"application/xml", "host"=>"127.0.0.1:9000",
|
43
|
+
"method"=>"GET", "scheme"=>"https",
|
44
|
+
"url"=>"/?echo=a&format=json","version"=>"HTTP/1.1"
|
45
|
+
}
|
27
46
|
|
28
|
-
|
29
|
-
|
47
|
+
sr.create({:stream_id => 1, :headers => headers})
|
48
|
+
sr.header.version.should == 2
|
49
|
+
sr.pri.should == 0
|
30
50
|
|
31
|
-
|
32
|
-
|
33
|
-
st.num_bytes.should == sr.to_binary_s.size
|
34
|
-
end
|
51
|
+
sr.header.len.should > 50
|
52
|
+
sr.data.should_not be_nil
|
35
53
|
|
36
|
-
|
37
|
-
|
38
|
-
|
54
|
+
st = SPDY::Protocol::Control::SynStream.new({:zlib_session => zlib_session})
|
55
|
+
st.parse(sr.to_binary_s)
|
56
|
+
st.num_bytes.should == sr.to_binary_s.size
|
57
|
+
end
|
39
58
|
|
40
|
-
|
59
|
+
it "should parse SYN_STREAM packet" do
|
60
|
+
zlib_session = SPDY::Zlib.new
|
41
61
|
|
42
|
-
|
43
|
-
|
44
|
-
sr.uncompressed_data.to_h['method'].should == 'GET'
|
62
|
+
sr = SPDY::Protocol::Control::SynStream.new({:zlib_session => zlib_session})
|
63
|
+
sr.parse(SYN_STREAM)
|
45
64
|
|
46
|
-
|
65
|
+
sr.num_bytes.should == SYN_STREAM.size
|
66
|
+
|
67
|
+
sr.header.type.should == 1
|
68
|
+
sr.uncompressed_data.to_h.class.should == Hash
|
69
|
+
sr.uncompressed_data.to_h['method'].should == 'GET'
|
70
|
+
|
71
|
+
sr.to_binary_s.should == SYN_STREAM
|
72
|
+
end
|
47
73
|
end
|
48
|
-
end
|
49
74
|
|
50
|
-
|
51
|
-
|
52
|
-
|
75
|
+
describe "SYN_REPLY" do
|
76
|
+
describe "creating a packet" do
|
77
|
+
before do
|
78
|
+
zlib_session = SPDY::Zlib.new
|
79
|
+
|
80
|
+
@sr = SPDY::Protocol::Control::SynReply.new({:zlib_session => zlib_session})
|
81
|
+
|
82
|
+
headers = {'Content-Type' => 'text/plain', 'status' => '200 OK', 'version' => 'HTTP/1.1'}
|
83
|
+
@sr.create({:stream_id => 1, :headers => headers})
|
84
|
+
end
|
53
85
|
|
54
|
-
|
55
|
-
|
86
|
+
describe "common control frame fields" do
|
87
|
+
it "is version 2" do
|
88
|
+
@sr.header.version.should == 2
|
89
|
+
end
|
90
|
+
it "is type 2" do
|
91
|
+
@sr.header.type.should == 2
|
92
|
+
end
|
93
|
+
it "has empty flags" do
|
94
|
+
@sr.header.flags.should == 0
|
95
|
+
end
|
96
|
+
end
|
56
97
|
|
57
|
-
|
58
|
-
|
98
|
+
describe "type specific frame fields" do
|
99
|
+
it "has a stream id" do
|
100
|
+
@sr.header.stream_id.should == 1
|
101
|
+
end
|
102
|
+
it "has data" do
|
103
|
+
@sr.data.should_not be_nil
|
104
|
+
end
|
105
|
+
specify { @sr.header.len.should > 50 }
|
106
|
+
end
|
59
107
|
|
60
|
-
|
61
|
-
|
108
|
+
describe "assembled packet" do
|
109
|
+
before do
|
110
|
+
@packet = @sr.to_binary_s
|
111
|
+
end
|
62
112
|
|
63
|
-
|
113
|
+
specify "starts with a control bit" do
|
114
|
+
@packet[0...1].should == "\x80"
|
115
|
+
end
|
116
|
+
specify "followed by the version" do
|
117
|
+
@packet[1...2].should == "\x02"
|
118
|
+
end
|
119
|
+
specify "followed by the type" do
|
120
|
+
@packet[2..3].should == "\x00\x02"
|
121
|
+
end
|
122
|
+
specify "followed by flags" do
|
123
|
+
@packet[4...5].should == "\x00"
|
124
|
+
end
|
125
|
+
specify "followed by the length" do
|
126
|
+
@packet[5..7].should == "\x00\x005"
|
127
|
+
end
|
128
|
+
specify "followed by the stream ID" do
|
129
|
+
@packet[8..11].should == "\x00\x00\x00\x01"
|
130
|
+
end
|
131
|
+
specify "followed by unused space" do
|
132
|
+
@packet[12..13].should == "\x00\x00"
|
133
|
+
end
|
134
|
+
specify "followed by compressed NV data" do
|
135
|
+
zlib_session = SPDY::Zlib.new
|
136
|
+
|
137
|
+
data = zlib_session.inflate(@packet[14..-1].to_s)
|
138
|
+
data.should =~ %r{\x00\x0cContent-Type}
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should parse SYN_REPLY packet" do
|
145
|
+
zlib_session = SPDY::Zlib.new
|
146
|
+
|
147
|
+
sr = SPDY::Protocol::Control::SynReply.new({:zlib_session => zlib_session})
|
148
|
+
sr.parse(SYN_REPLY)
|
149
|
+
|
150
|
+
sr.header.type.should == 2
|
151
|
+
sr.uncompressed_data.to_h.class.should == Hash
|
152
|
+
sr.uncompressed_data.to_h['status'].should == '200 OK'
|
153
|
+
|
154
|
+
sr.to_binary_s.should == SYN_REPLY
|
155
|
+
end
|
64
156
|
end
|
65
157
|
|
66
|
-
|
67
|
-
|
68
|
-
|
158
|
+
describe "RST_STREAM" do
|
159
|
+
it "can parse a reset packet" do
|
160
|
+
ping = SPDY::Protocol::Control::RstStream.new
|
161
|
+
ping.parse(RST_STREAM)
|
69
162
|
|
70
|
-
|
71
|
-
|
72
|
-
sr.uncompressed_data.to_h['status'].should == '200 OK'
|
163
|
+
ping.stream_id.should == 1
|
164
|
+
ping.type.should == 3
|
73
165
|
|
74
|
-
|
166
|
+
ping.to_binary_s.should == RST_STREAM
|
167
|
+
end
|
168
|
+
|
169
|
+
describe "the assembled packet" do
|
170
|
+
before do
|
171
|
+
@rs = SPDY::Protocol::Control::RstStream.new
|
172
|
+
@rs.create(:stream_id => 1, :status_code => 1)
|
173
|
+
@frame = Array(@rs.to_binary_s.bytes)
|
174
|
+
end
|
175
|
+
specify "starts with a control bit" do
|
176
|
+
@frame[0].should == 128
|
177
|
+
end
|
178
|
+
specify "followed by the version (2)" do
|
179
|
+
@frame[1].should == 2
|
180
|
+
end
|
181
|
+
specify "followed by the type (3)" do
|
182
|
+
@frame[2..3].should == [0,3]
|
183
|
+
end
|
184
|
+
specify "followed by flags (0)" do
|
185
|
+
@frame[4].should == 0
|
186
|
+
end
|
187
|
+
specify "followed by the length (always 8)" do
|
188
|
+
@frame[5..7].should == [0,0,8]
|
189
|
+
end
|
190
|
+
specify "followed by the status code" do
|
191
|
+
@frame[8..11].should == [0,0,0,1]
|
192
|
+
end
|
193
|
+
end
|
75
194
|
end
|
76
|
-
end
|
77
195
|
|
78
|
-
|
79
|
-
|
80
|
-
|
196
|
+
describe "SETTINGS" do
|
197
|
+
it "can parse a SETTINGS packet" do
|
198
|
+
settings = SPDY::Protocol::Control::Settings.new
|
199
|
+
settings.parse(SETTINGS)
|
200
|
+
|
201
|
+
settings.type.should == 4
|
202
|
+
settings.pairs.should == 1
|
203
|
+
|
204
|
+
settings.headers[0].id_data.should == SPDY::Protocol::SETTINGS_ROUND_TRIP_TIME
|
205
|
+
settings.headers[0].value_data.should == 300
|
206
|
+
|
207
|
+
settings.to_binary_s.should == SETTINGS
|
208
|
+
end
|
81
209
|
|
82
|
-
|
83
|
-
|
210
|
+
describe "the assembled packet" do
|
211
|
+
before do
|
212
|
+
@settings = SPDY::Protocol::Control::Settings.new
|
213
|
+
@settings.create(:settings_round_trip_time => 300)
|
214
|
+
@frame = Array(@settings.to_binary_s.bytes)
|
215
|
+
end
|
216
|
+
specify "starts with a control bit" do
|
217
|
+
@frame[0].should == 128
|
218
|
+
end
|
219
|
+
specify "followed by the version (2)" do
|
220
|
+
@frame[1].should == 2
|
221
|
+
end
|
222
|
+
specify "followed by the type (4)" do
|
223
|
+
@frame[2..3].should == [0,4]
|
224
|
+
end
|
225
|
+
specify "followed by flags" do
|
226
|
+
@frame[4].should == 0
|
227
|
+
end
|
228
|
+
specify "followed by the length (24 bits)" do
|
229
|
+
@frame[5..7].should == [0,0,8]
|
230
|
+
end
|
231
|
+
specify "followed by the number of entries (32 bits)" do
|
232
|
+
@frame[8..11].should == [0,0,0,1]
|
233
|
+
end
|
234
|
+
specify "followed by ID/Value Pairs (32 bits each)" do
|
235
|
+
@frame[12..19].should == [0,0,0,3, 0,0,1,44]
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
84
239
|
|
85
|
-
|
240
|
+
describe "NOOP" do
|
241
|
+
specify "not implemented (being dropped from protocol)" do
|
242
|
+
# NOOP
|
243
|
+
end
|
86
244
|
end
|
87
245
|
|
88
|
-
|
89
|
-
|
90
|
-
|
246
|
+
describe "PING" do
|
247
|
+
it "can parse a PING packet" do
|
248
|
+
ping = SPDY::Protocol::Control::Ping.new
|
249
|
+
ping.parse(PING)
|
250
|
+
|
251
|
+
ping.ping_id.should == 1
|
252
|
+
ping.type.should == 6
|
253
|
+
|
254
|
+
ping.to_binary_s.should == PING
|
255
|
+
end
|
91
256
|
|
92
|
-
|
257
|
+
describe "the assembled packet" do
|
258
|
+
before do
|
259
|
+
@ping = SPDY::Protocol::Control::Ping.new
|
260
|
+
@ping.create(:ping_id => 1)
|
261
|
+
@frame = Array(@ping.to_binary_s.bytes)
|
262
|
+
end
|
263
|
+
specify "starts with a control bit" do
|
264
|
+
@frame[0].should == 128
|
265
|
+
end
|
266
|
+
specify "followed by the version (2)" do
|
267
|
+
@frame[1].should == 2
|
268
|
+
end
|
269
|
+
specify "followed by the type (6)" do
|
270
|
+
@frame[2..3].should == [0,6]
|
271
|
+
end
|
272
|
+
specify "followed by flags (0)" do
|
273
|
+
@frame[4].should == 0
|
274
|
+
end
|
275
|
+
specify "followed by the length (always 4)" do
|
276
|
+
@frame[5..7].should == [0,0,4]
|
277
|
+
end
|
278
|
+
end
|
93
279
|
end
|
94
280
|
|
95
|
-
|
96
|
-
|
97
|
-
|
281
|
+
describe "GOAWAY" do
|
282
|
+
it "can parse a GOAWAY packet" do
|
283
|
+
goaway = SPDY::Protocol::Control::Goaway.new
|
284
|
+
goaway.parse(GOAWAY)
|
98
285
|
|
99
|
-
|
100
|
-
|
101
|
-
|
286
|
+
goaway.stream_id.should == 1
|
287
|
+
goaway.type.should == 7
|
288
|
+
|
289
|
+
goaway.to_binary_s.should == GOAWAY
|
290
|
+
end
|
291
|
+
|
292
|
+
describe "the assembled packet" do
|
293
|
+
before do
|
294
|
+
@goaway = SPDY::Protocol::Control::Goaway.new
|
295
|
+
@goaway.create(:stream_id => 42)
|
296
|
+
@frame = Array(@goaway.to_binary_s.bytes)
|
297
|
+
end
|
298
|
+
specify "starts with a control bit" do
|
299
|
+
@frame[0].should == 128
|
300
|
+
end
|
301
|
+
specify "followed by the version (2)" do
|
302
|
+
@frame[1].should == 2
|
303
|
+
end
|
304
|
+
specify "followed by the type (7)" do
|
305
|
+
@frame[2..3].should == [0,7]
|
306
|
+
end
|
307
|
+
specify "followed by flags (0)" do
|
308
|
+
@frame[4].should == 0
|
309
|
+
end
|
310
|
+
specify "followed by the length (always 4)" do
|
311
|
+
@frame[5..7].should == [0,0,4]
|
312
|
+
end
|
313
|
+
specify "followed by the last good stream ID (1 ignored bit + 31 bits)" do
|
314
|
+
@frame[8..11].should == [0,0,0,42]
|
315
|
+
end
|
316
|
+
end
|
102
317
|
end
|
103
318
|
|
104
|
-
|
319
|
+
describe "HEADERS" do
|
320
|
+
it "can parse a HEADERS packet"do
|
321
|
+
zlib_session = SPDY::Zlib.new
|
322
|
+
|
323
|
+
headers = SPDY::Protocol::Control::Headers.new({:zlib_session => zlib_session})
|
324
|
+
headers.parse(HEADERS)
|
325
|
+
|
326
|
+
headers.header.stream_id.should == 1
|
327
|
+
headers.header.type.should == 8
|
328
|
+
|
329
|
+
headers.to_binary_s.should == HEADERS
|
330
|
+
end
|
331
|
+
|
332
|
+
describe "the assembled packet" do
|
333
|
+
before do
|
334
|
+
zlib_session = SPDY::Zlib.new
|
335
|
+
|
336
|
+
@headers = SPDY::Protocol::Control::Headers.new({:zlib_session => zlib_session})
|
105
337
|
|
106
|
-
|
107
|
-
|
108
|
-
|
338
|
+
nv = {'Content-Type' => 'text/plain', 'status' => '200 OK', 'version' => 'HTTP/1.1'}
|
339
|
+
@headers.create({:stream_id => 42, :headers => nv})
|
340
|
+
|
341
|
+
@frame = Array(@headers.to_binary_s.bytes)
|
342
|
+
end
|
343
|
+
specify "starts with a control bit" do
|
344
|
+
@frame[0].should == 128
|
345
|
+
end
|
346
|
+
specify "followed by the version (2)" do
|
347
|
+
@frame[1].should == 2
|
348
|
+
end
|
349
|
+
specify "followed by the type (8)" do
|
350
|
+
@frame[2..3].should == [0,8]
|
351
|
+
end
|
352
|
+
specify "followed by flags (8 bits)" do
|
353
|
+
@frame[4].should == 0
|
354
|
+
end
|
355
|
+
specify "followed by the length (24 bits)" do
|
356
|
+
# 4 bytes (stream ID)
|
357
|
+
# 2 bytes (unused)
|
358
|
+
# N bytes for compressed NV section
|
359
|
+
@frame[5..7].should == [0,0,53]
|
360
|
+
end
|
361
|
+
specify "followed by the stream ID (1 ignored bit + 31 bits)" do
|
362
|
+
@frame[8..11].should == [0,0,0,42]
|
363
|
+
end
|
364
|
+
specify "followed by 16 unused bits" do
|
365
|
+
@frame[12..13].should == [0,0]
|
366
|
+
end
|
367
|
+
specify "followed by name/value pairs" do
|
368
|
+
@frame[14..-1].size.should == 47
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
describe "NV" do
|
374
|
+
describe "creating a packet" do
|
375
|
+
before do
|
376
|
+
nv = SPDY::Protocol::NV.new
|
377
|
+
|
378
|
+
@name_values = {'version' => 'HTTP/1.1', 'status' => '200 OK', 'Content-Type' => 'text/plain'}
|
379
|
+
nv.create(@name_values)
|
380
|
+
|
381
|
+
@binary_string = nv.to_binary_s
|
382
|
+
end
|
383
|
+
|
384
|
+
it "begins with the number of name-value pairs" do
|
385
|
+
@binary_string[0..1].should == "\x00\x03"
|
386
|
+
end
|
387
|
+
|
388
|
+
it "prefaces names with the length of the name" do
|
389
|
+
@binary_string.should =~ %r{\x00\x0cContent-Type}
|
390
|
+
end
|
391
|
+
it "prefaces values with the length of the value" do
|
392
|
+
@binary_string.should =~ %r{\x00\x08HTTP/1.1}
|
393
|
+
end
|
394
|
+
|
395
|
+
it "has 2 bytes (total number of name-value pairs) + 2 bytes for each name (length of name) + 2 bytes for each value (length of value) + names + values" do
|
396
|
+
num_size_bytes = 2 + @name_values.size * (2 + 2)
|
397
|
+
|
398
|
+
@binary_string.length.should ==
|
399
|
+
@name_values.inject(num_size_bytes) {|sum, kv| sum + kv[0].length + kv[1].length}
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
109
404
|
end
|
metadata
CHANGED
@@ -1,61 +1,56 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: spdy
|
3
|
-
version: !ruby/object:Gem::Version
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
4
5
|
prerelease:
|
5
|
-
version: 0.0.2
|
6
6
|
platform: ruby
|
7
|
-
authors:
|
7
|
+
authors:
|
8
8
|
- Ilya Grigorik
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
dependencies:
|
16
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2011-10-06 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
17
15
|
name: bindata
|
18
|
-
|
19
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: &2157355140 !ruby/object:Gem::Requirement
|
20
17
|
none: false
|
21
|
-
requirements:
|
22
|
-
- -
|
23
|
-
- !ruby/object:Gem::Version
|
24
|
-
version:
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
25
22
|
type: :runtime
|
26
|
-
version_requirements: *id001
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: ffi-zlib
|
29
23
|
prerelease: false
|
30
|
-
|
24
|
+
version_requirements: *2157355140
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: ffi-zlib
|
27
|
+
requirement: &2157354720 !ruby/object:Gem::Requirement
|
31
28
|
none: false
|
32
|
-
requirements:
|
33
|
-
- -
|
34
|
-
- !ruby/object:Gem::Version
|
35
|
-
version:
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
36
33
|
type: :runtime
|
37
|
-
version_requirements: *id002
|
38
|
-
- !ruby/object:Gem::Dependency
|
39
|
-
name: rspec
|
40
34
|
prerelease: false
|
41
|
-
|
35
|
+
version_requirements: *2157354720
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
requirement: &2157354300 !ruby/object:Gem::Requirement
|
42
39
|
none: false
|
43
|
-
requirements:
|
44
|
-
- -
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version:
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
47
44
|
type: :development
|
48
|
-
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2157354300
|
49
47
|
description: SPDY is an experiment with protocols for the web
|
50
|
-
email:
|
48
|
+
email:
|
51
49
|
- ilya@igvita.com
|
52
50
|
executables: []
|
53
|
-
|
54
51
|
extensions: []
|
55
|
-
|
56
52
|
extra_rdoc_files: []
|
57
|
-
|
58
|
-
files:
|
53
|
+
files:
|
59
54
|
- .gitignore
|
60
55
|
- .rspec
|
61
56
|
- Gemfile
|
@@ -63,6 +58,7 @@ files:
|
|
63
58
|
- Rakefile
|
64
59
|
- examples/spdy_server.rb
|
65
60
|
- lib/spdy.rb
|
61
|
+
- lib/spdy/compat.rb
|
66
62
|
- lib/spdy/compressor.rb
|
67
63
|
- lib/spdy/parser.rb
|
68
64
|
- lib/spdy/protocol.rb
|
@@ -72,35 +68,31 @@ files:
|
|
72
68
|
- spec/helper.rb
|
73
69
|
- spec/parser_spec.rb
|
74
70
|
- spec/protocol_spec.rb
|
75
|
-
has_rdoc: true
|
76
71
|
homepage: https://github.com/igrigorik/spdy
|
77
72
|
licenses: []
|
78
|
-
|
79
73
|
post_install_message:
|
80
74
|
rdoc_options: []
|
81
|
-
|
82
|
-
require_paths:
|
75
|
+
require_paths:
|
83
76
|
- lib
|
84
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
78
|
none: false
|
86
|
-
requirements:
|
87
|
-
- -
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version:
|
90
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
84
|
none: false
|
92
|
-
requirements:
|
93
|
-
- -
|
94
|
-
- !ruby/object:Gem::Version
|
95
|
-
version:
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
96
89
|
requirements: []
|
97
|
-
|
98
90
|
rubyforge_project: spdy
|
99
|
-
rubygems_version: 1.
|
91
|
+
rubygems_version: 1.8.5
|
100
92
|
signing_key:
|
101
93
|
specification_version: 3
|
102
94
|
summary: SPDY is an experiment with protocols for the web
|
103
|
-
test_files:
|
95
|
+
test_files:
|
104
96
|
- spec/compressor_spec.rb
|
105
97
|
- spec/helper.rb
|
106
98
|
- spec/parser_spec.rb
|