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. 77
      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();
res.sendHeader( status
, [ ["Content-Type", "text/plain"]
, ["Content-Length", content_length]
]
, { "Content-Type": "text/plain"
, "Content-Length": content_length
}
);
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) {
res.sendHeader(200, [
["Content-Type", "text/plain"],
["Content-Length", body.length]
]);
res.sendHeader(200, {
"Content-Type": "text/plain",
"Content-Length": body.length
});
res.sendBody(body);
res.finish();
})

77
src/http.js

@ -117,7 +117,7 @@ function IncomingMessage (connection) {
this.connection = connection;
this.httpVersion = null;
this.headers = [];
this.headers = {};
// request (server) only
this.uri = "";
@ -142,6 +142,15 @@ IncomingMessage.prototype.resume = function () {
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 () {
node.EventEmitter.call(this);
@ -162,22 +171,26 @@ OutgoingMessage.prototype.send = function (data, encoding) {
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_content_length_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"
// in the case of response it is: "HTTP/1.1 200 OK\r\n"
var header = first_line;
for (var i = 0; i < header_lines.length; i++) {
var field = header_lines[i][0];
var value = header_lines[i][1];
var message_header = first_line;
var field, value;
for (var i in headers) {
if (headers instanceof Array) {
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)) {
sent_connection_header = true;
@ -196,23 +209,23 @@ OutgoingMessage.prototype.sendHeaderLines = function (first_line, header_lines)
// keep-alive logic
if (sent_connection_header == false) {
if (this.should_keep_alive) {
header += "Connection: keep-alive\r\n";
message_header += "Connection: keep-alive\r\n";
} else {
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 (this.use_chunked_encoding_by_default) {
header += "Transfer-Encoding: chunked\r\n";
message_header += "Transfer-Encoding: chunked\r\n";
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.
};
@ -255,7 +268,7 @@ ServerResponse.prototype.sendHeader = function (statusCode, headers) {
};
function ClientRequest (method, uri, header_lines) {
function ClientRequest (method, uri, headers) {
OutgoingMessage.call(this);
this.should_keep_alive = false;
@ -266,7 +279,7 @@ function ClientRequest (method, uri, header_lines) {
}
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);
@ -282,7 +295,7 @@ function createIncomingMessageStream (connection, incoming_listener) {
stream.addListener("incoming", incoming_listener);
var incoming;
var last_header_was_a_value = false;
var field = null, value = null;
connection.addListener("message_begin", function () {
incoming = new IncomingMessage(connection);
@ -294,25 +307,31 @@ function createIncomingMessageStream (connection, incoming_listener) {
});
connection.addListener("header_field", function (data) {
if (incoming.headers.length > 0 && last_header_was_a_value == false) {
incoming.headers[incoming.headers.length-1][0] += data;
if (value) {
incoming._addHeaderLine(field, value);
field = null;
value = null;
}
if (field) {
field += data;
} else {
incoming.headers.push([data]);
field = data;
}
last_header_was_a_value = false;
});
connection.addListener("header_value", function (data) {
var last_pair = incoming.headers[incoming.headers.length-1];
if (last_pair.length == 1) {
last_pair[1] = data;
} else {
last_pair[1] += data;
}
last_header_was_a_value = true;
if (value) {
value += data;
} else {
value = data;
}
});
connection.addListener("headers_complete", function (info) {
if (field && value) {
incoming._addHeaderLine(field, value);
}
incoming.httpVersion = info.httpVersion;
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 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.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 body = req.uri.path === "/1" ? body1_s : body2_s;
res.sendHeader(200, [
["Content-Type", "text/plain"],
["Content-Length", body.length]
]);
res.sendHeader(200, { "Content-Type": "text/plain"
, "Content-Length": body.length
});
res.sendBody(body);
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 () {
server_req_complete = true;
puts("request complete from server");
res.sendHeader(200, [['Content-Type', 'text/plain']]);
res.sendHeader(200, {'Content-Type': 'text/plain'});
res.sendBody('hello\n');
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) {
// node.debug("backend");
res.sendHeader(200, [["content-type", "text/plain"]]);
res.sendHeader(200, {"content-type": "text/plain"});
res.sendBody("hello world\n");
res.finish();
});
@ -14,7 +14,7 @@ backend.listen(BACKEND_PORT);
var proxy_client = node.http.createClient(BACKEND_PORT);
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);
proxy_req.finish(function(proxy_res) {
res.sendHeader(proxy_res.statusCode, proxy_res.headers);

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

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

11
test/mjsunit/test-http.js

@ -11,6 +11,13 @@ function onLoad () {
if (responses_sent == 0) {
assertEquals("GET", req.method);
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) {
@ -20,7 +27,7 @@ function onLoad () {
}
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.finish();
responses_sent += 1;
@ -30,7 +37,7 @@ function onLoad () {
}).listen(PORT);
var client = node.http.createClient(PORT);
var req = client.get("/hello");
var req = client.get("/hello", {"Accept": "*/*", "Foo": "bar"});
req.finish(function (res) {
assertEquals(200, res.statusCode);
responses_recvd += 1;

32
website/api.txt

@ -18,7 +18,7 @@ World" after waiting two seconds:
----------------------------------------
node.http.createServer(function (request, response) {
setTimeout(function () {
response.sendHeader(200, [["Content-Type", "text/plain"]]);
response.sendHeader(200, {"Content-Type": "text/plain"});
response.sendBody("Hello World");
response.finish();
}, 2000);
@ -575,15 +575,14 @@ In particular, large, possibly chunk-encoded, messages. The interface is
careful to never buffer entire requests or responses--the
user is able to stream data.
HTTP message headers are represented by an array of 2-element
arrays like this
HTTP message headers are represented by an object like this
----------------------------------------
[ ["Content-Length", "123"]
, ["Content-Type", "text/plain"]
, ["Connection", "keep-alive"]
, ["Accept", "*/*"]
]
{ "Content-Length": "123"
, "Content-Type": "text/plain"
, "Connection": "keep-alive"
, "Accept": "*/*"
}
----------------------------------------
In order to support the full spectrum of possible HTTP applications, Node's
@ -693,7 +692,6 @@ in the actual HTTP Request.
+request.headers+ ::
The request headers expressed as an array of 2-element arrays.
Read only.
@ -727,17 +725,16 @@ passed as the second parameter to the +"request"+ event.
+response.sendHeader(statusCode, headers)+ ::
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
of 2-element arrays, representing the response headers.
status code, like +404+. The second argument, +headers+ are the response headers.
+
Example:
+
----------------------------------------
var body = "hello world";
response.sendHeader(200, [
["Content-Length", body.length],
["Content-Type", "text/plain"]
]);
response.sendHeader(200, {
"Content-Length": body.length,
"Content-Type": "text/plain"
});
----------------------------------------
+
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.
+
+request_headers+ is optional.
+request_headers+ should be an array of 2-element
arrays. Additional request headers might be added internally
Additional request headers might be added internally
by Node. Returns a +ClientRequest+ object.
+
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"+.
+response.headers+ ::
The response headers. An Array of 2-element arrays.
The response headers.
+response.setBodyEncoding(encoding)+ ::
Set the encoding for the response body. Either +"utf8"+ or +"raw"+.

2
website/index.html

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

Loading…
Cancel
Save