Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

HEADERS parser #12

Merged
merged 6 commits into from

2 participants

@jonasschneider
Collaborator

This adds support for the HEADERS packet.

@igrigorik
Owner

Hmm, this is a bit confusing.. We have the headers coming in SYN_STREAM, which fire on_headers_complete, but then we have a separate on_headers, which is invoked for the HEADERS packets... If memory serves, draft 3 is suppose to reconcile this case by separating the headers out of SYN_STREAM into its own dedicated packet?

For sanity, I'd love to keep a consistent interface (although based on draft 3, this may be a non-issue). For example, since we're basically saying the stream can be augmented with any number of headers at any point (application specific semantics), then perhaps we shouldn't even have the on_headers_complete, since that's just misleading..

@jonasschneider
Collaborator

Yep, the notes on http://www.chromium.org/spdy/spdy-protocol contain this as an expected change for draft 3.

Move the Name/Value blocks out of SYN_STREAM and SYN_REPLY; instead they only exist in HEADERS.

I personally am not sure if this is the right way to go; if a large body is being POSTed, the request can only be processed after the whole body has been sent, as additional HEADERS frames are allowed to come in at any time. But hey, it's the spec.

How about we remove on_headers_complete, but instead add on_stream_establish (or something like that) and
on_headers? When a draft 2 SYN_STREAM arrives, we call on_stream_establish and immediately afterwards on_headers with the headers contained in the packet. Also, we call on_headers when a HEADERS frame arrives.

For draft 3, we can then just stop firing the on_headers callback for SYN_STREAMs.

@igrigorik
Owner

The fact that there are two distinct ways to send headers, to me, is a sign that it should be refactored, so I'm all for the draft 3 change. In terms of changes, let's do the following:

  • Remove on_headers_complete
  • Add on_headers and fire the callback on SYN_STREAM and HEADERS packets, this way, once draft 3 comes around we'll just remove the SYN_STREAM and preserve the api
  • How about on_open for new connections, to reuse the language from websocket's, etc.
@jonasschneider
Collaborator

That's about what I wanted to say.

@jonasschneider
Collaborator

Once we go draft 3, we can then just remove unpack_control(pckt, data) and move the call to pckt.parse into try_parse.

lib/spdy/parser.rb
@@ -38,12 +42,10 @@ def on_reset(&blk)
def unpack_control(pckt, data)
pckt.parse(data)
-
- if @on_headers_complete
- @on_headers_complete.call(pckt.header.stream_id.to_i,
- (pckt.associated_to_stream_id.to_i rescue nil),
- (pckt.pri.to_i rescue nil),
- pckt.uncompressed_data.to_h)
+
+ if @on_headers && pckt.uncompressed_data.to_h != {}
@igrigorik Owner

pedantic, but let's call to_h line higher to avoid creating two hashes (one in if, one lower), and to_h.empty?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jonasschneider
Collaborator

Done.

@igrigorik igrigorik merged commit 5b74597 into from
@igrigorik
Owner

Great stuff, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 4, 2012
  1. @jonasschneider

    add parser support for HEADERS

    jonasschneider authored
    Conflicts:
    
    	lib/spdy/parser.rb
    	spec/parser_spec.rb
  2. @jonasschneider
  3. @jonasschneider

    Remove on_headers_complete, add on_open.

    jonasschneider authored
    SYN_STREAM fires both of them now.
  4. @jonasschneider
  5. @jonasschneider
  6. @jonasschneider
This page is out of date. Refresh to see the latest.
Showing with 85 additions and 17 deletions.
  1. +23 −9 lib/spdy/parser.rb
  2. +62 −8 spec/parser_spec.rb
