Send to Kindle

Saturday, June 4, 2011

Minimal Headers Needed for SPDY Server Push

‹prev | My Chain | next›

I finally got server push in SPDY working last night. Today, I hope to get it working well.

I ended up with a bunch of headers in my server push fork of node-spdy. Most were in there as vain attempts to get server push working:
   this._headers = {
"cache-control": "public, max-age=0",
url: "https://localhost:8081/style.css",
"status": 200,
"version": "http/1.1",
"content-length": 20,
"content-type": "text/css; charset=UTF-8",
etag: "347-1306200491000",
"last-modified": "Tue, 24 May 2011 01:28:11 GMT"
};
I know that status and version are required because those are the ones that finally got server push working. But what about the others?

First up, I remove everything aside from url, status and version:
   this._headers = {
url: "https://localhost:8081/style.css",
"status": 200,
"version": "http/1.1"
};
Accessing the site and checking out the SPDY tab of Chrome's about:net-internals reveals that everything is still working (my notes inline):
#####
# The client request for the web page:
t=1307239273224 [st= 1] SPDY_SESSION_SYN_STREAM
--> flags = 1
--> accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
accept-encoding: gzip,deflate,sdch
accept-language: en-US,en;q=0.8
host: localhost:8081
method: GET
scheme: https
url: /
user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.1 Safari/535.1
version: HTTP/1.1
--> id = 1

#####
# Settings, currently improperly implemented in Chrome:
t=1307239273232 [st= 9] SPDY_SESSION_RECV_SETTINGS
--> settings = ["[0:100]"]

#####
# Replying with the home page headers:
t=1307239273264 [st=41] SPDY_SESSION_SYN_REPLY
--> flags = 0
--> accept-ranges: bytes
cache-control: public, max-age=0
connection: keep-alive
content-length: 698
content-type: text/html; charset=UTF-8
etag: "698-1306200491000"
last-modified: Tue, 24 May 2011 01:28:11 GMT
status: 200 OK
version: HTTP/1.1
--> id = 1

#####
# The push stream
t=1307239273264 [st=41] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> status: 200
url: https://localhost:8081/style.css
version: http/1.1
--> id = 2

#####
# Push stream data
t=1307239273264 [st=41] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 20
--> stream_id = 2

#####
# FIN data omitted

#####
# Home page data
t=1307239273265 [st=42] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 698
--> stream_id = 1

#####
# It's a legit push stream!
t=1307239273274 [st=51] SPDY_STREAM_ADOPTED_PUSH_STREAM

#####
# A second request from the browser (POSTing AJAX)
t=1307239273313 [st=90] SPDY_SESSION_SYN_STREAM
--> flags = 0
--> accept: */*
accept-charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
accept-encoding: gzip,deflate,sdch
accept-language: en-US,en;q=0.8
content-length: 18
content-type: application/xml
host: localhost:8081
method: POST
origin: https://localhost:8081
referer: https://localhost:8081/
scheme: https
url: /
user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.1 Safari/535.1
version: HTTP/1.1
--> id = 3
OK. So the minimally required values set for SPDY server push to work with Chrome are:
  • status—this is an HTTP status. Why this would be anything other than 200 in a push stream is beyond me. Regardless, it is required.
  • version—the version of HTTP over SPDY being used. Why this would be anything other than HTTP/1.1 is beyond me. Regardless, it is required.
  • url—the fully qualified URL of the resource being pushed.
What strikes me as strange about this is that draft 3 of the SPDY spec seems to indicate that host, scheme, etc. are required as well:
The pushed stream inherits all of the headers from the associated-stream-id with the exception of ":host", ":scheme", and ":path", which are provided as part of the pushed response stream headers.
Clearly one can derive this information from the fully qualified URL. But does it work to replace the URL with these attributes?
  this._headers = {
"status": 200,
"version": "http/1.1",
//url: "https://localhost:8081/style.css"
method: "GET",
scheme: "https",
host: "localhost:8081",
path: "/style.css"
};
The answer, it turns out, is "no":
t=1307245599194 [st= 40]     SPDY_SESSION_PUSHED_SYN_STREAM  
--> associated_stream = 1
--> flags = 2
--> host: localhost:8081
method: GET
path: /style.css
scheme: https
status: 200
version: http/1.1
--> id = 2
t=1307245599195 [st= 41] SPDY_SESSION_SEND_RST_STREAM
--> status = 1
--> stream_id = 2
The scheme, host, and path for which the spec calls definitely do not work with Chrome, which replies immediately with a RST_STREAM. More specifically, a PROTOCOL_ERROR:
1 - PROTOCOL_ERROR. This is a generic error, and should only be used if a more specific error is not available.
Interesting.

The only other thing that I would like to verify is that I cannot get away with a relative URL. It is a bit of a hassle to include the domain name in the URL. In real life, this might need to work in development, staging and production environments. Sure, I can grab the scheme and host from the associated stream, but if I do not have to...

Unfortunately, I do have to:
t=1307208760541 [st=     39]     SPDY_SESSION_PUSHED_SYN_STREAM  
--> associated_stream = 1
--> flags = 2
--> host: localhost:8081
method: GET
scheme: https
status: 200
url: /style.css
version: http/1.1
--> id = 2
t=1307208760541 [st= 39] SPDY_SESSION_SEND_RST_STREAM
--> status = 1
--> stream_id = 2
...
t=1307208760551 [st= 49] SPDY_SESSION_SYN_STREAM
--> flags = 1
--> accept: text/css,*/*;q=0.1
accept-charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
accept-encoding: gzip,deflate,sdch
accept-language: en-US,en;q=0.8
host: localhost:8081
method: GET
referer: https://localhost:8081/
scheme: https
url: /style.css
user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.1 Safari/535.1
version: HTTP/1.1
--> id = 3
Ah well. I now have the minimal set of headers required to make SPDY server push work with Chrome. That is a fine stopping point for today.

Day #41

No comments:

Post a Comment