Support for outgoing HTTP trailing headers

This commit is contained in:
Mark Nottingham 2010-09-09 16:29:35 +10:00 committed by Ryan Dahl
parent 83ff473d30
commit 4fe3007a1a
3 changed files with 118 additions and 2 deletions

View File

@ -1894,6 +1894,24 @@ header information and the first body to the client. The second time
data, and sends that separately. That is, the response is buffered up to the data, and sends that separately. That is, the response is buffered up to the
first chunk of body. first chunk of body.
### response.addTrailers(headers)
This method adds HTTP trailing headers (a header but at the end of the
message) to the response.
Trailers will **only** be emitted if chunked encoding is used for the
response; if it is not (e.g., if the request was HTTP/1.0), they will
be silently discarded.
Note that HTTP requires the `Trailer` header to be sent if you intend to
emit trailers, with a list of the header fields in its value. E.g.,
response.writeHead(200, { 'Content-Type': 'text/plain',
'Trailer': 'TraceInfo' });
response.write(fileData);
response.addTrailers({'Content-MD5': "7895bf4b8828b55ceaf47747b4bca667"});
response.end();
### response.end([data], [encoding]) ### response.end([data], [encoding])

View File

@ -292,6 +292,7 @@ function OutgoingMessage (socket) {
this.useChunkedEncodingByDefault = true; this.useChunkedEncodingByDefault = true;
this._hasBody = true; this._hasBody = true;
this._trailer = '';
this.finished = false; this.finished = false;
} }
@ -484,6 +485,26 @@ OutgoingMessage.prototype.write = function (chunk, encoding) {
return ret; return ret;
}; };
OutgoingMessage.prototype.addTrailers = function (headers) {
this._trailer = "";
var keys = Object.keys(headers);
var isArray = (Array.isArray(headers));
for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i];
if (isArray) {
field = headers[key][0];
value = headers[key][1];
} else {
field = key;
value = headers[key];
}
this._trailer += field + ": " + value + CRLF;
}
};
OutgoingMessage.prototype.finish = function () { OutgoingMessage.prototype.finish = function () {
throw new Error("finish() has been renamed to close()."); throw new Error("finish() has been renamed to close().");
}; };
@ -520,7 +541,9 @@ OutgoingMessage.prototype.end = function (data, encoding) {
+ l + l
+ CRLF + CRLF
+ data + data
+ "\r\n0\r\n\r\n" + "\r\n0\r\n"
+ this._trailer
+ "\r\n"
, encoding , encoding
); );
} else { } else {
@ -535,7 +558,7 @@ OutgoingMessage.prototype.end = function (data, encoding) {
if (!hot) { if (!hot) {
if (this.chunkedEncoding) { if (this.chunkedEncoding) {
ret = this._send('0\r\n\r\n'); // Last chunk. ret = this._send('0\r\n' + this._trailer + '\r\n'); // Last chunk.
} else if (!data) { } else if (!data) {
// Force a flush, HACK. // Force a flush, HACK.
ret = this._send(''); ret = this._send('');

View File

@ -0,0 +1,75 @@
common = require("../common");
assert = common.assert;
http = require("http");
net = require("net");
outstanding_reqs = 0;
var server = http.createServer(function(req, res) {
res.writeHead(200, [ ['content-type', 'text/plain'] ]);
res.addTrailers({"x-foo": "bar"});
res.end("stuff" + "\n");
});
server.listen(common.PORT);
// first, we test an HTTP/1.0 request.
server.addListener("listening", function() {
var c = net.createConnection(common.PORT);
var res_buffer = "";
c.setEncoding("utf8");
c.addListener("connect", function () {
outstanding_reqs++;
c.write( "GET / HTTP/1.0\r\n\r\n" );
});
c.addListener("data", function (chunk) {
// console.log(chunk);
res_buffer += chunk;
});
c.addListener("end", function () {
c.end();
assert.ok(! /x-foo/.test(res_buffer), "Trailer in HTTP/1.0 response.");
outstanding_reqs--;
if (outstanding_reqs == 0) {
server.close();
process.exit();
}
});
});
// now, we test an HTTP/1.1 request.
server.addListener("listening", function() {
var c = net.createConnection(common.PORT);
var res_buffer = "";
var tid;
c.setEncoding("utf8");
c.addListener("connect", function () {
outstanding_reqs++;
c.write( "GET / HTTP/1.1\r\n\r\n" );
tid = setTimeout(assert.fail, 2000, "Couldn't find last chunk.");
});
c.addListener("data", function (chunk) {
// console.log(chunk);
res_buffer += chunk;
if (/0\r\n/.test(res_buffer)) { // got the end.
outstanding_reqs--;
clearTimeout(tid);
assert.ok(
/0\r\nx-foo: bar\r\n\r\n$/.test(res_buffer),
"No trailer in HTTP/1.1 response."
);
if (outstanding_reqs == 0) {
server.close();
process.exit();
}
}
});
});