Friday, June 3, 2011

SPDY Server Push

‹prev | My Chain | next›

Thanks to an assist from Mike Belshe (OK not so much an assist, more he solved it completely), I now have server push working in node-spdy.

This is just a spike at this point and not yet general purpose. When the client connects to the node-spdy server, I am trying to server push a dumb version of the CSS stylesheet. As Mike pointed out, I was missing version and status in the push stream's headers.

I had experimented with a number of headers. Per the spec:
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.
I completely overlooked the part of the spec describing responses (push or otherwise):
The response status line is unfolded into name/value pairs like other HTTP headers and must be present:
":status" - The HTTP response status code (e.g. "200" or "200 OK")
":version" - The HTTP response version (e.g. "HTTP/1.1")
So I go ahead and add this to my dumb, push version of the /style.css stylesheet in the node-spdy sample app:
var PushStream = exports.PushStream = function(cframe, c) {
stream.Stream.call(this);
this.streamID = 2; // TODO auto-increment even numbers per: http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2#TOC-Stream-creation

this.associatedStreamId = cframe.data.streamID;
this.c = c;

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"
};
};
Now, when I load up the sample node-spdy app, I see:



That is definitely the dumb version of the /style.css stylesheet in effect:
  push_stream.write(
"h1 { color: orange }"
);
Taking a look at this in the SPDY tab of about:net-internals, I see that my push stream is adopted by Chrome:

t=1307160566672 [st= 0] +SPDY_SESSION  [dt=?]
--> host = "localhost:8081"
--> proxy = "DIRECT"
t=1307160566672 [st= 0] SPDY_SESSION_RECV_SETTINGS
--> settings = ["[0:100]"]
t=1307160566672 [st= 0] 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
t=1307160566677 [st= 5] 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
t=1307160566711 [st=39] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> cache-control: public, max-age=0
content-length: 20
content-type: text/css; charset=UTF-8
etag: 347-1306200491000
last-modified: Tue, 24 May 2011 01:28:11 GMT
status: 200
url: https://localhost:8081/style.css
version: http/1.1
--> id = 2
t=1307160566711 [st=39] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 20
--> stream_id = 2
...
The new status and version are in place and seem to be making the difference.

After receiving the pushed CSS as well as the HTML for the page itself, Chrome notes that yes, it really is a legit push stream:
t=1307160566714 [st=42]     SPDY_STREAM_ADOPTED_PUSH_STREAM  
That is not new. I had node-spdy that far days ago. What is new is that, since I have all of the necessary headers in place, Chrome does not block waiting on a HEADERS SPDY frame. Instead, it makes the next request (an AJAX POST):
t=1307160566721 [st=49]     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
After sending the headers for that request, Chrome sends the POST data itself:
t=1307160566723 [st=51]     SPDY_SESSION_SEND_DATA  
--> flags = 1
--> size = 18
--> stream_id = 3
The server replies with header and data:
t=1307160566725 [st=53]     SPDY_SESSION_SYN_REPLY  
--> flags = 0
--> connection: keep-alive
status: 200 OK
version: HTTP/1.1
--> id = 3
t=1307160566761 [st=89] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 18
--> stream_id = 3
t=1307160566761 [st=89] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 3
And that is it.

The most important aspect of this SPDY session is that the browser never requests the CSS stylesheet. The page itself definitely includes the CSS:
<!DOCTYPE html> 
<html>
<head>
<link rel=icon type=image/png href="favicon.png" />
<link rel=stylesheet type=text/css href="style.css" />

<title>SPDY Server</title>
...
But since the server performed a SPDY server push, the client recognizes that the resource in question has already been pushed into cache, making a request unnecessary.

And, if I shift-reload the page, I by-pass cache and download the real stylesheet for the node-spdy sample app:




Yay! SPDY server push is finally mine!


Day #39

No comments:

Post a Comment