Skip to content

Commit

Permalink
Merge 23aa364 into 3d9b330
Browse files Browse the repository at this point in the history
  • Loading branch information
DonutEspresso committed Jul 11, 2018
2 parents 3d9b330 + 23aa364 commit 99d45a5
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 1 deletion.
20 changes: 20 additions & 0 deletions README.md
Expand Up @@ -218,6 +218,7 @@ client.del('/foo/bar', function(err, req, res) {
});
```


### StringClient

`StringClient` is what `JsonClient` is built on, and provides a base
Expand Down Expand Up @@ -470,6 +471,25 @@ client.get(options, function(err, res, socket, head) {
});
```

### More APIs

The following methods are shared by all versions of the client:

#### inflightRequests()

Returns the number of inflight requests the client is currently handling. An
inflight request is a request that is in any of the following states:

* connection establishment (dns resolution, socket connection, etc.)
* request serialization (uploading of req bodies)
* waiting for server response
* response marshalling/consumption

The request count is incremented when a verb method is called, and is
decremented when the response's `end` event is emitted, or when the request's
`error` event is emitted.


### Events

The client emits the following events:
Expand Down
22 changes: 21 additions & 1 deletion lib/HttpClient.js
Expand Up @@ -268,8 +268,8 @@ function rawRequest(opts, cb) {
req.getMetrics = function getMetrics() {
return metrics;
};
opts.client._inflightRequests--;
opts.client.emit('metrics', metrics);

emitAfter(req, res, err);
});

