I continue to struggle with implementing version 3 of the SPDY specification in node-spdy. I was finally able to get a response from inbound spdy/3 requests, but Chrome does not like the format of the response.
In fact, I have gotten to the point that Chrome actually recognizes that I am trying to speak spdy/3:
But, when I look at what Chrome thinks of the conversation, I see:
SPDY_SESSION_SEND_RST_STREAM --> description = "Could not parse Spdy Control Frame Header." --> status = 1 --> stream_id = 1I have gone through the actual construction of the head a couple of times by hand and it looks OK. Of course, it's all writing bytes and lengths and things like that so it would be easy to make a mistake. Then there is the the Zlib compression...
I know that I have the Zlib inflation working because I can parse the inbound SPDY headers from Chrome. So a logical next step would be to attempt to parse the SPDY headers that I am generating. But before that, could it be as simple as missing those colon headers?
When I first parsed the inbound headers, I noticed some headers that started with colons:
headers: { ':host': 'localhost:3000', ':method': 'GET', ':path': '/', ':scheme': 'https', ':version': 'HTTP/1.1', // ... }I mistakenly thought something had gone wrong, but it turns out that they are part of the spec. And there are also colon headers that I am not including outbound:
:status
and :version
. So I convert those headers over to be leading colon: // ...
var dict = headersToDict(headers, function(headers) {
headers[':status'] = code;
headers[':version'] = 'HTTP/1.1';
});
this._synFrame('SYN_REPLY', id, null, dict, callback);
But unfortunately, Chrome still refuses to accept the headers.So, it seems that I have to run through the internal parser after all. And it turns out that something is in fact not quite right with the header.
var spdy = require('spdy'); var inflate = spdy.utils.zwrap(spdy.utils.createInflate()); var b = new Buffer([0x38, 0xac, 0xe3, 0xc6, 0xa7, 0xc2, 0x02, 0xe5, 0x0e, 0x50, 0x7a, 0xb4, 0x82, 0x27, 0x52, 0x66, 0x60, 0x22, 0x05, 0x25, 0x4b, 0x2b, 0xa4, 0x24, 0x0a, 0x00, 0x00, 0x00, 0xff, 0xff]); inflate(b, function(err, chunks, length) { console.log(chunks); }This ends up logging what looks like the correct opening series of bytes, but not the correct ending.
Sigh.... it seems that I will be assembling headers manually tomorrow.
Update: I figured it out! It was a simple matter of not including the correct number of octets / bytes for the headers. When I converted from spdy/2 to spdy/3, I accounted for the increase from 16 bit to 32 bit header length values everywhere except when totalling the length. All that I needed was an 8 (4 octets for the keys and 4 for the values) every time that I added a new key value pair to the header:
// ...
var len = 4,
pairs = Object.keys(loweredHeaders).filter(function(key) {
var lkey = key.toLowerCase();
return lkey !== 'connection' && lkey !== 'keep-alive' &&
lkey !== 'proxy-connection' && lkey !== 'transfer-encoding';
}).map(function(key) {
var klen = Buffer.byteLength(key),
value = stringify(loweredHeaders[key]),
vlen = Buffer.byteLength(value);
len += 8 + klen + vlen;
return [klen, key, vlen, value];
}),
result = new Buffer(len);
I cannot believe that I caused myself two nights of pain for an 4 when I meant 8. Binary protocols: catch the fever!At any rate, I finally have a spdy/3 express site:
Tomorrow comes the great cleanup. I have a ton of debug statements in here.
Day #367
No comments:
Post a Comment