Sunday, June 5, 2011

Pushing Multiple Items with SPDY

‹prev | My Chain | next›

With my spike on SPDY server push finally finding some measure of success, I am almost ready to move on to some real code. But first, I would like to try pushing out more than one resource.

In my spike, I have been pushing out a dummy stylesheet. The easiest way that I can think to push out two resources (and verify that it is working) is to push out the real stylesheet, which includes:
section#main {
//...
background: url(spdy.jpg) no-repeat 50% 50%;
//...
}
If I can push out multiple resources with the page request, then I should be able to push out everything needed to render the node-spdy sample page with only a single request from the browser.

First up, I do a little clean-up. I will need to set multiple push streams, so I cannot hard code information in the push stream class. As noted yesterday, the only information that I believe truly changes between push streams is the URL, so I add URL to the push stream constructor:
exports.createPushStream = function(cframe, c, url) {
return new PushStream(cframe, c, url);
};
With that, I can rewrite the named-in-a-spike pushStuff() method to:
Response.prototype.pushStuff = function() {
if (this._pushed) return false;
if (this.streamID != 1) return false;

// TODO: Combine FIN & write? (once push stream is working)

var data = fs.readFileSync('pub/style.css', 'utf8');

var push_stream = createPushStream(this.cframe, this.c, "https://localhost:8081/style.css");

push_stream.write(
// "h1 { color: orange }"
data
);
push_stream.end();

this._pushed = true;
};
I have also commented out the dummy data for the stylesheet. Instead I am now reading the real one from the filesystem (blocking, I will worry about async another day).

After verifying that everything still works, I factor the actual push out into a separate method:
Response.prototype.pushStuff = function() {
if (this._pushed) return false;
if (this.streamID != 1) return false;

this._push("pub/style.css", "https://localhost:8081/style.css");

this._pushed = true;
};

Response.prototype._push = function(filename, url) {
var push_stream = createPushStream(this.cframe, this.c, url);

push_stream.write(fs.readFileSync(filename, 'utf8'));

// TODO: Combine FIN & write? (once push stream is working)
push_stream.end();
};
That will allow me to add a second push, this time for the background JPG image:
Response.prototype.pushStuff = function() {
if (this._pushed) return false;
if (this.streamID != 1) return false;

this._push("pub/style.css", "https://localhost:8081/style.css");
this._push("pub/spdy.jpg", "https://localhost:8081/spdy.jpg");

this._pushed = true;
};
Now, when I load the page and check things out in Chrome's about:net-internals SPDY tab, I find (my notes inline):
t=1307330730921 [st=     0] +SPDY_SESSION  [dt=?]
--> host = "localhost:8081"
--> proxy = "DIRECT"
t=1307330730922 [st= 1] SPDY_SESSION_RECV_SETTINGS
--> settings = ["[0:100]"]


#####
# The HTTP Request send over SPDY
t=1307330730922 [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

#####
# HTTP Response headers being delivered over SPDY
t=1307330730929 [st= 8] 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

#####
# Headers for the CSS being pushed
t=1307330730954 [st= 33] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> status: 200
url: https://localhost:8081/style.css
version: http/1.1
--> id = 2

#####
# Data for the CSS
t=1307330730955 [st= 34] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 347
--> stream_id = 2


#####
# Data FIN for the CSS
t=1307330730955 [st= 34] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 2


#####
# Headers for the JPEG image being pushed
t=1307330730955 [st= 34] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> status: 200
url: https://localhost:8081/spdy.jpg
version: http/1.1
--> id = 4
#####
# Data for the JPEG (many other packets omitted)
t=1307330730957 [st= 36] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 8184
--> stream_id = 4

#####
# Data FIN for the JPEG
t=1307330730966 [st= 45] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 4

#####
# Data for the web page itself
t=1307330730966 [st= 45] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 698
--> stream_id = 1

#####
# Data FIN for the web page itself
t=1307330730966 [st= 45] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 1

#####
# Successfully recognized push stream - yay!
t=1307330730975 [st= 54] SPDY_STREAM_ADOPTED_PUSH_STREAM
Cool! That looks to have worked.

Unfortunately, the webpage begs to differ:


(no JPEG background)

Since the SPDY tab seems quite satisfied, I guess that I am doing something silly and (hopefully not too) subtle. I play with the order of the CSS and the JPEG image, to not avail. Next I try reading the data from the filesystem directly into a Buffer rather than reading it into a string. This is easily accomplished by omitting the 'utf8' from the calls to fs.readFileSync():
Response.prototype._push = function(filename, url) {
var push_stream = createPushStream(this.cframe, this.c, url);

push_stream.write(fs.readFileSync(filename));

// TODO: Combine FIN & write? (once push stream is working)
push_stream.end();
};
That actually makes a certain amount of sense as an explanation for my trouble. Encoding a JPEG as a UTF-8 string may not produce the desired effects. In fact, that turns out to be the explanation:



Nice!

I think tomorrow I will dump my spike and begin coding this push stuff up for real.


Day #42

No comments:

Post a Comment