Up tonight, I would like to finish off my server push work on node-spdy. Specifically, I would like the implementation a little smaller and actually re-usable.
Currently, I have node-spdy sending a push stream after it sends a response:
Response.prototype._push_stream = function() {If the two guard clauses do not match, then the
if (this._pushed) return false;
// TODO don't hard code this
if (this.streamID != 1) return false;
this._push(this);
this._pushed = true;
};
_push()
callback is invoked. I am sending the Response
into the _push()
callback so that the callback has access to methods like:Response.prototype.push_file = function(filename, url) {(and
var push_stream = createPushStream(this.cframe, this.c, url);
push_stream.write(fs.readFileSync(filename));
push_stream.end();
};
push_file()
has access to the SPDY control frame, the connect object, etc)But now that I think about it, I am not convinced that I need the guard clause in the
_push_stream()
method—at least not in _pushStream()
proper. I may have added them during the spike. Removing them leaves me with:Response.prototype._push_stream = function() {I can probably get right of that indirection, assuming that I have not broken anything. The vows.js specs still pass without the guard clauses, but that is because the test is quite simple. To be sure, I also ensure that the
this._push(this);
};
test/basic-push-server.js
example server still works. It does (no RST_STREAMS).But...
There are now two push streams in the SPDY session (from the SPDY tab of Chrome's about:net-internals):
t=1307586375807 [st= 34] SPDY_SESSION_PUSHED_SYN_STREAMThe first push-stream was associated with the original SPDY request (stream ID #1). The second push-stream was associated with a different stream ID (3), which is the POST request for an AJAX resource. The whole purpose of push streams is to reduce the number of requests and responses so this is no good.
--> associated_stream = 1
--> flags = 2
--> status: 200
url: https://localhost:8082/style.css
version: http/1.1
--> id = 2
...
t=1307586375827 [st= 54] SPDY_STREAM_ADOPTED_PUSH_STREAM
#####
# And, shortly thereafter...
t=1307586375874 [st=101] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 3
--> flags = 2
--> status: 200
url: https://localhost:8082/style.css
version: http/1.1
--> id = 6
...
t=1307586375868 [st= 95] SPDY_STREAM_ADOPTED_PUSH_STREAM
Fortunately, a solution is now easily accomplished in the
push()
callback that is established when the server is started. I need to change it from:var options = {I add a guard clause to only push for the initial SPDY stream:
//...
push: function(pusher) {
pusher.push_file("pub/style.css", "https://localhost:8082/style.css");
pusher.push_file("pub/spdy.jpg", "https://localhost:8082/spdy.jpg");
}
};
var server = spdy.createServer(options, function(req, res) {
//...
});
var options = {With that, I am back to my nice server push of resources along with the homepage, followed by the AJAX POST and nothing else (no requests for images, CSS, etc.):
//...
push: function(pusher) {
if (pusher.streamID > 1) return;
pusher.push_file("pub/style.css", "https://localhost:8082/style.css");
pusher.push_file("pub/spdy.jpg", "https://localhost:8082/spdy.jpg");
}
};
t=1307589500211 [st= 69] SPDY_STREAM_ADOPTED_PUSH_STREAMThat marks the end of the SPDY session. Since the last stream ID is 3, I know that only two requests from the client were issued. Client stream IDs are odd in SPDY while server initiated streams are even. Thus, stream #1 replied with the homepage (and got some associated server push streams) and stream #3 replied to the AJAX request. Nice.
t=1307589500213 [st= 71] SPDY_SESSION_SEND_DATA
--> flags = 1
--> size = 18
--> stream_id = 3
t=1307589500216 [st= 74] SPDY_SESSION_SYN_REPLY
--> flags = 0
--> connection: keep-alive
status: 200 OK
version: HTTP/1.1
--> id = 3
t=1307589500251 [st=109] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 18
--> stream_id = 3
t=1307589500251 [st=109] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 3
For a basic implementation, I am satisfied with stopping there. Before stopping for the night, however, I would like to answer a question from last night's work. Namely, is it possible to reply with different push data depending on the resource being requested? That is, if a client requests the home page, can I send certain resources back via push stream, but if the client requests a members-only page, can I send back separate push data?
The answer is yes, but the route is a little indirect. The
Response
class, being properly encapsulated with low coupling, has no knowledge of the request:var Response = exports.Response = function(cframe, c) {It does have access to that
stream.Stream.call(this);
this.cframe = cframe;
this.streamID = cframe.data.streamID;
this.c = c;
this.statusCode = 200;
this._headers = {
'Connection': 'keep-alive'
};
this._written = false;
this._reasonPhrase = 'OK';
this._push = function() {};
// For stream.pipe and others
this.writable = true;
};
cframe
thingy. And it turns out that the cframe
thingy is the SPDY control frame of the original request. So I can dig down into that object in order to only push data when requesting the homepage with a GET request:push: function(pusher) {It would be nicer to have a solution that involved fewer dots, but I will leave that for another day. For now, I think I have a pretty decent SPDY server push implementation.
//if (pusher.streamID > 1) return;
var req = pusher.cframe.data.nameValues;
if (req.url != "/") return;
if (req.method != "GET") return;
pusher.push_file("pub/style.css", "https://localhost:8082/style.css");
pusher.push_file("pub/spdy.jpg", "https://localhost:8082/spdy.jpg");
}
Day #44
No comments:
Post a Comment