test: improvements to various http tests

* Add common/countdown utility
* Numerous improvements to http tests

PR-URL: https://github.com/nodejs/node/pull/14315
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
This commit is contained in:
James M Snell 2017-07-14 15:05:24 -07:00
parent ed21cb1774
commit b0a8a7c6ba
26 changed files with 393 additions and 476 deletions

View File

@ -338,6 +338,42 @@ The realpath of the 'tmp' directory.
Name of the temp directory used by tests. Name of the temp directory used by tests.
## Countdown Module
The `Countdown` module provides a simple countdown mechanism for tests that
require a particular action to be taken after a given number of completed
tasks (for instance, shutting down an HTTP server after a specific number of
requests).
<!-- eslint-disable strict, required-modules -->
```js
const Countdown = require('../common/countdown');
function doSomething() {
console.log('.');
}
const countdown = new Countdown(2, doSomething);
countdown.dec();
countdown.dec();
```
### new Countdown(limit, callback)
* `limit` {number}
* `callback` {function}
Creates a new `Countdown` instance.
### Countdown.prototype.dec()
Decrements the `Countdown` counter.
### Coutndown.prototype.remaining
Specifies the remaining number of times `Countdown.prototype.dec()` must be
called before the callback is invoked.
## WPT Module ## WPT Module
The wpt.js module is a port of parts of The wpt.js module is a port of parts of

27
test/common/countdown.js Normal file
View File

@ -0,0 +1,27 @@
/* eslint-disable required-modules */
'use strict';
const assert = require('assert');
const kLimit = Symbol('limit');
const kCallback = Symbol('callback');
class Countdown {
constructor(limit, cb) {
assert.strictEqual(typeof limit, 'number');
assert.strictEqual(typeof cb, 'function');
this[kLimit] = limit;
this[kCallback] = cb;
}
dec() {
assert(this[kLimit] > 0, 'Countdown expired');
if (--this[kLimit] === 0)
this[kCallback]();
}
get remaining() {
return this[kLimit];
}
}
module.exports = Countdown;

View File

@ -0,0 +1,15 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const Countdown = require('../common/countdown');
let done = '';
const countdown = new Countdown(2, common.mustCall(() => done = true));
assert.strictEqual(countdown.remaining, 2);
countdown.dec();
assert.strictEqual(countdown.remaining, 1);
countdown.dec();
assert.strictEqual(countdown.remaining, 0);
assert.strictEqual(done, true);

View File

@ -24,42 +24,24 @@ const common = require('../common');
const http = require('http'); const http = require('http');
let serverRes; let serverRes;
const server = http.Server(function(req, res) { const server = http.Server((req, res) => {
console.log('Server accepted request.');
serverRes = res; serverRes = res;
res.writeHead(200); res.writeHead(200);
res.write('Part of my res.'); res.write('Part of my res.');
}); });
server.listen(0, common.mustCall(function() { server.listen(0, common.mustCall(() => {
http.get({ http.get({
port: this.address().port, port: server.address().port,
headers: { connection: 'keep-alive' } headers: { connection: 'keep-alive' }
}, common.mustCall(function(res) { }, common.mustCall((res) => {
server.close(); server.close();
serverRes.destroy(); serverRes.destroy();
console.log(`Got res: ${res.statusCode}`); res.resume();
console.dir(res.headers); res.on('end', common.mustCall());
res.on('aborted', common.mustCall());
res.on('data', function(chunk) {
console.log(`Read ${chunk.length} bytes`);
console.log(' chunk=%j', chunk.toString());
});
res.on('end', function() {
console.log('Response ended.');
});
res.on('aborted', function() {
console.log('Response aborted.');
});
res.socket.on('close', function() {
console.log('socket closed, but not res');
});
// it would be nice if this worked:
res.on('close', common.mustCall()); res.on('close', common.mustCall());
res.socket.on('close', common.mustCall());
})); }));
})); }));

View File

