Tonight I continue to explore server push in SPDY.
First I define a very skeleton class for a push stream in node-spdy:
/**As indicated by the throw statements, I am an fairly certain that most of what was required for the
* Initiate SYN_STREAM
*/
Response.prototype.writeHead = function(code, reasonPhrase, headers) {
// TODO: I *think* this should only be built inside this class
throw new Error("PushStream#writeHead invoked externally");
};
/**
* Write data
*/
Response.prototype.write = function(data, encoding) {
return this._write(data, encoding, false);
};
/**
* Write any data (Internal)
*/
Response.prototype._write = function(data, encoding, fin) {
throw new Error("Please implement");
};
/**
* End stream
*/
Response.prototype.end = function(data, encoding) {
this.writable = false;
return this._write(data, encoding, true);
};
/**
* Mirroring node.js default API
*/
Response.prototype.setHeader = function(name, value) {
throw new Error("Not implemented for push stream");
};
/**
* Mirroring node.js default API
*/
Response.prototype.getHeader = function(name) {
throw new Error("Not implemented for push stream");
};
/**
* Cloning node.js default API
*/
Response.prototype.removeHeader = function(name) {
throw new Error("Not implemented for push stream");
};
Response
class will not be needed in PushStream
. Ideally just writing back the data.Then I start to noodle things through a bit and...
But wait a second, the
write
method in Response
is only sending out data:Response.prototype._write = function(data, encoding, fin) {A reply to a HTTP request coming in over a SYN_STREAM needs both a SYN_REPLY and a DATA response. The
if (!this._written) {
this._flushHead();
}
encoding = encoding || 'utf8';
if (data === undefined) {
data = new Buffer(0);
}
var dframe = createDataFrame(this.c.zlib, {
streamID: this.streamID,
flags: fin ? enums.DATA_FLAG_FIN : 0,
}, Buffer.isBuffer(data) ? data : new Buffer(data, encoding));
return this.c.write(dframe);
};
write()
method in Response
is only sending the DATA frame. What about the SYN_REPLY?Ah. I get it now. The
Response
class is responsible for sending out both the header and the data frame.So I create a private
_flushHead()
method in PushStream
(based on the method of the same name in Response
):/**Included here is the type, which is a SYN_STREAM. The
* Flush buffered head
*/
Response.prototype._flushHead = function() {
var headers = this._headers;
var cframe = createControlFrame(this.c.zlib, {
type: enums.SYN_STREAM,
flags: enums.FLAG_UNIDIRECTIONAL,
streamID: this.streamID,
assocStreamID: this.associatedStreamId
}, headers);
return this.c.write(cframe);
};
Response
class had been returning a SYN_REPLY
in response to an already open SYN_STREAM (e.g. one that requested the homepage). Here I am trying to initiate a server push, hence the new SYN_STREAM. Also in there is the unidirectional flags as required by SPDY server push (there will not be a back-and-forth here, just push from the server). The new stream ID and the associated ID are set in the constructor.(Actually, after double-checking in the enums.js file, I find that it is
CONTROL_FLAG_UNIDIRECTIONAL
)Another requirement of the server push spec is that the URL is set in the SYN_STREAM. Like the stream ID and associated stream ID, the headers are currently being assigned in the constructor. For this spike, I will explicitly set the URL to the stylesheet being used. If this works, the stylesheet will make its way into the browser's cache—ultimately preventing the browser from needing to request it. So, the header:
var PushStream = exports.PushStream = function(cframe, c) {I think this is starting to come together. Next up, is sending the actual stylesheet data. For that, I copy the
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 = {
url: "https://localhost:8081/style.css"
};
};
write
method directly from Response
:/**This method writes the just crafted header (unless it has already done so). Then it builds the DATA frame and writes it back to the connect stream.
* Write any data (Internal)
*/
Response.prototype._write = function(data, encoding, fin) {
if (!this._written) {
this._flushHead();
}
encoding = encoding || 'utf8';
if (data === undefined) {
data = new Buffer(0);
}
var dframe = createDataFrame(this.c.zlib, {
streamID: this.streamID,
flags: fin ? enums.DATA_FLAG_FIN : 0,
}, Buffer.isBuffer(data) ? data : new Buffer(data, encoding));
return this.c.write(dframe);
};
Last up tonight it doing a little work back in the
Response
class. The server push will be initiated back in the Response class because server push needs an associated stream ID in order to be valid. With that in mind, I define the previously stubbed Reponse#createPushStream
method:/**The control frame (from the original browser request) will be sufficient to determine the associated stream ID and the connect stream will provide the transport for the server push.
* Server push
*/
Response.prototype.createPushStream = function() {
return new PushStream(this.cframe, this.c)
};
Tomorrow I will add the actual data and (without a doubt) perform quite a bit of debugging because I have really played fast and loose with this code tonight as I simple tried to wrap my brain around it. Even so, I am pleased with the progress tonight. I feel as though I am close to having a workable server push in SPDY.
Day #31
No comments:
Post a Comment