Browse Source

Support for outgoing HTTP trailing headers

v0.7.4-release
Mark Nottingham 14 years ago
committed by Ryan Dahl
parent
commit
4fe3007a1a
  1. 18
      doc/api.markdown
  2. 27
      lib/http.js
  3. 75
      test/simple/test-http-set-trailers.js

18
doc/api.markdown

@ -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
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])

27
lib/http.js

@ -292,6 +292,7 @@ function OutgoingMessage (socket) {
this.useChunkedEncodingByDefault = true;
this._hasBody = true;
this._trailer = '';
this.finished = false;
}
@ -484,6 +485,26 @@ OutgoingMessage.prototype.write = function (chunk, encoding) {
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 () {
throw new Error("finish() has been renamed to close().");
};
@ -520,7 +541,9 @@ OutgoingMessage.prototype.end = function (data, encoding) {
+ l
+ CRLF
+ data
+ "\r\n0\r\n\r\n"
+ "\r\n0\r\n"
+ this._trailer
+ "\r\n"
, encoding
);
} else {
@ -535,7 +558,7 @@ OutgoingMessage.prototype.end = function (data, encoding) {
if (!hot) {
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) {
// Force a flush, HACK.
ret = this._send('');

75
test/simple/test-http-set-trailers.js

@ -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();
}
}
});
});
Loading…
Cancel
Save