mirror of
https://github.com/nodejs/node.git
synced 2025-05-04 07:59:32 +00:00

The copyright and license notice is already in the LICENSE file. There is no justifiable reason to also require that it be included in every file, since the individual files are not individually distributed except as part of the entire package.
550 lines
14 KiB
JavaScript
550 lines
14 KiB
JavaScript
var common = require('../common');
|
|
var assert = require('assert');
|
|
|
|
var HTTPParser = process.binding('http_parser').HTTPParser;
|
|
|
|
var CRLF = '\r\n';
|
|
var REQUEST = HTTPParser.REQUEST;
|
|
var RESPONSE = HTTPParser.RESPONSE;
|
|
|
|
var methods = HTTPParser.methods;
|
|
|
|
var kOnHeaders = HTTPParser.kOnHeaders | 0;
|
|
var kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
|
|
var kOnBody = HTTPParser.kOnBody | 0;
|
|
var kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;
|
|
|
|
// The purpose of this test is not to check HTTP compliance but to test the
|
|
// binding. Tests for pathological http messages should be submitted
|
|
// upstream to https://github.com/joyent/http-parser for inclusion into
|
|
// deps/http-parser/test.c
|
|
|
|
|
|
function newParser(type) {
|
|
var parser = new HTTPParser(type);
|
|
|
|
parser.headers = [];
|
|
parser.url = '';
|
|
|
|
parser[kOnHeaders] = function(headers, url) {
|
|
parser.headers = parser.headers.concat(headers);
|
|
parser.url += url;
|
|
};
|
|
|
|
parser[kOnHeadersComplete] = function(info) {
|
|
};
|
|
|
|
parser[kOnBody] = function(b, start, len) {
|
|
assert.ok(false, 'Function should not be called.');
|
|
};
|
|
|
|
parser[kOnMessageComplete] = function() {
|
|
};
|
|
|
|
return parser;
|
|
}
|
|
|
|
|
|
function mustCall(f, times) {
|
|
var actual = 0;
|
|
|
|
process.setMaxListeners(256);
|
|
process.on('exit', function() {
|
|
assert.equal(actual, times || 1);
|
|
});
|
|
|
|
return function() {
|
|
actual++;
|
|
return f.apply(this, Array.prototype.slice.call(arguments));
|
|
};
|
|
}
|
|
|
|
|
|
function expectBody(expected) {
|
|
return mustCall(function(buf, start, len) {
|
|
var body = '' + buf.slice(start, start + len);
|
|
assert.equal(body, expected);
|
|
});
|
|
}
|
|
|
|
|
|
//
|
|
// Simple request test.
|
|
//
|
|
(function() {
|
|
var request = Buffer(
|
|
'GET /hello HTTP/1.1' + CRLF +
|
|
CRLF);
|
|
|
|
var parser = newParser(REQUEST);
|
|
|
|
parser[kOnHeadersComplete] = mustCall(function(info) {
|
|
assert.equal(info.method, methods.indexOf('GET'));
|
|
assert.equal(info.url || parser.url, '/hello');
|
|
assert.equal(info.versionMajor, 1);
|
|
assert.equal(info.versionMinor, 1);
|
|
});
|
|
|
|
parser.execute(request, 0, request.length);
|
|
|
|
//
|
|
// Check that if we throw an error in the callbacks that error will be
|
|
// thrown from parser.execute()
|
|
//
|
|
|
|
parser[kOnHeadersComplete] = function(info) {
|
|
throw new Error('hello world');
|
|
};
|
|
|
|
parser.reinitialize(HTTPParser.REQUEST);
|
|
|
|
assert.throws(function() {
|
|
parser.execute(request, 0, request.length);
|
|
}, Error, 'hello world');
|
|
})();
|
|
|
|
|
|
//
|
|
// Simple response test.
|
|
//
|
|
(function() {
|
|
var request = Buffer(
|
|
'HTTP/1.1 200 OK' + CRLF +
|
|
'Content-Type: text/plain' + CRLF +
|
|
'Content-Length: 4' + CRLF +
|
|
CRLF +
|
|
'pong');
|
|
|
|
var parser = newParser(RESPONSE);
|
|
|
|
parser[kOnHeadersComplete] = mustCall(function(info) {
|
|
assert.equal(info.method, undefined);
|
|
assert.equal(info.versionMajor, 1);
|
|
assert.equal(info.versionMinor, 1);
|
|
assert.equal(info.statusCode, 200);
|
|
assert.equal(info.statusMessage, "OK");
|
|
});
|
|
|
|
parser[kOnBody] = mustCall(function(buf, start, len) {
|
|
var body = '' + buf.slice(start, start + len);
|
|
assert.equal(body, 'pong');
|
|
});
|
|
|
|
parser.execute(request, 0, request.length);
|
|
})();
|
|
|
|
|
|
//
|
|
// Response with no headers.
|
|
//
|
|
(function() {
|
|
var request = Buffer(
|
|
'HTTP/1.0 200 Connection established' + CRLF +
|
|
CRLF);
|
|
|
|
var parser = newParser(RESPONSE);
|
|
|
|
parser[kOnHeadersComplete] = mustCall(function(info) {
|
|
assert.equal(info.method, undefined);
|
|
assert.equal(info.versionMajor, 1);
|
|
assert.equal(info.versionMinor, 0);
|
|
assert.equal(info.statusCode, 200);
|
|
assert.equal(info.statusMessage, "Connection established");
|
|
assert.deepEqual(info.headers || parser.headers, []);
|
|
});
|
|
|
|
parser.execute(request, 0, request.length);
|
|
})();
|
|
|
|
|
|
//
|
|
// Trailing headers.
|
|
//
|
|
(function() {
|
|
var request = Buffer(
|
|
'POST /it HTTP/1.1' + CRLF +
|
|
'Transfer-Encoding: chunked' + CRLF +
|
|
CRLF +
|
|
'4' + CRLF +
|
|
'ping' + CRLF +
|
|
'0' + CRLF +
|
|
'Vary: *' + CRLF +
|
|
'Content-Type: text/plain' + CRLF +
|
|
CRLF);
|
|
|
|
var seen_body = false;
|
|
|
|
function onHeaders(headers, url) {
|
|
assert.ok(seen_body); // trailers should come after the body
|
|
assert.deepEqual(headers,
|
|
['Vary', '*', 'Content-Type', 'text/plain']);
|
|
}
|
|
|
|
var parser = newParser(REQUEST);
|
|
|
|
parser[kOnHeadersComplete] = mustCall(function(info) {
|
|
assert.equal(info.method, methods.indexOf('POST'));
|
|
assert.equal(info.url || parser.url, '/it');
|
|
assert.equal(info.versionMajor, 1);
|
|
assert.equal(info.versionMinor, 1);
|
|
// expect to see trailing headers now
|
|
parser[kOnHeaders] = mustCall(onHeaders);
|
|
});
|
|
|
|
parser[kOnBody] = mustCall(function(buf, start, len) {
|
|
var body = '' + buf.slice(start, start + len);
|
|
assert.equal(body, 'ping');
|
|
seen_body = true;
|
|
});
|
|
|
|
parser.execute(request, 0, request.length);
|
|
})();
|
|
|
|
|
|
//
|
|
// Test header ordering.
|
|
//
|
|
(function() {
|
|
var request = Buffer(
|
|
'GET / HTTP/1.0' + CRLF +
|
|
'X-Filler: 1337' + CRLF +
|
|
'X-Filler: 42' + CRLF +
|
|
'X-Filler2: 42' + CRLF +
|
|
CRLF);
|
|
|
|
var parser = newParser(REQUEST);
|
|
|
|
parser[kOnHeadersComplete] = mustCall(function(info) {
|
|
assert.equal(info.method, methods.indexOf('GET'));
|
|
assert.equal(info.versionMajor, 1);
|
|
assert.equal(info.versionMinor, 0);
|
|
assert.deepEqual(info.headers || parser.headers,
|
|
['X-Filler', '1337',
|
|
'X-Filler', '42',
|
|
'X-Filler2', '42']);
|
|
});
|
|
|
|
parser.execute(request, 0, request.length);
|
|
})();
|
|
|
|
|
|
//
|
|
// Test large number of headers
|
|
//
|
|
(function() {
|
|
// 256 X-Filler headers
|
|
var lots_of_headers = 'X-Filler: 42' + CRLF;
|
|
for (var i = 0; i < 8; ++i) lots_of_headers += lots_of_headers;
|
|
|
|
var request = Buffer(
|
|
'GET /foo/bar/baz?quux=42#1337 HTTP/1.0' + CRLF +
|
|
lots_of_headers +
|
|
CRLF);
|
|
|
|
var parser = newParser(REQUEST);
|
|
|
|
parser[kOnHeadersComplete] = mustCall(function(info) {
|
|
assert.equal(info.method, methods.indexOf('GET'));
|
|
assert.equal(info.url || parser.url, '/foo/bar/baz?quux=42#1337');
|
|
assert.equal(info.versionMajor, 1);
|
|
assert.equal(info.versionMinor, 0);
|
|
|
|
var headers = info.headers || parser.headers;
|
|
|
|
assert.equal(headers.length, 2 * 256); // 256 key/value pairs
|
|
for (var i = 0; i < headers.length; i += 2) {
|
|
assert.equal(headers[i], 'X-Filler');
|
|
assert.equal(headers[i + 1], '42');
|
|
}
|
|
});
|
|
|
|
parser.execute(request, 0, request.length);
|
|
})();
|
|
|
|
|
|
//
|
|
// Test request body
|
|
//
|
|
(function() {
|
|
var request = Buffer(
|
|
'POST /it HTTP/1.1' + CRLF +
|
|
'Content-Type: application/x-www-form-urlencoded' + CRLF +
|
|
'Content-Length: 15' + CRLF +
|
|
CRLF +
|
|
'foo=42&bar=1337');
|
|
|
|
var parser = newParser(REQUEST);
|
|
|
|
parser[kOnHeadersComplete] = mustCall(function(info) {
|
|
assert.equal(info.method, methods.indexOf('POST'));
|
|
assert.equal(info.url || parser.url, '/it');
|
|
assert.equal(info.versionMajor, 1);
|
|
assert.equal(info.versionMinor, 1);
|
|
});
|
|
|
|
parser[kOnBody] = mustCall(function(buf, start, len) {
|
|
var body = '' + buf.slice(start, start + len);
|
|
assert.equal(body, 'foo=42&bar=1337');
|
|
});
|
|
|
|
parser.execute(request, 0, request.length);
|
|
})();
|
|
|
|
|
|
//
|
|
// Test chunked request body
|
|
//
|
|
(function() {
|
|
var request = Buffer(
|
|
'POST /it HTTP/1.1' + CRLF +
|
|
'Content-Type: text/plain' + CRLF +
|
|
'Transfer-Encoding: chunked' + CRLF +
|
|
CRLF +
|
|
'3' + CRLF +
|
|
'123' + CRLF +
|
|
'6' + CRLF +
|
|
'123456' + CRLF +
|
|
'A' + CRLF +
|
|
'1234567890' + CRLF +
|
|
'0' + CRLF);
|
|
|
|
var parser = newParser(REQUEST);
|
|
|
|
parser[kOnHeadersComplete] = mustCall(function(info) {
|
|
assert.equal(info.method, methods.indexOf('POST'));
|
|
assert.equal(info.url || parser.url, '/it');
|
|
assert.equal(info.versionMajor, 1);
|
|
assert.equal(info.versionMinor, 1);
|
|
});
|
|
|
|
var body_part = 0,
|
|
body_parts = ['123', '123456', '1234567890'];
|
|
|
|
function onBody(buf, start, len) {
|
|
var body = '' + buf.slice(start, start + len);
|
|
assert.equal(body, body_parts[body_part++]);
|
|
}
|
|
|
|
parser[kOnBody] = mustCall(onBody, body_parts.length);
|
|
parser.execute(request, 0, request.length);
|
|
})();
|
|
|
|
|
|
//
|
|
// Test chunked request body spread over multiple buffers (packets)
|
|
//
|
|
(function() {
|
|
var request = Buffer(
|
|
'POST /it HTTP/1.1' + CRLF +
|
|
'Content-Type: text/plain' + CRLF +
|
|
'Transfer-Encoding: chunked' + CRLF +
|
|
CRLF +
|
|
'3' + CRLF +
|
|
'123' + CRLF +
|
|
'6' + CRLF +
|
|
'123456' + CRLF);
|
|
|
|
var parser = newParser(REQUEST);
|
|
|
|
parser[kOnHeadersComplete] = mustCall(function(info) {
|
|
assert.equal(info.method, methods.indexOf('POST'));
|
|
assert.equal(info.url || parser.url, '/it');
|
|
assert.equal(info.versionMajor, 1);
|
|
assert.equal(info.versionMinor, 1);
|
|
});
|
|
|
|
var body_part = 0,
|
|
body_parts = [
|
|
'123', '123456', '123456789',
|
|
'123456789ABC', '123456789ABCDEF'];
|
|
|
|
function onBody(buf, start, len) {
|
|
var body = '' + buf.slice(start, start + len);
|
|
assert.equal(body, body_parts[body_part++]);
|
|
}
|
|
|
|
parser[kOnBody] = mustCall(onBody, body_parts.length);
|
|
parser.execute(request, 0, request.length);
|
|
|
|
request = Buffer(
|
|
'9' + CRLF +
|
|
'123456789' + CRLF +
|
|
'C' + CRLF +
|
|
'123456789ABC' + CRLF +
|
|
'F' + CRLF +
|
|
'123456789ABCDEF' + CRLF +
|
|
'0' + CRLF);
|
|
|
|
parser.execute(request, 0, request.length);
|
|
})();
|
|
|
|
|
|
//
|
|
// Stress test.
|
|
//
|
|
(function() {
|
|
var request = Buffer(
|
|
'POST /helpme HTTP/1.1' + CRLF +
|
|
'Content-Type: text/plain' + CRLF +
|
|
'Transfer-Encoding: chunked' + CRLF +
|
|
CRLF +
|
|
'3' + CRLF +
|
|
'123' + CRLF +
|
|
'6' + CRLF +
|
|
'123456' + CRLF +
|
|
'9' + CRLF +
|
|
'123456789' + CRLF +
|
|
'C' + CRLF +
|
|
'123456789ABC' + CRLF +
|
|
'F' + CRLF +
|
|
'123456789ABCDEF' + CRLF +
|
|
'0' + CRLF);
|
|
|
|
function test(a, b) {
|
|
var parser = newParser(REQUEST);
|
|
|
|
parser[kOnHeadersComplete] = mustCall(function(info) {
|
|
assert.equal(info.method, methods.indexOf('POST'));
|
|
assert.equal(info.url || parser.url, '/helpme');
|
|
assert.equal(info.versionMajor, 1);
|
|
assert.equal(info.versionMinor, 1);
|
|
});
|
|
|
|
var expected_body = '123123456123456789123456789ABC123456789ABCDEF';
|
|
|
|
parser[kOnBody] = function(buf, start, len) {
|
|
var chunk = '' + buf.slice(start, start + len);
|
|
assert.equal(expected_body.indexOf(chunk), 0);
|
|
expected_body = expected_body.slice(chunk.length);
|
|
};
|
|
|
|
parser.execute(a, 0, a.length);
|
|
parser.execute(b, 0, b.length);
|
|
|
|
assert.equal(expected_body, '');
|
|
}
|
|
|
|
for (var i = 1; i < request.length - 1; ++i) {
|
|
var a = request.slice(0, i);
|
|
console.error('request.slice(0, ' + i + ') = ',
|
|
JSON.stringify(a.toString()));
|
|
var b = request.slice(i);
|
|
console.error('request.slice(' + i + ') = ',
|
|
JSON.stringify(b.toString()));
|
|
test(a, b);
|
|
}
|
|
})();
|
|
|
|
|
|
//
|
|
// Byte by byte test.
|
|
//
|
|
(function() {
|
|
var request = Buffer(
|
|
'POST /it HTTP/1.1' + CRLF +
|
|
'Content-Type: text/plain' + CRLF +
|
|
'Transfer-Encoding: chunked' + CRLF +
|
|
CRLF +
|
|
'3' + CRLF +
|
|
'123' + CRLF +
|
|
'6' + CRLF +
|
|
'123456' + CRLF +
|
|
'9' + CRLF +
|
|
'123456789' + CRLF +
|
|
'C' + CRLF +
|
|
'123456789ABC' + CRLF +
|
|
'F' + CRLF +
|
|
'123456789ABCDEF' + CRLF +
|
|
'0' + CRLF);
|
|
|
|
var parser = newParser(REQUEST);
|
|
|
|
parser[kOnHeadersComplete] = mustCall(function(info) {
|
|
assert.equal(info.method, methods.indexOf('POST'));
|
|
assert.equal(info.url || parser.url, '/it');
|
|
assert.equal(info.versionMajor, 1);
|
|
assert.equal(info.versionMinor, 1);
|
|
assert.deepEqual(info.headers || parser.headers,
|
|
['Content-Type', 'text/plain',
|
|
'Transfer-Encoding', 'chunked']);
|
|
});
|
|
|
|
var expected_body = '123123456123456789123456789ABC123456789ABCDEF';
|
|
|
|
parser[kOnBody] = function(buf, start, len) {
|
|
var chunk = '' + buf.slice(start, start + len);
|
|
assert.equal(expected_body.indexOf(chunk), 0);
|
|
expected_body = expected_body.slice(chunk.length);
|
|
};
|
|
|
|
for (var i = 0; i < request.length; ++i) {
|
|
parser.execute(request, i, 1);
|
|
}
|
|
|
|
assert.equal(expected_body, '');
|
|
})();
|
|
|
|
|
|
//
|
|
// Test parser reinit sequence.
|
|
//
|
|
(function() {
|
|
var req1 = Buffer(
|
|
'PUT /this HTTP/1.1' + CRLF +
|
|
'Content-Type: text/plain' + CRLF +
|
|
'Transfer-Encoding: chunked' + CRLF +
|
|
CRLF +
|
|
'4' + CRLF +
|
|
'ping' + CRLF +
|
|
'0' + CRLF);
|
|
|
|
var req2 = Buffer(
|
|
'POST /that HTTP/1.0' + CRLF +
|
|
'Content-Type: text/plain' + CRLF +
|
|
'Content-Length: 4' + CRLF +
|
|
CRLF +
|
|
'pong');
|
|
|
|
function onHeadersComplete1(info) {
|
|
assert.equal(info.method, methods.indexOf('PUT'));
|
|
assert.equal(info.url, '/this');
|
|
assert.equal(info.versionMajor, 1);
|
|
assert.equal(info.versionMinor, 1);
|
|
assert.deepEqual(info.headers,
|
|
['Content-Type', 'text/plain',
|
|
'Transfer-Encoding', 'chunked']);
|
|
};
|
|
|
|
function onHeadersComplete2(info) {
|
|
assert.equal(info.method, methods.indexOf('POST'));
|
|
assert.equal(info.url, '/that');
|
|
assert.equal(info.versionMajor, 1);
|
|
assert.equal(info.versionMinor, 0);
|
|
assert.deepEqual(info.headers,
|
|
['Content-Type', 'text/plain',
|
|
'Content-Length', '4']);
|
|
};
|
|
|
|
var parser = newParser(REQUEST);
|
|
parser[kOnHeadersComplete] = onHeadersComplete1;
|
|
parser[kOnBody] = expectBody('ping');
|
|
parser.execute(req1, 0, req1.length);
|
|
|
|
parser.reinitialize(REQUEST);
|
|
parser[kOnBody] = expectBody('pong');
|
|
parser[kOnHeadersComplete] = onHeadersComplete2;
|
|
parser.execute(req2, 0, req2.length);
|
|
})();
|
|
|
|
// Test parser 'this' safety
|
|
// https://github.com/joyent/node/issues/6690
|
|
assert.throws(function() {
|
|
var request = Buffer(
|
|
'GET /hello HTTP/1.1' + CRLF +
|
|
CRLF);
|
|
|
|
var parser = newParser(REQUEST);
|
|
var notparser = { execute: parser.execute };
|
|
notparser.execute(request, 0, request.length);
|
|
}, TypeError);
|