Browse Source

Use flat object instead of array-of-arrays for HTTP headers.

E.G. { "Content-Length": 10, "Content-Type": "text/html" } instead of
[["Content-Length", 10], ["Content-Type", "text/html"]].
The main reason for this change is object-creation efficiency.

This still needs testing and some further changes (like when receiving
multiple header lines with the same field-name, they are concatenated with a
comma but some headers ("Content-Length") should not be concatenated ; the
new header line should replace the old value).

Various thoughts on this subject:
http://groups.google.com/group/nodejs/browse_thread/thread/9a67bb32706d9efc#
http://four.livejournal.com/979640.html
http://mail.gnome.org/archives/libsoup-list/2009-March/msg00015.html
v0.7.4-release
Ryan 16 years ago
parent
commit
316e2833f0
  1. 6
      benchmark/http_simple.js
  2. 8
      benchmark/static_http_server.js
  3. 75
      src/http.js
  4. 2
      test/mjsunit/disabled/test-http-stress.js
  5. 7
      test/mjsunit/test-http-client-race.js
  6. 2
      test/mjsunit/test-http-client-upload.js
  7. 4
      test/mjsunit/test-http-proxy.js
  8. 2
      test/mjsunit/test-http-server.js
  9. 11
      test/mjsunit/test-http.js
  10. 32
      website/api.txt
  11. 2
      website/index.html

6
benchmark/http_simple.js

