indexzero
2/9/2011 - 10:54 AM

Add-some-mutability-to-headers-in-node-for-easy-midd.patch

From 5c5748d3f99f3590b4c149f5758eac970d51682f Mon Sep 17 00:00:00 2001
From: indexzero <charlie.robbins@gmail.com>
Date: Wed, 9 Feb 2011 23:27:25 -0500
Subject: [PATCH] Add mutable, implicit headers for easy middleware

---
 lib/http.js                              |   41 ++++++++++++-
 test/simple/test-http-mutable-headers.js |   96 ++++++++++++++++++++++++++++++
 2 files changed, 136 insertions(+), 1 deletions(-)
 create mode 100644 test/simple/test-http-mutable-headers.js

diff --git a/lib/http.js b/lib/http.js
index 40ebb22..e8e4b57 100644
--- a/lib/http.js
+++ b/lib/http.js
@@ -303,6 +303,10 @@ function OutgoingMessage() {
   this._trailer = '';
 
   this.finished = false;
+
+  this.statusCode = 200;
+  this._headers = {};
+  this._headerNames = {};
 }
 util.inherits(OutgoingMessage, stream.Stream);
 
@@ -497,7 +501,7 @@ OutgoingMessage.prototype._storeHeader = function(firstLine, headers) {
 
 OutgoingMessage.prototype.write = function(chunk, encoding) {
   if (!this._header) {
-    throw new Error('You have to call writeHead() before write()');
+    this.writeHead(this.statusCode, this._renderHeaders());
   }
 
   if (!this._hasBody) {
@@ -682,6 +686,41 @@ ServerResponse.prototype.writeContinue = function() {
 };
 
 
+ServerResponse.prototype.setHeader = function(name, value) {
+  if (arguments.length < 2) { throw new Error("`name` and `value` are required for setHeader()."); }
+  if (this._header) { throw new Error("Can't set headers after they are sent to the client."); }
+  var key = name.toLowerCase();
+  this._headers[key] = value;
+  this._headerNames[key] = name;
+};
+
+ServerResponse.prototype.getHeader = function(name) {
+  if (arguments.length < 1) { throw new Error("`name` is required for getHeader()."); }
+  if (this._header) { throw new Error("Can't use mutable header APIs after sending headers to client."); }
+  var key = name.toLowerCase();
+  return this._headers[key];
+};
+ServerResponse.prototype.removeHeader = function(name) {
+  if (arguments.length < 1) { throw new Error("`name` is required for removeHeader()."); }
+  if (this._header) { throw new Error("Can't remove headers after they are sent to the client."); }
+  var key = name.toLowerCase();
+  delete this._headers[key];
+  delete this._headerNames[key];
+};
+
+// Private
+ServerResponse.prototype._renderHeaders = function() {
+  if (this._header) { throw new Error("Can't render headers after they are sent to the client."); }
+  var headers = {};
+  var keys = Object.keys(this._headers);
+  for (var i = 0, l = keys.length; i < l; i++) {
+    var key = keys[i];
+    headers[this._headerNames[key]] = this._headers[key];
+  }
+  return headers;
+};
+
+
 ServerResponse.prototype.writeHead = function(statusCode) {
   var reasonPhrase, headers, headerIndex;
 
diff --git a/test/simple/test-http-mutable-headers.js b/test/simple/test-http-mutable-headers.js
new file mode 100644
index 0000000..536c69d
--- /dev/null
+++ b/test/simple/test-http-mutable-headers.js
@@ -0,0 +1,96 @@
+var common = require('../common');
+var assert = require('assert');
+var http = require('http');
+
+// Simple test of Node's HTTP Client mutable headers
+// ServerResponse.prototype.setHeader(name, value)
+// ServerResponse.prototype.getHeader(name)
+// ServerResponse.prototype.removeHeader(name, value)
+// Private
+//   ServerResponse.protoype._renderHeaders()
+
+var test = 'headers', content = 'hello world';
+var cookies = [
+  'session_token=; path=/; expires=Sun, 15-Sep-2030 13:48:52 GMT',
+  'prefers_open_id=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT'
+];
+
+var s = http.createServer(function(req, res) {
+  switch (test) {
+    case 'headers':
+      assert.throws(function () { res.setHeader() });
+      assert.throws(function () { res.setHeader('someHeader') });
+      assert.throws(function () { res.getHeader() });
+      assert.throws(function () { res.removeHeader() });
+
+      res.setHeader('x-test-header', 'testing');
+      res.setHeader('X-TEST-HEADER2', 'testing');
+      res.setHeader('set-cookie', cookies);
+      res.setHeader('x-test-array-header', [1, 2, 3]);
+
+      var val1 = res.getHeader('x-test-header');
+      var val2 = res.getHeader('x-test-header2');
+      assert.equal(val1, 'testing');
+      assert.equal(val2, 'testing');
+
+      res.removeHeader('x-test-header2');
+      break;
+    case 'contentLength':
+      res.setHeader('content-length', content.length);
+      assert.equal(content.length, res.getHeader('Content-Length'));
+      break;
+    case 'transferEncoding':
+      res.setHeader('transfer-encoding', 'chunked');
+      assert.equal(res.getHeader('Transfer-Encoding'), 'chunked');
+      break;
+  }
+  
+  res.statusCode = 201;
+  res.end(content);
+});
+
+s.listen(common.PORT, function() {
+  function nextTest () {
+    if (test === 'end') {
+      return s.close();
+    }
+    
+    var r = http.createClient(common.PORT);
+    var request = r.request('GET', '/');
+    request.on('response', function(response) {
+      console.log('TEST: ' + test);
+      console.log('STATUS: ' + response.statusCode);
+      console.log('HEADERS: ');
+      console.dir(response.headers);
+
+      switch (test) {
+        case 'headers':
+          assert.equal(response.statusCode, 201);
+          assert.equal(response.headers['x-test-header'], 'testing');
+          assert.equal(response.headers['x-test-array-header'], [1,2,3].join(', '));
+          assert.deepEqual(cookies, response.headers['set-cookie']);
+          assert.equal(response.headers['x-test-header2'] !== undefined, false);
+          
+          // Make the next request
+          test = 'contentLength';
+          console.log('foobar');
+          nextTest();
+          break;
+        case 'contentLength':
+          assert.equal(response.headers['content-length'], content.length);
+          test = 'transferEncoding';
+          nextTest();
+          break;
+        case 'transferEncoding':
+          assert.equal(response.headers['transfer-encoding'], 'chunked');
+          test = 'end';
+          nextTest();
+          break;
+      }
+    });
+    request.end();
+  }
+  
+  // Start the tests
+  nextTest();
+});
-- 
1.7.1