Expand Down Expand Up @@ -310,6 +310,7 @@ function rawRequest(opts, cb) {
log.trace({ err: realErr }, 'Request failed');
clearTimeout(connectionTimer);
clearTimeout(requestTimer);
opts.client._inflightRequests--;

// the user provided callback is invoked as soon as a connection is
// established. however, the request can be aborted or can fail after
Expand Down Expand Up @@ -512,6 +513,10 @@ function HttpClient(options) {

var self = this;

// internal only properties
this._inflightRequests = 0;

// options properties
this.agent = options.agent;
this.appendPath = options.appendPath || false;
this.ca = options.ca;
Expand Down Expand Up @@ -788,6 +793,9 @@ HttpClient.prototype.request = function request(opts, cb) {
assert.object(opts, 'options');
assert.func(cb, 'callback');

var self = this;
self._inflightRequests++;

/* eslint-disable no-param-reassign */
cb = once.strict(cb);
/* eslint-enable no-param-reassign */
Expand Down Expand Up @@ -947,3 +955,15 @@ HttpClient.prototype._options = function (method, options) {

return (opts);
};


/**
* return number of currently inflight requests
* @public
* @method inflightRequests
* @returns {Number}
*/
HttpClient.prototype.inflightRequests = function () {
var self = this;
return self._inflightRequests;
};
205 changes: 205 additions & 0 deletions test/inflightRequests.js
@@ -0,0 +1,205 @@
'use strict';

// external files
var _ = require('lodash');
var assert = require('chai').assert;
var bunyan = require('bunyan');
var restify = require('restify');

// local files
var clients = require('../lib');


describe('inflightRequests', function () {

var SERVER;
var HTTPCLIENT = clients.createHttpClient({
url: 'http://localhost:3000/',
requestTimeout: 100
});
var STRINGCLIENT = clients.createStringClient({
url: 'http://localhost:3000/',
requestTimeout: 100
});
var LOG = bunyan.createLogger({
name: 'clientlog'
});

beforeEach(function (done) {
assert.strictEqual(HTTPCLIENT.inflightRequests(), 0);
assert.strictEqual(STRINGCLIENT.inflightRequests(), 0);

SERVER = restify.createServer({
name: 'unittest',
log: LOG
});
SERVER.get('/200', function (req, res, next) {
res.send(200, { hello: 'world' });
return next();
});
SERVER.get('/500', function (req, res, next) {
res.send(500, { empty: 'world' });
return next();
});
SERVER.get('/timeout', function (req, res, next) {
setTimeout(function () {
return next();
}, 1000);
});
SERVER.use(restify.plugins.queryParser());
SERVER.listen(3000, done);
});

afterEach(function (done) {
assert.strictEqual(HTTPCLIENT.inflightRequests(), 0);
assert.strictEqual(STRINGCLIENT.inflightRequests(), 0);

HTTPCLIENT.close();
STRINGCLIENT.close();
SERVER.close(done);
});

it('StringClient should increment and decrement inflight requests',
function (done) {
// request count decremented right before callback is fired.
STRINGCLIENT.get('/200', function (err, req, res, data) {
assert.ifError(err);
assert.strictEqual(STRINGCLIENT.inflightRequests(), 0);
return done();
});

// after firing one request
assert.strictEqual(STRINGCLIENT.inflightRequests(), 1);
});

it('StringClient should increment and decrement inflight on connection ' +
'timeout',
function (done) {
// setup client to point to unresolvable IP
var client = clients.createStringClient({
url: 'http://10.255.255.1/',
connectTimeout: 100,
retry: {
minTimeout: 100,
maxTimeout: 500,
// ensure even with retries we do correct counting
retries: 1
}
});
client.get('/foo', function (err, req, res, data) {
assert.strictEqual(err.name, 'ConnectTimeoutError');
assert.strictEqual(STRINGCLIENT.inflightRequests(), 0);
return done();
});
assert.strictEqual(client.inflightRequests(), 1);
});

it('StringClient should increment and decrement inflight on request ' +
'timeout', function (done) {
// setup client to point to unresolvable IP
STRINGCLIENT.get('/timeout', function (err, req, res, data) {
assert.strictEqual(err.name, 'RequestTimeoutError');
assert.strictEqual(STRINGCLIENT.inflightRequests(), 0);
return done();
});
assert.strictEqual(STRINGCLIENT.inflightRequests(), 1);
});

it('HttpClient should increment and decrement inflight requests',
function (done) {
HTTPCLIENT.get('/200', function (err, req) {
assert.ifError(err);
assert.strictEqual(HTTPCLIENT.inflightRequests(), 1);

req.on('result', function (_err, res) {
assert.ifError(_err);
res.on('data', _.noop);
res.on('end', function () {
assert.strictEqual(HTTPCLIENT.inflightRequests(), 0);
return done();
});
});
});
});

it('HttpClient should increment and decrement connect timeout',
function (done) {
// setup client to point to unresolvable IP
var client = clients.createHttpClient({
url: 'http://10.255.255.1/',
connectTimeout: 100,
retry: false
});

client.get('/timeout', function (err, req) {
assert.ok(err);
assert.strictEqual(err.name, 'ConnectTimeoutError');
assert.strictEqual(client.inflightRequests(), 0);
return done();
});

assert.strictEqual(client.inflightRequests(), 1);
});

it('HttpClient should increment and decrement request timeout',
function (done) {
HTTPCLIENT.get('/timeout', function (err, req) {
// no connect timeout
assert.ifError(err);
assert.strictEqual(HTTPCLIENT.inflightRequests(), 1);

req.on('result', function (_err, res) {
assert.ok(_err);
assert.strictEqual(_err.name, 'RequestTimeoutError');
assert.notOk(res);
assert.strictEqual(HTTPCLIENT.inflightRequests(), 0);
return done();
});
});
});

it('HttpClient should increment and decrement on forced req abort',
function (done) {

var client = clients.createHttpClient({
url: 'http://localhost:3000'
});

client.get('/timeout', function (err, req) {
// no connect timeout
assert.ifError(err);
assert.strictEqual(client.inflightRequests(), 1);

req.on('result', function (_err, res) {
assert.strictEqual(client.inflightRequests(), 0);
assert.ok(_err);
assert.ok(_err.message, 'socket hang up');
assert.isNull(res);
return done();
});

req.abort();
});
});

it('should count multiple inflight requests', function (done) {

STRINGCLIENT.get('/200', _.noop);
assert.strictEqual(STRINGCLIENT.inflightRequests(), 1);

STRINGCLIENT.get('/200', _.noop);
assert.strictEqual(STRINGCLIENT.inflightRequests(), 2);

STRINGCLIENT.get('/200', _.noop);
assert.strictEqual(STRINGCLIENT.inflightRequests(), 3);

STRINGCLIENT.get('/200', _.noop);
assert.strictEqual(STRINGCLIENT.inflightRequests(), 4);

setTimeout(function () {
// wait for all requests to complete
assert.strictEqual(STRINGCLIENT.inflightRequests(), 0);
return done();
}, 500);
});
});

0 comments on commit 99d45a5

Please sign in to comment.