@ -40,9 +40,9 @@ node.http.createServer(function (req, res) {
var content_length = body.length.toString(); var content_length = body.length.toString();
res.sendHeader( status res.sendHeader( status
, [ ["Content-Type", "text/plain"] , { "Content-Type": "text/plain"
, ["Content-Length", content_length] , "Content-Length": content_length
] }
); );
res.sendBody(body); res.sendBody(body);

8
benchmark/static_http_server.js

@ -12,10 +12,10 @@ for (var i = 0; i < bytes; i++) {
} }
var server = node.http.createServer(function (req, res) { var server = node.http.createServer(function (req, res) {
res.sendHeader(200, [ res.sendHeader(200, {
["Content-Type", "text/plain"], "Content-Type": "text/plain",
["Content-Length", body.length] "Content-Length": body.length
]); });
res.sendBody(body); res.sendBody(body);
res.finish(); res.finish();
}) })

75
src/http.js

@ -117,7 +117,7 @@ function IncomingMessage (connection) {
this.connection = connection; this.connection = connection;
this.httpVersion = null; this.httpVersion = null;
this.headers = []; this.headers = {};
// request (server) only // request (server) only
this.uri = ""; this.uri = "";
@ -142,6 +142,15 @@ IncomingMessage.prototype.resume = function () {
this.connection.readResume(); this.connection.readResume();
}; };
IncomingMessage.prototype._addHeaderLine = function (field, value) {
if (field in this.headers) {
// TODO Certain headers like 'Content-Type' should not be concatinated.
// See https://www.google.com/reader/view/?tab=my#overview-page
this.headers[field] += ", " + value;
} else {
this.headers[field] = value;
}
};
function OutgoingMessage () { function OutgoingMessage () {
node.EventEmitter.call(this); node.EventEmitter.call(this);
@ -162,22 +171,26 @@ OutgoingMessage.prototype.send = function (data, encoding) {
this.output.push(data); this.output.push(data);
}; };
OutgoingMessage.prototype.sendHeaderLines = function (first_line, header_lines) { OutgoingMessage.prototype.sendHeaderLines = function (first_line, headers) {
var sent_connection_header = false; var sent_connection_header = false;
var sent_content_length_header = false; var sent_content_length_header = false;
var sent_transfer_encoding_header = false; var sent_transfer_encoding_header = false;
header_lines = header_lines || [];
// first_line in the case of request is: "GET /index.html HTTP/1.1\r\n" // first_line in the case of request is: "GET /index.html HTTP/1.1\r\n"
// in the case of response it is: "HTTP/1.1 200 OK\r\n" // in the case of response it is: "HTTP/1.1 200 OK\r\n"
var header = first_line; var message_header = first_line;
var field, value;
for (var i = 0; i < header_lines.length; i++) { for (var i in headers) {
var field = header_lines[i][0]; if (headers instanceof Array) {
var value = header_lines[i][1]; field = headers[i][0];
value = headers[i][1];
} else {
if (!headers.hasOwnProperty(i)) continue;
field = i;
value = headers[i];
}
header += field + ": " + value + CRLF; message_header += field + ": " + value + CRLF;
if (connection_expression.exec(field)) { if (connection_expression.exec(field)) {
sent_connection_header = true; sent_connection_header = true;
@ -196,23 +209,23 @@ OutgoingMessage.prototype.sendHeaderLines = function (first_line, header_lines)
// keep-alive logic // keep-alive logic
if (sent_connection_header == false) { if (sent_connection_header == false) {
if (this.should_keep_alive) { if (this.should_keep_alive) {
header += "Connection: keep-alive\r\n"; message_header += "Connection: keep-alive\r\n";
} else { } else {
this.closeOnFinish = true; this.closeOnFinish = true;
header += "Connection: close\r\n"; message_header += "Connection: close\r\n";
} }
} }
if (sent_content_length_header == false && sent_transfer_encoding_header == false) { if (sent_content_length_header == false && sent_transfer_encoding_header == false) {
if (this.use_chunked_encoding_by_default) { if (this.use_chunked_encoding_by_default) {
header += "Transfer-Encoding: chunked\r\n"; message_header += "Transfer-Encoding: chunked\r\n";
this.chunked_encoding = true; this.chunked_encoding = true;
} }
} }
header += CRLF; message_header += CRLF;
this.send(header); this.send(message_header);
// wait until the first body chunk, or finish(), is sent to flush. // wait until the first body chunk, or finish(), is sent to flush.
}; };
@ -255,7 +268,7 @@ ServerResponse.prototype.sendHeader = function (statusCode, headers) {
}; };
function ClientRequest (method, uri, header_lines) { function ClientRequest (method, uri, headers) {
OutgoingMessage.call(this); OutgoingMessage.call(this);
this.should_keep_alive = false; this.should_keep_alive = false;
@ -266,7 +279,7 @@ function ClientRequest (method, uri, header_lines) {
} }
this.closeOnFinish = true; this.closeOnFinish = true;
this.sendHeaderLines(method + " " + uri + " HTTP/1.1\r\n", header_lines); this.sendHeaderLines(method + " " + uri + " HTTP/1.1\r\n", headers);
} }
node.inherits(ClientRequest, OutgoingMessage); node.inherits(ClientRequest, OutgoingMessage);
@ -282,7 +295,7 @@ function createIncomingMessageStream (connection, incoming_listener) {
stream.addListener("incoming", incoming_listener); stream.addListener("incoming", incoming_listener);
var incoming; var incoming;
var last_header_was_a_value = false; var field = null, value = null;
connection.addListener("message_begin", function () { connection.addListener("message_begin", function () {
incoming = new IncomingMessage(connection); incoming = new IncomingMessage(connection);
@ -294,25 +307,31 @@ function createIncomingMessageStream (connection, incoming_listener) {
}); });
connection.addListener("header_field", function (data) { connection.addListener("header_field", function (data) {
if (incoming.headers.length > 0 && last_header_was_a_value == false) { if (value) {
incoming.headers[incoming.headers.length-1][0] += data; incoming._addHeaderLine(field, value);
field = null;
value = null;
}
if (field) {
field += data;
} else { } else {
incoming.headers.push([data]); field = data;
} }
last_header_was_a_value = false;
}); });
connection.addListener("header_value", function (data) { connection.addListener("header_value", function (data) {
var last_pair = incoming.headers[incoming.headers.length-1]; if (value) {
if (last_pair.length == 1) { value += data;
last_pair[1] = data; } else {
} else { value = data;
last_pair[1] += data;
} }
last_header_was_a_value = true;
}); });
connection.addListener("headers_complete", function (info) { connection.addListener("headers_complete", function (info) {
if (field && value) {
incoming._addHeaderLine(field, value);
}
incoming.httpVersion = info.httpVersion; incoming.httpVersion = info.httpVersion;
if (info.method) { if (info.method) {

2
test/mjsunit/disabled/test-http-stress.js

@ -5,7 +5,7 @@ var request_count = 1000;
var response_body = '{"ok": true}'; var response_body = '{"ok": true}';
var server = node.http.createServer(function(req, res) { var server = node.http.createServer(function(req, res) {
res.sendHeader(200, [['Content-Type', 'text/javascript']]); res.sendHeader(200, {'Content-Type': 'text/javascript'});
res.sendBody(response_body); res.sendBody(response_body);
res.finish(); res.finish();
}); });

7
test/mjsunit/test-http-client-race.js

@ -6,10 +6,9 @@ var body2_s = "22222";
var server = node.http.createServer(function (req, res) { var server = node.http.createServer(function (req, res) {
var body = req.uri.path === "/1" ? body1_s : body2_s; var body = req.uri.path === "/1" ? body1_s : body2_s;
res.sendHeader(200, [ res.sendHeader(200, { "Content-Type": "text/plain"
["Content-Type", "text/plain"], , "Content-Length": body.length
["Content-Length", body.length] });
]);
res.sendBody(body); res.sendBody(body);
res.finish(); res.finish();
}); });

2
test/mjsunit/test-http-client-upload.js

@ -17,7 +17,7 @@ var server = node.http.createServer(function(req, res) {
req.addListener("complete", function () { req.addListener("complete", function () {
server_req_complete = true; server_req_complete = true;
puts("request complete from server"); puts("request complete from server");
res.sendHeader(200, [['Content-Type', 'text/plain']]); res.sendHeader(200, {'Content-Type': 'text/plain'});
res.sendBody('hello\n'); res.sendBody('hello\n');
res.finish(); res.finish();
}); });

4
test/mjsunit/test-http-proxy.js

@ -5,7 +5,7 @@ var BACKEND_PORT = 8870;
var backend = node.http.createServer(function (req, res) { var backend = node.http.createServer(function (req, res) {
// node.debug("backend"); // node.debug("backend");
res.sendHeader(200, [["content-type", "text/plain"]]); res.sendHeader(200, {"content-type": "text/plain"});
res.sendBody("hello world\n"); res.sendBody("hello world\n");
res.finish(); res.finish();
}); });
@ -14,7 +14,7 @@ backend.listen(BACKEND_PORT);
var proxy_client = node.http.createClient(BACKEND_PORT); var proxy_client = node.http.createClient(BACKEND_PORT);
var proxy = node.http.createServer(function (req, res) { var proxy = node.http.createServer(function (req, res) {
// node.debug("proxy req"); node.debug("proxy req headers: " + JSON.stringify(req.headers));
var proxy_req = proxy_client.get(req.uri.path); var proxy_req = proxy_client.get(req.uri.path);
proxy_req.finish(function(proxy_res) { proxy_req.finish(function(proxy_res) {
res.sendHeader(proxy_res.statusCode, proxy_res.headers); res.sendHeader(proxy_res.statusCode, proxy_res.headers);

2
test/mjsunit/test-http-server.js

@ -26,7 +26,7 @@ function onLoad() {
} }
setTimeout(function () { setTimeout(function () {
res.sendHeader(200, [["Content-Type", "text/plain"]]); res.sendHeader(200, {"Content-Type": "text/plain"});
res.sendBody(req.uri.path); res.sendBody(req.uri.path);
res.finish(); res.finish();
}, 1); }, 1);

11
test/mjsunit/test-http.js

@ -11,6 +11,13 @@ function onLoad () {
if (responses_sent == 0) { if (responses_sent == 0) {
assertEquals("GET", req.method); assertEquals("GET", req.method);
assertEquals("/hello", req.uri.path); assertEquals("/hello", req.uri.path);
p(req.headers);
assertTrue("Accept" in req.headers);
assertEquals("*/*", req.headers["Accept"]);
assertTrue("Foo" in req.headers);
assertEquals("bar", req.headers["Foo"]);
} }
if (responses_sent == 1) { if (responses_sent == 1) {
@ -20,7 +27,7 @@ function onLoad () {
} }
req.addListener("complete", function () { req.addListener("complete", function () {
res.sendHeader(200, [["Content-Type", "text/plain"]]); res.sendHeader(200, {"Content-Type": "text/plain"});
res.sendBody("The path was " + req.uri.path); res.sendBody("The path was " + req.uri.path);
res.finish(); res.finish();
responses_sent += 1; responses_sent += 1;
@ -30,7 +37,7 @@ function onLoad () {
}).listen(PORT); }).listen(PORT);
var client = node.http.createClient(PORT); var client = node.http.createClient(PORT);
var req = client.get("/hello"); var req = client.get("/hello", {"Accept": "*/*", "Foo": "bar"});
req.finish(function (res) { req.finish(function (res) {
assertEquals(200, res.statusCode); assertEquals(200, res.statusCode);
responses_recvd += 1; responses_recvd += 1;

32
website/api.txt

@ -18,7 +18,7 @@ World" after waiting two seconds:
---------------------------------------- ----------------------------------------
node.http.createServer(function (request, response) { node.http.createServer(function (request, response) {
setTimeout(function () { setTimeout(function () {
response.sendHeader(200, [["Content-Type", "text/plain"]]); response.sendHeader(200, {"Content-Type": "text/plain"});
response.sendBody("Hello World"); response.sendBody("Hello World");
response.finish(); response.finish();
}, 2000); }, 2000);
@ -575,15 +575,14 @@ In particular, large, possibly chunk-encoded, messages. The interface is
careful to never buffer entire requests or responses--the careful to never buffer entire requests or responses--the
user is able to stream data. user is able to stream data.
HTTP message headers are represented by an array of 2-element HTTP message headers are represented by an object like this
arrays like this
---------------------------------------- ----------------------------------------
[ ["Content-Length", "123"] { "Content-Length": "123"
, ["Content-Type", "text/plain"] , "Content-Type": "text/plain"
, ["Connection", "keep-alive"] , "Connection": "keep-alive"
, ["Accept", "*/*"] , "Accept": "*/*"
] }
---------------------------------------- ----------------------------------------
In order to support the full spectrum of possible HTTP applications, Node's In order to support the full spectrum of possible HTTP applications, Node's
@ -693,7 +692,6 @@ in the actual HTTP Request.
+request.headers+ :: +request.headers+ ::
The request headers expressed as an array of 2-element arrays.
Read only. Read only.
@ -727,17 +725,16 @@ passed as the second parameter to the +"request"+ event.
+response.sendHeader(statusCode, headers)+ :: +response.sendHeader(statusCode, headers)+ ::
Sends a response header to the request. The status code is a 3-digit HTTP Sends a response header to the request. The status code is a 3-digit HTTP
status code, like +404+. The second argument, +headers+, should be an array status code, like +404+. The second argument, +headers+ are the response headers.
of 2-element arrays, representing the response headers.
+ +
Example: Example:
+ +
---------------------------------------- ----------------------------------------
var body = "hello world"; var body = "hello world";
response.sendHeader(200, [ response.sendHeader(200, {
["Content-Length", body.length], "Content-Length": body.length,
["Content-Type", "text/plain"] "Content-Type": "text/plain"
]); });
---------------------------------------- ----------------------------------------
+ +
This method must only be called once on a message and it must This method must only be called once on a message and it must
@ -799,8 +796,7 @@ connection is not established until a request is issued.
Issues a request; if necessary establishes connection. Returns a +node.http.ClientRequest+ instance. Issues a request; if necessary establishes connection. Returns a +node.http.ClientRequest+ instance.
+ +
+request_headers+ is optional. +request_headers+ is optional.
+request_headers+ should be an array of 2-element Additional request headers might be added internally
arrays. Additional request headers might be added internally
by Node. Returns a +ClientRequest+ object. by Node. Returns a +ClientRequest+ object.
+ +
Do remember to include the +Content-Length+ header if you Do remember to include the +Content-Length+ header if you
@ -894,7 +890,7 @@ After emitted no other events will be emitted on the response.
+"1.1"+ or +"1.0"+. +"1.1"+ or +"1.0"+.
+response.headers+ :: +response.headers+ ::
The response headers. An Array of 2-element arrays. The response headers.
+response.setBodyEncoding(encoding)+ :: +response.setBodyEncoding(encoding)+ ::
Set the encoding for the response body. Either +"utf8"+ or +"raw"+. Set the encoding for the response body. Either +"utf8"+ or +"raw"+.

2
website/index.html

@ -43,7 +43,7 @@
<pre> <pre>
node.http.createServer(function (req, res) { node.http.createServer(function (req, res) {
setTimeout(function () { setTimeout(function () {
res.sendHeader(200, [["Content-Type", "text/plain"]]); res.sendHeader(200, {"Content-Type": "text/plain"});
res.sendBody("Hello World"); res.sendBody("Hello World");
res.finish(); res.finish();
}, 2000); }, 2000);

Loading…
Cancel
Save