summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Nottingham <mnot@mnot.net>2010-09-09 16:29:35 +1000
committerRyan Dahl <ry@tinyclouds.org>2010-09-14 15:20:45 -0700
commit4fe3007a1ae4c35e18d297af3151eb51d0458859 (patch)
tree24447426383f8759d8d3b6461141829262ccbae6
parent83ff473d30c45baa2c975793a7ffee06f19e0b3b (diff)
downloadnodejs-4fe3007a1ae4c35e18d297af3151eb51d0458859.tar.gz
nodejs-4fe3007a1ae4c35e18d297af3151eb51d0458859.tar.bz2
nodejs-4fe3007a1ae4c35e18d297af3151eb51d0458859.zip
Support for outgoing HTTP trailing headers
-rw-r--r--doc/api.markdown18
-rw-r--r--lib/http.js27
-rw-r--r--test/simple/test-http-set-trailers.js75
3 files changed, 118 insertions, 2 deletions
diff --git a/doc/api.markdown b/doc/api.markdown
index 0b0ecee9f..e5109c296 100644
--- a/doc/api.markdown
+++ b/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])
diff --git a/lib/http.js b/lib/http.js
index ea0ce2afc..3f9d8fcad 100644
--- a/lib/http.js
+++ b/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('');
diff --git a/test/simple/test-http-set-trailers.js b/test/simple/test-http-set-trailers.js
new file mode 100644
index 000000000..6b6a34c8d
--- /dev/null
+++ b/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();
+ }
+ }
+ });
+
+});