@ -20,13 +20,13 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const http = require('http'); const http = require('http');
let complete; let complete;
const server = http.createServer(function(req, res) { const server = http.createServer((req, res) => {
// We should not see the queued /thatotherone request within the server // We should not see the queued /thatotherone request within the server
// as it should be aborted before it is sent. // as it should be aborted before it is sent.
assert.strictEqual(req.url, '/'); assert.strictEqual(req.url, '/');
@ -40,9 +40,7 @@ const server = http.createServer(function(req, res) {
}); });
server.listen(0, function() { server.listen(0, () => {
console.log('listen', server.address().port);
const agent = new http.Agent({ maxSockets: 1 }); const agent = new http.Agent({ maxSockets: 1 });
assert.strictEqual(Object.keys(agent.sockets).length, 0); assert.strictEqual(Object.keys(agent.sockets).length, 0);
@ -55,7 +53,7 @@ server.listen(0, function() {
}; };
const req1 = http.request(options); const req1 = http.request(options);
req1.on('response', function(res1) { req1.on('response', (res1) => {
assert.strictEqual(Object.keys(agent.sockets).length, 1); assert.strictEqual(Object.keys(agent.sockets).length, 1);
assert.strictEqual(Object.keys(agent.requests).length, 0); assert.strictEqual(Object.keys(agent.requests).length, 0);
@ -69,7 +67,9 @@ server.listen(0, function() {
assert.strictEqual(Object.keys(agent.sockets).length, 1); assert.strictEqual(Object.keys(agent.sockets).length, 1);
assert.strictEqual(Object.keys(agent.requests).length, 1); assert.strictEqual(Object.keys(agent.requests).length, 1);
req2.on('error', function(err) { // TODO(jasnell): This event does not appear to currently be triggered.
// is this handler actually required?
req2.on('error', (err) => {
// This is expected in response to our explicit abort call // This is expected in response to our explicit abort call
assert.strictEqual(err.code, 'ECONNRESET'); assert.strictEqual(err.code, 'ECONNRESET');
}); });
@ -80,25 +80,16 @@ server.listen(0, function() {
assert.strictEqual(Object.keys(agent.sockets).length, 1); assert.strictEqual(Object.keys(agent.sockets).length, 1);
assert.strictEqual(Object.keys(agent.requests).length, 1); assert.strictEqual(Object.keys(agent.requests).length, 1);
console.log(`Got res: ${res1.statusCode}`); res1.on('data', (chunk) => complete());
console.dir(res1.headers);
res1.on('data', function(chunk) { res1.on('end', common.mustCall(() => {
console.log(`Read ${chunk.length} bytes`); setTimeout(common.mustCall(() => {
console.log(' chunk=%j', chunk.toString());
complete();
});
res1.on('end', function() {
console.log('Response ended.');
setTimeout(function() {
assert.strictEqual(Object.keys(agent.sockets).length, 0); assert.strictEqual(Object.keys(agent.sockets).length, 0);
assert.strictEqual(Object.keys(agent.requests).length, 0); assert.strictEqual(Object.keys(agent.requests).length, 0);
server.close(); server.close();
}, 100); }), 100);
}); }));
}); });
req1.end(); req1.end();

View File

@ -20,7 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const http = require('http'); const http = require('http');
@ -28,20 +28,21 @@ const http = require('http');
const maxSize = 1024; const maxSize = 1024;
let size = 0; let size = 0;
const s = http.createServer(function(req, res) { const server = http.createServer(common.mustCall((req, res) => {
this.close(); server.close();
res.writeHead(200, { 'Content-Type': 'text/plain' }); res.writeHead(200, { 'Content-Type': 'text/plain' });
for (let i = 0; i < maxSize; i++) { for (let i = 0; i < maxSize; i++) {
res.write('x' + i); res.write(`x${i}`);
} }
res.end(); res.end();
}); }));
let aborted = false; let aborted = false;
s.listen(0, function() { server.listen(0, () => {
const req = http.get('http://localhost:' + s.address().port, function(res) {
res.on('data', function(chunk) { const res = common.mustCall((res) => {
res.on('data', (chunk) => {
size += chunk.length; size += chunk.length;
assert(!aborted, 'got data after abort'); assert(!aborted, 'got data after abort');
if (size > maxSize) { if (size > maxSize) {
@ -50,11 +51,9 @@ s.listen(0, function() {
size = maxSize; size = maxSize;
} }
}); });
});
});
process.on('exit', function() { req.on('abort', common.mustCall(() => assert.strictEqual(size, maxSize)));
assert(aborted); });
assert.strictEqual(size, maxSize);
console.log('ok'); const req = http.get(`http://localhost:${server.address().port}`, res);
}); });

View File

@ -23,61 +23,51 @@
const common = require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const http = require('http'); const http = require('http');
const Countdown = require('../common/countdown');
let clientResponses = 0; const server = http.createServer(common.mustCall((req, res) => {
const server = http.createServer(common.mustCall(function(req, res) {
console.error('Server got GET request');
req.resume(); req.resume();
res.writeHead(200); res.writeHead(200);
res.write(''); res.write('');
setTimeout(function() { setTimeout(() => res.end(req.url), 50);
res.end(req.url);
}, 50);
}, 2)); }, 2));
server.on('connect', common.mustCall(function(req, socket) {
console.error('Server got CONNECT request'); const countdown = new Countdown(2, common.mustCall(() => server.close()));
server.on('connect', common.mustCall((req, socket) => {
socket.write('HTTP/1.1 200 Connection established\r\n\r\n'); socket.write('HTTP/1.1 200 Connection established\r\n\r\n');
socket.resume(); socket.resume();
socket.on('end', function() { socket.on('end', () => socket.end());
socket.end();
});
})); }));
server.listen(0, function() {
server.listen(0, common.mustCall(() => {
const req = http.request({ const req = http.request({
port: this.address().port, port: server.address().port,
method: 'CONNECT', method: 'CONNECT',
path: 'google.com:80' path: 'google.com:80'
}); });
req.on('connect', common.mustCall(function(res, socket) { req.on('connect', common.mustCall((res, socket) => {
console.error('Client got CONNECT response');
socket.end(); socket.end();
socket.on('end', function() { socket.on('end', common.mustCall(() => {
doRequest(0); doRequest(0);
doRequest(1); doRequest(1);
}); }));
socket.resume(); socket.resume();
})); }));
req.end(); req.end();
}); }));
function doRequest(i) { function doRequest(i) {
http.get({ http.get({
port: server.address().port, port: server.address().port,
path: `/request${i}` path: `/request${i}`
}, common.mustCall(function(res) { }, common.mustCall((res) => {
console.error('Client got GET response');
let data = ''; let data = '';
res.setEncoding('utf8'); res.setEncoding('utf8');
res.on('data', function(chunk) { res.on('data', (chunk) => data += chunk);
data += chunk; res.on('end', common.mustCall(() => {
});
res.on('end', function() {
assert.strictEqual(data, `/request${i}`); assert.strictEqual(data, `/request${i}`);
++clientResponses; countdown.dec();
if (clientResponses === 2) { }));
server.close();
}
});
})); }));
} }

View File

@ -23,35 +23,30 @@
const common = require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const http = require('http'); const http = require('http');
const Countdown = require('../common/countdown');
const server = http.createServer(function(req, res) { const server = http.createServer(common.mustCall((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' }); res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World\n'); res.end('Hello World\n');
}).listen(0, common.mustCall(function() { }, 2)).listen(0, common.mustCall(() => {
const agent = new http.Agent({ maxSockets: 1 }); const agent = new http.Agent({ maxSockets: 1 });
agent.on('free', function(socket) { agent.on('free', common.mustCall(3));
console.log('freeing socket. destroyed? ', socket.destroyed);
});
const requestOptions = { const requestOptions = {
agent: agent, agent: agent,
host: 'localhost', host: 'localhost',
port: this.address().port, port: server.address().port,
path: '/' path: '/'
}; };
const request1 = http.get(requestOptions, common.mustCall(function(response) { const request1 = http.get(requestOptions, common.mustCall((response) => {
// assert request2 is queued in the agent // assert request2 is queued in the agent
const key = agent.getName(requestOptions); const key = agent.getName(requestOptions);
assert.strictEqual(agent.requests[key].length, 1); assert.strictEqual(agent.requests[key].length, 1);
console.log('got response1'); request1.socket.on('close', common.mustCall());
request1.socket.on('close', function() { response.resume();
console.log('request1 socket closed'); response.on('end', common.mustCall(() => {
});
response.pipe(process.stdout);
response.on('end', common.mustCall(function() {
console.log('response1 done');
///////////////////////////////// /////////////////////////////////
// //
// THE IMPORTANT PART // THE IMPORTANT PART
@ -65,43 +60,29 @@ const server = http.createServer(function(req, res) {
// is triggered. // is triggered.
request1.socket.destroy(); request1.socket.destroy();
response.once('close', function() { // TODO(jasnell): This close event does not appear to be triggered.
// is it necessary?
response.once('close', () => {
// assert request2 was removed from the queue // assert request2 was removed from the queue
assert(!agent.requests[key]); assert(!agent.requests[key]);
console.log("waiting for request2.onSocket's nextTick"); process.nextTick(() => {
process.nextTick(common.mustCall(function() {
// assert that the same socket was not assigned to request2, // assert that the same socket was not assigned to request2,
// since it was destroyed. // since it was destroyed.
assert.notStrictEqual(request1.socket, request2.socket); assert.notStrictEqual(request1.socket, request2.socket);
assert(!request2.socket.destroyed, 'the socket is destroyed'); assert(!request2.socket.destroyed, 'the socket is destroyed');
})); });
}); });
})); }));
})); }));
const request2 = http.get(requestOptions, common.mustCall(function(response) { const request2 = http.get(requestOptions, common.mustCall((response) => {
assert(!request2.socket.destroyed); assert(!request2.socket.destroyed);
assert(request1.socket.destroyed); assert(request1.socket.destroyed);
// assert not reusing the same socket, since it was destroyed. // assert not reusing the same socket, since it was destroyed.
assert.notStrictEqual(request1.socket, request2.socket); assert.notStrictEqual(request1.socket, request2.socket);
console.log('got response2'); const countdown = new Countdown(2, common.mustCall(() => server.close()));
let gotClose = false; request2.socket.on('close', common.mustCall(() => countdown.dec()));
let gotResponseEnd = false; response.on('end', common.mustCall(() => countdown.dec()));
request2.socket.on('close', function() { response.resume();
console.log('request2 socket closed');
gotClose = true;
done();
});
response.pipe(process.stdout);
response.on('end', function() {
console.log('response2 done');
gotResponseEnd = true;
done();
});
function done() {
if (gotResponseEnd && gotClose)
server.close();
}
})); }));
})); }));

View File

@ -1,32 +1,31 @@
'use strict'; 'use strict';
require('../common');
const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const http = require('http'); const http = require('http');
const Agent = http.Agent; const Agent = http.Agent;
const server = http.createServer(function(req, res) { const server = http.createServer(common.mustCall((req, res) => {
res.end('hello world'); res.end('hello world');
}); }, 2));
server.listen(0, function() { server.listen(0, () => {
const agent = new Agent({ const agent = new Agent({ keepAlive: true });
keepAlive: true,
});
const requestParams = { const requestParams = {
host: 'localhost', host: 'localhost',
port: this.address().port, port: server.address().port,
agent: agent, agent: agent,
path: '/' path: '/'
}; };
const socketKey = agent.getName(requestParams); const socketKey = agent.getName(requestParams);
get(function(res) { http.get(requestParams, common.mustCall((res) => {
assert.strictEqual(res.statusCode, 200); assert.strictEqual(res.statusCode, 200);
res.resume(); res.resume();
res.on('end', function() { res.on('end', common.mustCall(() => {
process.nextTick(function() { process.nextTick(common.mustCall(() => {
const freeSockets = agent.freeSockets[socketKey]; const freeSockets = agent.freeSockets[socketKey];
assert.strictEqual(freeSockets.length, 1, assert.strictEqual(freeSockets.length, 1,
`expect a free socket on ${socketKey}`); `expect a free socket on ${socketKey}`);
@ -35,14 +34,10 @@ server.listen(0, function() {
const freeSocket = freeSockets[0]; const freeSocket = freeSockets[0];
freeSocket.emit('error', new Error('ECONNRESET: test')); freeSocket.emit('error', new Error('ECONNRESET: test'));
get(done); http.get(requestParams, done);
}); }));
}); }));
}); }));
function get(callback) {
return http.get(requestParams, callback);
}
function done() { function done() {
assert.strictEqual(Object.keys(agent.freeSockets).length, 0, assert.strictEqual(Object.keys(agent.freeSockets).length, 0,
@ -50,6 +45,5 @@ server.listen(0, function() {
agent.destroy(); agent.destroy();
server.close(); server.close();
process.exit(0);
} }
}); });

View File

@ -20,8 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert');
const http = require('http'); const http = require('http');
// sending `agent: false` when `port: null` is also passed in (i.e. the result // sending `agent: false` when `port: null` is also passed in (i.e. the result
@ -35,20 +34,13 @@ const opts = {
agent: false agent: false
}; };
let good = false;
process.on('exit', function() {
assert(good, 'expected either an "error" or "response" event');
});
// we just want an "error" (no local HTTP server on port 80) or "response" // we just want an "error" (no local HTTP server on port 80) or "response"
// to happen (user happens ot have HTTP server running on port 80). // to happen (user happens ot have HTTP server running on port 80).
// As long as the process doesn't crash from a C++ assertion then we're good. // As long as the process doesn't crash from a C++ assertion then we're good.
const req = http.request(opts); const req = http.request(opts);
req.on('response', function(res) {
good = true; // Will be called by either the response event or error event, not both
}); const oneResponse = common.mustCall();
req.on('error', function(err) { req.on('response', oneResponse);
// an "error" event is ok, don't crash the process req.on('error', oneResponse);
good = true;
});
req.end(); req.end();

View File

@ -34,19 +34,17 @@ const agent = new Agent({
maxFreeSockets: 5 maxFreeSockets: 5
}); });
const server = http.createServer(function(req, res) { const server = http.createServer(common.mustCall((req, res) => {
if (req.url === '/error') { if (req.url === '/error') {
res.destroy(); res.destroy();
return; return;
} else if (req.url === '/remote_close') { } else if (req.url === '/remote_close') {
// cache the socket, close it after a short delay // cache the socket, close it after a short delay
const socket = res.connection; const socket = res.connection;
setImmediate(function() { setImmediate(common.mustCall(() => socket.end()));
socket.end();
});
} }
res.end('hello world'); res.end('hello world');
}); }, 4));
function get(path, callback) { function get(path, callback) {
return http.get({ return http.get({
@ -65,82 +63,75 @@ function checkDataAndSockets(body) {
function second() { function second() {
// request second, use the same socket // request second, use the same socket
get('/second', function(res) { get('/second', common.mustCall((res) => {
assert.strictEqual(res.statusCode, 200); assert.strictEqual(res.statusCode, 200);
res.on('data', checkDataAndSockets); res.on('data', checkDataAndSockets);
res.on('end', function() { res.on('end', common.mustCall(() => {
assert.strictEqual(agent.sockets[name].length, 1); assert.strictEqual(agent.sockets[name].length, 1);
assert.strictEqual(agent.freeSockets[name], undefined); assert.strictEqual(agent.freeSockets[name], undefined);
process.nextTick(function() { process.nextTick(common.mustCall(() => {
assert.strictEqual(agent.sockets[name], undefined); assert.strictEqual(agent.sockets[name], undefined);
assert.strictEqual(agent.freeSockets[name].length, 1); assert.strictEqual(agent.freeSockets[name].length, 1);
remoteClose(); remoteClose();
}); }));
}); }));
}); }));
} }
function remoteClose() { function remoteClose() {
// mock remote server close the socket // mock remote server close the socket
get('/remote_close', function(res) { get('/remote_close', common.mustCall((res) => {
assert.deepStrictEqual(res.statusCode, 200); assert.deepStrictEqual(res.statusCode, 200);
res.on('data', checkDataAndSockets); res.on('data', checkDataAndSockets);
res.on('end', function() { res.on('end', common.mustCall(() => {
assert.strictEqual(agent.sockets[name].length, 1); assert.strictEqual(agent.sockets[name].length, 1);
assert.strictEqual(agent.freeSockets[name], undefined); assert.strictEqual(agent.freeSockets[name], undefined);
process.nextTick(function() { process.nextTick(common.mustCall(() => {
assert.strictEqual(agent.sockets[name], undefined); assert.strictEqual(agent.sockets[name], undefined);
assert.strictEqual(agent.freeSockets[name].length, 1); assert.strictEqual(agent.freeSockets[name].length, 1);
// waitting remote server close the socket // waitting remote server close the socket
setTimeout(function() { setTimeout(common.mustCall(() => {
assert.strictEqual(agent.sockets[name], undefined); assert.strictEqual(agent.sockets[name], undefined);
assert.strictEqual(agent.freeSockets[name], undefined, assert.strictEqual(agent.freeSockets[name], undefined,
'freeSockets is not empty'); 'freeSockets is not empty');
remoteError(); remoteError();
}, common.platformTimeout(200)); }), common.platformTimeout(200));
}); }));
}); }));
}); }));
} }
function remoteError() { function remoteError() {
// remove server will destroy ths socket // remove server will destroy ths socket
const req = get('/error', function(res) { const req = get('/error', common.mustNotCall());
throw new Error('should not call this function'); req.on('error', common.mustCall((err) => {
}); assert(err);
req.on('error', function(err) {
assert.ok(err);
assert.strictEqual(err.message, 'socket hang up'); assert.strictEqual(err.message, 'socket hang up');
assert.strictEqual(agent.sockets[name].length, 1); assert.strictEqual(agent.sockets[name].length, 1);
assert.strictEqual(agent.freeSockets[name], undefined); assert.strictEqual(agent.freeSockets[name], undefined);
// Wait socket 'close' event emit // Wait socket 'close' event emit
setTimeout(function() { setTimeout(common.mustCall(() => {
assert.strictEqual(agent.sockets[name], undefined); assert.strictEqual(agent.sockets[name], undefined);
assert.strictEqual(agent.freeSockets[name], undefined); assert.strictEqual(agent.freeSockets[name], undefined);
done(); server.close();
}, common.platformTimeout(1)); }), common.platformTimeout(1));
}); }));
} }
function done() { server.listen(0, common.mustCall(() => {
console.log('http keepalive agent test success.');
process.exit(0);
}
server.listen(0, function() {
name = `localhost:${server.address().port}:`; name = `localhost:${server.address().port}:`;
// request first, and keep alive // request first, and keep alive
get('/first', function(res) { get('/first', common.mustCall((res) => {
assert.strictEqual(res.statusCode, 200); assert.strictEqual(res.statusCode, 200);
res.on('data', checkDataAndSockets); res.on('data', checkDataAndSockets);
res.on('end', function() { res.on('end', common.mustCall(() => {
assert.strictEqual(agent.sockets[name].length, 1); assert.strictEqual(agent.sockets[name].length, 1);
assert.strictEqual(agent.freeSockets[name], undefined); assert.strictEqual(agent.freeSockets[name], undefined);
process.nextTick(function() { process.nextTick(common.mustCall(() => {
assert.strictEqual(agent.sockets[name], undefined); assert.strictEqual(agent.sockets[name], undefined);
assert.strictEqual(agent.freeSockets[name].length, 1); assert.strictEqual(agent.freeSockets[name].length, 1);
second(); second();
}); }));
}); }));
}); }));
}); }));

View File

@ -1,7 +1,8 @@
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const http = require('http'); const http = require('http');
const Countdown = require('../common/countdown');
const MAX_SOCKETS = 2; const MAX_SOCKETS = 2;
@ -12,9 +13,11 @@ const agent = new http.Agent({
maxFreeSockets: 2 maxFreeSockets: 2
}); });
const server = http.createServer(function(req, res) { const server = http.createServer(common.mustCall((req, res) => {
res.end('hello world'); res.end('hello world');
}); }, 6));
const countdown = new Countdown(6, common.mustCall(() => server.close()));
function get(path, callback) { function get(path, callback) {
return http.get({ return http.get({
@ -25,19 +28,14 @@ function get(path, callback) {
}, callback); }, callback);
} }
server.listen(0, function() { server.listen(0, common.mustCall(() => {
let finished = 0; for (let i = 0; i < 6; i++) {
const num_requests = 6; const request = get('/1', common.mustCall());
for (let i = 0; i < num_requests; i++) { request.on('response', common.mustCall(() => {
const request = get('/1', function() {
});
request.on('response', function() {
request.abort(); request.abort();
const sockets = agent.sockets[Object.keys(agent.sockets)[0]]; const sockets = agent.sockets[Object.keys(agent.sockets)[0]];
assert(sockets.length <= MAX_SOCKETS); assert(sockets.length <= MAX_SOCKETS);
if (++finished === num_requests) { countdown.dec();
server.close(); }));
}
});
} }
}); }));

View File

@ -1,7 +1,8 @@
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const http = require('http'); const http = require('http');
const Countdown = require('../common/countdown');
const agent = new http.Agent({ const agent = new http.Agent({
keepAlive: true, keepAlive: true,
@ -10,9 +11,9 @@ const agent = new http.Agent({
maxFreeSockets: 2 maxFreeSockets: 2
}); });
const server = http.createServer(function(req, res) { const server = http.createServer(common.mustCall((req, res) => {
res.end('hello world'); res.end('hello world');
}); }, 2));
function get(path, callback) { function get(path, callback) {
return http.get({ return http.get({
@ -23,32 +24,25 @@ function get(path, callback) {
}, callback); }, callback);
} }
let count = 0; const countdown = new Countdown(2, common.mustCall(() => {
function done() {
if (++count !== 2) {
return;
}
const freepool = agent.freeSockets[Object.keys(agent.freeSockets)[0]]; const freepool = agent.freeSockets[Object.keys(agent.freeSockets)[0]];
assert.strictEqual(freepool.length, 2, assert.strictEqual(freepool.length, 2,
`expect keep 2 free sockets, but got ${freepool.length}`); `expect keep 2 free sockets, but got ${freepool.length}`);
agent.destroy(); agent.destroy();
server.close(); server.close();
}));
function dec() {
process.nextTick(() => countdown.dec());
} }
server.listen(0, function() { function onGet(res) {
get('/1', function(res) { assert.strictEqual(res.statusCode, 200);
assert.strictEqual(res.statusCode, 200); res.resume();
res.resume(); res.on('end', common.mustCall(dec));
res.on('end', function() { }
process.nextTick(done);
});
});
get('/2', function(res) { server.listen(0, common.mustCall(() => {
assert.strictEqual(res.statusCode, 200); get('/1', common.mustCall(onGet));
res.resume(); get('/2', common.mustCall(onGet));
res.on('end', function() { }));
process.nextTick(done);
});
});
});

View File

@ -24,17 +24,17 @@ const common = require('../common');
const http = require('http'); const http = require('http');
const url = require('url'); const url = require('url');
const server = http.createServer(common.mustCall(function(req, res) { const server = http.createServer(common.mustCall((req, res) => {
res.end(); res.end();
})).listen(0, '127.0.0.1', common.mustCall(function() { })).listen(0, '127.0.0.1', common.mustCall(() => {
const opts = url.parse(`http://127.0.0.1:${this.address().port}/`); const opts = url.parse(`http://127.0.0.1:${server.address().port}/`);
// remove the `protocol` field… the `http` module should fall back // remove the `protocol` field… the `http` module should fall back
// to "http:", as defined by the global, default `http.Agent` instance. // to "http:", as defined by the global, default `http.Agent` instance.
opts.agent = new http.Agent(); opts.agent = new http.Agent();
opts.agent.protocol = null; opts.agent.protocol = null;
http.get(opts, common.mustCall(function(res) { http.get(opts, common.mustCall((res) => {
res.resume(); res.resume();
server.close(); server.close();
})); }));

View File

@ -23,14 +23,14 @@
const common = require('../common'); const common = require('../common');
const http = require('http'); const http = require('http');
const server = http.createServer(common.mustCall(function(req, res) { const server = http.createServer(common.mustCall((req, res) => {
res.end(); res.end();
})).listen(0, common.mustCall(function() { })).listen(0, common.mustCall(() => {
const options = { const options = {
agent: null, agent: null,
port: this.address().port port: server.address().port
}; };
http.get(options, common.mustCall(function(res) { http.get(options, common.mustCall((res) => {
res.resume(); res.resume();
server.close(); server.close();
})); }));

View File

@ -1,23 +1,23 @@
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const http = require('http'); const http = require('http');
const server = http.createServer(function(req, res) { const server = http.createServer(common.mustCall((req, res) => {
res.setHeader('X-Date', 'foo'); res.setHeader('X-Date', 'foo');
res.setHeader('X-Connection', 'bar'); res.setHeader('X-Connection', 'bar');
res.setHeader('X-Content-Length', 'baz'); res.setHeader('X-Content-Length', 'baz');
res.end(); res.end();
}); }));
server.listen(0); server.listen(0);
server.on('listening', function() { server.on('listening', common.mustCall(() => {
const agent = new http.Agent({ port: this.address().port, maxSockets: 1 }); const agent = new http.Agent({ port: server.address().port, maxSockets: 1 });
http.get({ http.get({
port: this.address().port, port: server.address().port,
path: '/hello', path: '/hello',
agent: agent agent: agent
}, function(res) { }, common.mustCall((res) => {
assert.strictEqual(res.statusCode, 200); assert.strictEqual(res.statusCode, 200);
assert.strictEqual(res.headers['x-date'], 'foo'); assert.strictEqual(res.headers['x-date'], 'foo');
assert.strictEqual(res.headers['x-connection'], 'bar'); assert.strictEqual(res.headers['x-connection'], 'bar');
@ -27,5 +27,5 @@ server.on('listening', function() {
assert.strictEqual(res.headers['content-length'], '0'); assert.strictEqual(res.headers['content-length'], '0');
server.close(); server.close();
agent.destroy(); agent.destroy();
}); }));
}); }));

View File

@ -25,34 +25,29 @@ const assert = require('assert');
const http = require('http'); const http = require('http');
const net = require('net'); const net = require('net');
const server = http.createServer(common.mustCall(function(req, res) { const server = http.createServer(common.mustCall((req, res) => {
assert.strictEqual('GET', req.method); assert.strictEqual('GET', req.method);
assert.strictEqual('/blah', req.url); assert.strictEqual('/blah', req.url);
assert.deepStrictEqual({ assert.deepStrictEqual({
host: 'mapdevel.trolologames.ru:443', host: 'example.org:443',
origin: 'http://mapdevel.trolologames.ru', origin: 'http://example.org',
cookie: '' cookie: ''
}, req.headers); }, req.headers);
})); }));
server.listen(0, function() { server.listen(0, common.mustCall(() => {
const c = net.createConnection(this.address().port); const c = net.createConnection(server.address().port);
c.on('connect', function() { c.on('connect', common.mustCall(() => {
c.write('GET /blah HTTP/1.1\r\n' + c.write('GET /blah HTTP/1.1\r\n' +
'Host: mapdevel.trolologames.ru:443\r\n' + 'Host: example.org:443\r\n' +
'Cookie:\r\n' + 'Cookie:\r\n' +
'Origin: http://mapdevel.trolologames.ru\r\n' + 'Origin: http://example.org\r\n' +
'\r\n\r\nhello world' '\r\n\r\nhello world'
); );
}); }));
c.on('end', function() { c.on('end', common.mustCall(() => c.end()));
c.end(); c.on('close', common.mustCall(() => server.close()));
}); }));
c.on('close', function() {
server.close();
});
});

View File

@ -32,16 +32,12 @@ for (let i = 0; i < buffer.length; i++) {
buffer[i] = i % 256; buffer[i] = i % 256;
} }
const server = http.Server(function(req, res) {
const web = http.Server(function(req, res) { server.close();
web.close();
console.log(req.headers);
let i = 0; let i = 0;
req.on('data', function(d) { req.on('data', (d) => {
process.stdout.write(',');
measuredSize += d.length; measuredSize += d.length;
for (let j = 0; j < d.length; j++) { for (let j = 0; j < d.length; j++) {
assert.strictEqual(buffer[i], d[j]); assert.strictEqual(buffer[i], d[j]);
@ -49,39 +45,27 @@ const web = http.Server(function(req, res) {
} }
}); });
req.on('end', common.mustCall(() => {
req.on('end', function() { assert.strictEqual(bufferSize, measuredSize);
res.writeHead(200); res.writeHead(200);
res.write('thanks'); res.write('thanks');
res.end(); res.end();
console.log('response with \'thanks\''); }));
});
req.connection.on('error', function(e) {
console.log(`http server-side error: ${e.message}`);
process.exit(1);
});
}); });
web.listen(0, common.mustCall(function() { server.listen(0, common.mustCall(() => {
console.log('Making request');
const req = http.request({ const req = http.request({
port: this.address().port, port: server.address().port,
method: 'GET', method: 'POST',
path: '/', path: '/',
headers: { 'content-length': buffer.length } headers: { 'content-length': buffer.length }
}, common.mustCall(function(res) { }, common.mustCall((res) => {
console.log('Got response');
res.setEncoding('utf8'); res.setEncoding('utf8');
res.on('data', common.mustCall(function(string) { let data = '';
assert.strictEqual('thanks', string); res.on('data', (chunk) => data += chunk);
res.on('end', common.mustCall(() => {
assert.strictEqual('thanks', data);
})); }));
})); }));
req.end(buffer); req.end(buffer);
})); }));
process.on('exit', function() {
assert.strictEqual(bufferSize, measuredSize);
});

View File

@ -20,7 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const http = require('http'); const http = require('http');
@ -34,31 +34,23 @@ const UTF8_STRING = '南越国是前203年至前111年存在于岭南地区的
'采用封建制和郡县制并存的制度,它的建立保证了秦末乱世岭南地区社会秩序的稳定,' + '采用封建制和郡县制并存的制度,它的建立保证了秦末乱世岭南地区社会秩序的稳定,' +
'有效的改善了岭南地区落后的政治、经济现状。'; '有效的改善了岭南地区落后的政治、经济现状。';
const server = http.createServer(function(req, res) { const server = http.createServer(common.mustCall((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf8' }); res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf8' });
res.end(UTF8_STRING, 'utf8'); res.end(UTF8_STRING, 'utf8');
}); }));
server.listen(0, function() { server.listen(0, common.mustCall(() => {
let data = ''; let data = '';
const get = http.get({ http.get({
path: '/', path: '/',
host: 'localhost', host: 'localhost',
port: this.address().port port: server.address().port
}, function(x) { }, common.mustCall((x) => {
x.setEncoding('utf8'); x.setEncoding('utf8');
x.on('data', function(c) { data += c; }); x.on('data', (c) => data += c);
x.on('error', function(e) { x.on('end', common.mustCall(() => {
throw e;
});
x.on('end', function() {
assert.strictEqual('string', typeof data); assert.strictEqual('string', typeof data);
console.log('here is the response:');
assert.strictEqual(UTF8_STRING, data); assert.strictEqual(UTF8_STRING, data);
console.log(data);
server.close(); server.close();
}); }));
}); })).end();
get.on('error', function(e) { throw e; }); }));
get.end();
});

View File

@ -20,57 +20,35 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert');
const http = require('http'); const http = require('http');
const Countdown = require('../common/countdown');
let clientAborts = 0; const N = 8;
const server = http.Server(function(req, res) { const countdown = new Countdown(N, common.mustCall(() => server.close()));
console.log('Got connection');
const server = http.Server(common.mustCall((req, res) => {
res.writeHead(200); res.writeHead(200);
res.write('Working on it...'); res.write('Working on it...');
req.on('aborted', common.mustCall(() => countdown.dec()));
}, N));
// I would expect an error event from req or res that the client aborted server.listen(0, common.mustCall(() => {
// before completing the HTTP request / response cycle, or maybe a new
// event like "aborted" or something.
req.on('aborted', function() {
clientAborts++;
console.log(`Got abort ${clientAborts}`);
if (clientAborts === N) {
console.log('All aborts detected, you win.');
server.close();
}
});
});
let responses = 0; const requests = [];
const N = 8; const reqCountdown = new Countdown(N, common.mustCall(() => {
const requests = []; requests.forEach((req) => req.abort());
}));
server.listen(0, function() { const options = { port: server.address().port };
console.log('Server listening.');
for (let i = 0; i < N; i++) { for (let i = 0; i < N; i++) {
console.log(`Making client ${i}`); options.path = `/?id=${i}`;
const options = { port: this.address().port, path: `/?id=${i}` }; requests.push(
const req = http.get(options, function(res) { http.get(options, common.mustCall((res) => {
console.log(`Client response code ${res.statusCode}`); res.resume();
reqCountdown.dec();
res.resume(); })));
if (++responses === N) {
console.log('All clients connected, destroying.');
requests.forEach(function(outReq) {
console.log('abort');
outReq.abort();
});
}
});
requests.push(req);
} }
}); }));
process.on('exit', function() {
assert.strictEqual(N, clientAborts);
});

View File

@ -20,18 +20,19 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const http = require('http'); const http = require('http');
const server = http.createServer(function(req, res) { const server = http.createServer(common.mustCall((req, res) => {
res.end('Hello'); res.end('Hello');
}); }));
server.listen(0, function() { server.listen(0, common.mustCall(() => {
const req = http.get({ port: this.address().port }, function(res) { const options = { port: server.address().port };
res.on('data', function(data) { const req = http.get(options, common.mustCall((res) => {
res.on('data', (data) => {
req.abort(); req.abort();
server.close(); server.close();
}); });
}); }));
}); }));

View File

@ -20,7 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const http = require('http'); const http = require('http');
@ -28,23 +28,22 @@ let name;
const max = 3; const max = 3;
let count = 0; let count = 0;
const server = http.Server(function(req, res) { const server = http.Server(common.mustCall((req, res) => {
if (req.url === '/0') { if (req.url === '/0') {
setTimeout(function() { setTimeout(common.mustCall(() => {
res.writeHead(200); res.writeHead(200);
res.end('Hello, World!'); res.end('Hello, World!');
}, 100); }), 100);
} else { } else {
res.writeHead(200); res.writeHead(200);
res.end('Hello, World!'); res.end('Hello, World!');
} }
}); }, max));
server.listen(0, function() { server.listen(0, common.mustCall(() => {
name = http.globalAgent.getName({ port: this.address().port }); name = http.globalAgent.getName({ port: server.address().port });
for (let i = 0; i < max; ++i) { for (let i = 0; i < max; ++i)
request(i); request(i);
} }));
});
function request(i) { function request(i) {
const req = http.get({ const req = http.get({
@ -52,7 +51,7 @@ function request(i) {
path: `/${i}` path: `/${i}`
}, function(res) { }, function(res) {
const socket = req.socket; const socket = req.socket;
socket.on('close', function() { socket.on('close', common.mustCall(() => {
++count; ++count;
if (count < max) { if (count < max) {
assert.strictEqual(http.globalAgent.sockets[name].includes(socket), assert.strictEqual(http.globalAgent.sockets[name].includes(socket),
@ -62,11 +61,7 @@ function request(i) {
assert(!http.globalAgent.requests.hasOwnProperty(name)); assert(!http.globalAgent.requests.hasOwnProperty(name));
server.close(); server.close();
} }
}); }));
res.resume(); res.resume();
}); });
} }
process.on('exit', function() {
assert.strictEqual(count, max);
});

View File

@ -2,39 +2,28 @@
const common = require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const http = require('http'); const http = require('http');
const Countdown = require('../common/countdown');
const expectedSuccesses = [undefined, null, 'GET', 'post']; const expectedSuccesses = [undefined, null, 'GET', 'post'];
let requestCount = 0; const expectedFails = [-1, 1, 0, {}, true, false, [], Symbol()];
const server = http.createServer((req, res) => { const countdown =
requestCount++; new Countdown(expectedSuccesses.length,
common.mustCall(() => server.close()));
const server = http.createServer(common.mustCall((req, res) => {
res.end(); res.end();
countdown.dec();
}, expectedSuccesses.length));
if (expectedSuccesses.length === requestCount) { server.listen(0, common.mustCall(() => {
server.close(); expectedFails.forEach((method) => {
}
}).listen(0, test);
function test() {
function fail(input) {
assert.throws(() => { assert.throws(() => {
http.request({ method: input, path: '/' }, common.mustNotCall()); http.request({ method, path: '/' }, common.mustNotCall());
}, /^TypeError: Method must be a string$/); }, /^TypeError: Method must be a string$/);
} });
fail(-1);
fail(1);
fail(0);
fail({});
fail(true);
fail(false);
fail([]);
function ok(method) {
http.request({ method: method, port: server.address().port }).end();
}
expectedSuccesses.forEach((method) => { expectedSuccesses.forEach((method) => {
ok(method); http.request({ method, port: server.address().port }).end();
}); });
} }));

View File

@ -20,9 +20,10 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const http = require('http'); const http = require('http');
const Countdown = require('../common/countdown');
const expectedHeaders = { const expectedHeaders = {
'DELETE': ['host', 'connection'], 'DELETE': ['host', 'connection'],
@ -35,17 +36,18 @@ const expectedHeaders = {
const expectedMethods = Object.keys(expectedHeaders); const expectedMethods = Object.keys(expectedHeaders);
let requestCount = 0; const countdown =
new Countdown(expectedMethods.length,
common.mustCall(() => server.close()));
const server = http.createServer(function(req, res) { const server = http.createServer(common.mustCall((req, res) => {
requestCount++;
res.end(); res.end();
assert(expectedHeaders.hasOwnProperty(req.method), assert(expectedHeaders.hasOwnProperty(req.method),
`${req.method} was an unexpected method`); `${req.method} was an unexpected method`);
const requestHeaders = Object.keys(req.headers); const requestHeaders = Object.keys(req.headers);
requestHeaders.forEach(function(header) { requestHeaders.forEach((header) => {
assert.strictEqual( assert.strictEqual(
expectedHeaders[req.method].includes(header.toLowerCase()), expectedHeaders[req.method].includes(header.toLowerCase()),
true, true,
@ -59,15 +61,14 @@ const server = http.createServer(function(req, res) {
`some headers were missing for method: ${req.method}` `some headers were missing for method: ${req.method}`
); );
if (expectedMethods.length === requestCount) countdown.dec();
server.close(); }, expectedMethods.length));
});
server.listen(0, function() { server.listen(0, common.mustCall(() => {
expectedMethods.forEach(function(method) { expectedMethods.forEach((method) => {
http.request({ http.request({
method: method, method: method,
port: server.address().port port: server.address().port
}).end(); }).end();
}); });
}); }));

View File

@ -20,20 +20,20 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert');
const http = require('http'); const http = require('http');
http.createServer(function(req, res) { const server = http.createServer((req, res) => {
res.end('ok\n'); res.end('ok');
this.close(); server.close();
}).listen(0, test); }).listen(0, common.mustCall(() => {
function test() {
http.request({ http.request({
port: this.address().port, port: server.address().port,
encoding: 'utf8' encoding: 'utf8'
}, function(res) { }, common.mustCall((res) => {
res.pipe(process.stdout); let data = '';
}).end(); res.on('data', (chunk) => data += chunk);
} res.on('end', common.mustCall(() => assert.strictEqual(data, 'ok')));
})).end();
}));

View File

@ -20,41 +20,33 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert');
const http = require('http'); const http = require('http');
const net = require('net'); const net = require('net');
const Countdown = require('../common/countdown');
let connects = 0; const countdown = new Countdown(2, common.mustCall(() => server.close()));
let parseErrors = 0;
const payloads = [
'HTTP/1.1 302 Object Moved\r\nContent-Length: 0\r\n\r\nhi world',
'bad http = should trigger parse error'
];
// Create a TCP server // Create a TCP server
net.createServer(function(c) { const server =
console.log('connection'); net.createServer(common.mustCall((c) => c.end(payloads.shift()), 2));
if (++connects === 1) {
c.end('HTTP/1.1 302 Object Moved\r\nContent-Length: 0\r\n\r\nhi world');
} else {
c.end('bad http - should trigger parse error\r\n');
this.close();
}
}).listen(0, '127.0.0.1', function() {
for (let i = 0; i < 2; i++) {
http.request({
host: '127.0.0.1',
port: this.address().port,
method: 'GET',
path: '/'
}).on('error', function(e) {
console.log('got error from client');
assert.ok(e.message.includes('Parse Error'));
assert.strictEqual(e.code, 'HPE_INVALID_CONSTANT');
parseErrors++;
}).end();
}
});
process.on('exit', function() { server.listen(0, common.mustCall(() => {
assert.strictEqual(connects, 2); for (let i = 0; i < 2; i++) {
assert.strictEqual(parseErrors, 2); http.get({
}); port: server.address().port,
path: '/'
}).on('error', common.mustCall((e) => {
common.expectsError({
code: 'HPE_INVALID_CONSTANT',
message: 'Parse Error'
})(e);
countdown.dec();
}));
}
}));