View
32 lib/spdy/parser.rb
@@ -14,14 +14,18 @@ def <<(data)
try_parse
end
- def on_headers_complete(&blk)
- @on_headers_complete = blk
+ def on_open(&blk)
+ @on_open = blk
end
def on_ping(&blk)
@on_ping = blk
end
+ def on_headers(&blk)
+ @on_headers = blk
+ end
+
def on_body(&blk)
@on_body = blk
end
@@ -38,12 +42,11 @@ def on_reset(&blk)
def unpack_control(pckt, data)
pckt.parse(data)
-
- if @on_headers_complete
- @on_headers_complete.call(pckt.header.stream_id.to_i,
- (pckt.associated_to_stream_id.to_i rescue nil),
- (pckt.pri.to_i rescue nil),
- pckt.uncompressed_data.to_h)
+
+ headers = pckt.uncompressed_data.to_h
+ if @on_headers && !headers.empty?
+ @on_headers.call(pckt.header.stream_id.to_i,
+ headers)
end
end
@@ -60,8 +63,13 @@ def try_parse
case pckt.type.to_i
when 1 then # SYN_STREAM
pckt = Control::SynStream.new({:zlib_session => @zlib_session})
+ if @on_open
+ @on_open.call(pckt.header.stream_id,
+ (pckt.associated_to_stream_id.to_i rescue nil),
+ (pckt.pri.to_i rescue nil))
+ end
unpack_control(pckt, @buffer)
-
+
@on_message_complete.call(pckt.header.stream_id) if @on_message_complete && fin?(pckt.header)
when 2 then # SYN_REPLY
@@ -83,6 +91,12 @@ def try_parse
@on_reset.call(pckt.stream_id, pckt.status_code) if @on_reset
+ when 8 then # HEADERS
+ pckt = Control::Headers.new({:zlib_session => @zlib_session})
+ pckt.parse(@buffer)
+
+ @on_headers.call(pckt.header.stream_id, pckt.uncompressed_data.to_h) if @on_headers
+
else
raise 'invalid control frame'
end
View
70 spec/parser_spec.rb
@@ -6,7 +6,7 @@
context "callbacks" do
it "should accept header callback" do
lambda do
- s.on_headers_complete {}
+ s.on_headers {}
end.should_not raise_error
end
@@ -38,7 +38,7 @@
data.should == 'This is SPDY.'
fired = false
- s.on_headers_complete { fired = true }
+ s.on_open { fired = true }
s << SYN_STREAM
fired.should be_true
@@ -54,21 +54,31 @@
context "CONTROL" do
it "should parse SYN_STREAM packet" do
fired = false
- s.on_headers_complete { fired = true }
+ s.on_open { fired = true }
s << SYN_STREAM
fired.should be_true
end
- it "should return parsed headers" do
- sid, asid, pri, headers = nil
- s.on_headers_complete do |stream, astream, priority, head|
- sid = stream; asid = astream; pri = priority; headers = head
+ it "should return parsed headers for SYN_STREAM" do
+ sid, sid2, asid, pri, headers = nil
+ order = []
+ s.on_open do |stream, astream, priority|
+ order << :open
+ sid = stream; asid = astream; pri = priority;
+ end
+
+ s.on_headers do |stream, head|
+ order << :headers
+ sid2 = stream; headers = head
end
s << SYN_STREAM
+ order.should == [:open, :headers]
+
sid.should == 1
+ sid2.should == 1
asid.should == 0
pri.should == 0
@@ -76,6 +86,28 @@
headers['version'].should == "HTTP/1.1"
end
+ it "should not fire the on_open callback for SYN_REPLY" do
+ failed = false
+ s.on_open { failed = true }
+ s << SYN_REPLY
+
+ failed.should be_false
+ end
+
+ it "should return parsed headers for SYN_REPLY" do
+ sid, headers = nil
+ s.on_headers do |stream, head|
+ sid = stream; headers = head
+ end
+
+ s << SYN_REPLY
+
+ sid.should == 1
+
+ headers.class.should == Hash
+ headers['version'].should == "HTTP/1.1"
+ end
+
it "should parse PING packet" do
fired = false
s.on_ping { |num| fired = num }
@@ -83,6 +115,28 @@
fired.should == 1
end
+
+ it "should parse HEADERS packet" do
+ fired = false
+ s.on_headers { fired = true }
+ s << HEADERS
+
+ fired.should be_true
+ end
+
+ it "should return parsed headers for HEADERS" do
+ sid, headers = nil
+ s.on_headers do |stream, head|
+ sid = stream; headers = head
+ end
+
+ s << HEADERS
+
+ sid.should == 1
+
+ headers.class.should == Hash
+ headers['version'].should == "HTTP/1.1"
+ end
end
context "DATA" do
@@ -110,7 +164,7 @@
context "FIN" do
it "should invoke message_complete on FIN flag in CONTROL packet" do
f1, f2 = false
- s.on_headers_complete { f1 = true }
+ s.on_open { f1 = true }
s.on_message_complete { |s| f2 = s }
sr = SPDY::Protocol::Control::SynStream.new
Something went wrong with that request. Please try again.