restify-clients
Advanced tools
Comparing version 2.5.1 to 2.5.2
@@ -7,2 +7,6 @@ # restify-clients Changelog | ||
## 2.5.2 | ||
* #187: fix callback getting invoked more than once causing throw | ||
## 2.5.1 | ||
@@ -9,0 +13,0 @@ |
@@ -92,2 +92,61 @@ // Copyright 2012 Mark Cavage, Inc. All rights reserved. | ||
/** | ||
* A function that handles the issuing of the raw request through the | ||
* underlying http core modules. This function is used as the target of | ||
* retry/backoff mechanism for retrying on connection timeouts. | ||
* | ||
* The callback provided to this function is invoked with the following | ||
* signature: | ||
* function(err, req) { ... } | ||
* 1) `err` can be any errors triggered before establishing a connection | ||
* (ConnectionTimeout, DNSTimeout, etc) | ||
* 2) If no err, then the connection has been established, and the user must | ||
* listen to req's result event for further next steps. | ||
* | ||
* Once the server begins to send a response following a successful connection | ||
* establishment, the HttpClient emits a `result` event with the following | ||
* signature: | ||
* function(err, res, req) { ... } // yes, res is intentionally first | ||
* | ||
* Some notes: | ||
* * `err` can be a RequestTimeout or an http 4xx/5xx. | ||
* * res can be null in the case of RequestTimeout | ||
* | ||
* In the case of HttpClient, the callback provided to the function here is | ||
* the user provided callback via the public API: | ||
* httpclient.get('/foo', cb); // cb here is the rawRequest's cb | ||
* | ||
* This is intentional as the HttpClient is fairly low level. This means the | ||
* user is responsible for consuming the request object's `data` and `result` | ||
* events. | ||
* | ||
* In the case of the String/JSONClient, the cb is a function internal to those | ||
* client implementations. These two clients handle dealing with the req | ||
* streams, and do not invoke the user provided callback until after the | ||
* `result` event is fired. | ||
* | ||
* In short, the callback should only ever be called once, | ||
* with the two known scenarios: | ||
* | ||
* 1) A ConnectionTimeout/DNSTimeoutError occurs, and connection is never | ||
* established, and the request object is never created | ||
* 2) The connection is established, and the request object is created and | ||
* returned | ||
* | ||
* However, a RequestTimeout can occur after the connection is established - | ||
* which means we should not invoke the callback again (since it's already been | ||
* invoked), and instead pass this error via the `result` event. | ||
* | ||
* This somewhat asymmetrical way of dealing with request errors (sometimes via | ||
* the callback, sometimes via the result event) means that `once` is used to | ||
* paper over multiple invocations of the callback function provided to this | ||
* function. This is not great, and maybe worth revisiting. The `result` event, | ||
* like the callback, should only ever be emitted once. | ||
* | ||
* @private | ||
* @method rawRequest | ||
* @param {Object} opts an options object | ||
* @param {Function} cb user provided callback fn | ||
* @returns {undefined} | ||
*/ | ||
function rawRequest(opts, cb) { | ||
@@ -100,3 +159,3 @@ assert.object(opts, 'options'); | ||
/* eslint-disable no-param-reassign */ | ||
cb = once.strict(cb); | ||
cb = once(cb); | ||
/* eslint-enable no-param-reassign */ | ||
@@ -119,6 +178,2 @@ | ||
var req; | ||
// flag that captures whether or not user provided callback has been | ||
// invoked yet. this is an alternative to using once() that is more | ||
// explicit. | ||
var cbCalled = false; | ||
@@ -328,6 +383,3 @@ // increment the number of currently inflight requests | ||
// explicit and easier to reason about. | ||
if (cbCalled === false) { | ||
cb(realErr, req); | ||
cbCalled = true; | ||
} | ||
cb(realErr, req); | ||
@@ -368,13 +420,25 @@ process.nextTick(function () { | ||
if (opts.protocol === 'https:' && socket.socket) { | ||
_socket = socket.socket; | ||
} | ||
if (_socket.writable && !_socket._connecting) { | ||
function onConnect() { | ||
startRequestTimeout(); | ||
clearTimeout(connectionTimer); | ||
eventTimes.tcpConnectionAt = process.hrtime(); | ||
if (opts._keep_alive) { | ||
_socket.setKeepAlive(true); | ||
socket.setKeepAlive(true); | ||
} | ||
// eagerly call the user provided callback here with the request | ||
// obj after we've established a connection. note that it's still | ||
// possible for the request to timeout at this point, but a | ||
// RequestTimeoutError would be triggered through the 'result' event | ||
// and not the callback. | ||
cb(null, req); | ||
return; | ||
} | ||
if (opts.protocol === 'https:' && socket.socket) { | ||
_socket = socket.socket; | ||
} | ||
// if the provided url to connect to is already an IP, preemptively set | ||
@@ -386,2 +450,13 @@ // the remote address. | ||
// before we attach any events to the socket, look to see if the | ||
// socket's `connect` event has already fired. if the _connecting flag | ||
// is false, the connection was established before we were able to | ||
// attach a listener to the event, so return the request. it appears | ||
// that in this scenario, timers for dnsLookup and tlsHandshake would | ||
// be missing. | ||
if (_socket.writable && !_socket._connecting) { | ||
onConnect(); | ||
return; | ||
} | ||
// eslint-disable-next-line handle-callback-err | ||
@@ -399,21 +474,3 @@ _socket.once('lookup', function onLookup(err, addr, family, host) { | ||
_socket.once('connect', function onConnect() { | ||
startRequestTimeout(); | ||
clearTimeout(connectionTimer); | ||
eventTimes.tcpConnectionAt = process.hrtime(); | ||
if (opts._keep_alive) { | ||
_socket.setKeepAlive(true); | ||
socket.setKeepAlive(true); | ||
} | ||
// eagerly call the user provided callback here with the request | ||
// obj after we've established a connection. note that it's still | ||
// possible for the request to timeout at this point, but a | ||
// RequestTimeoutError would be triggered through the 'result' event | ||
// and not the callback. | ||
cb(null, req); | ||
cbCalled = true; | ||
}); | ||
_socket.once('connect', onConnect); | ||
}); | ||
@@ -420,0 +477,0 @@ |
{ | ||
"name": "restify-clients", | ||
"version": "2.5.1", | ||
"version": "2.5.2", | ||
"main": "lib/index.js", | ||
@@ -5,0 +5,0 @@ "description": "HttpClient, StringClient, and JsonClient extracted from restify", |
@@ -655,2 +655,20 @@ # restify-clients | ||
### Cutting a release | ||
In order to release a new version of the restify-clients module, maintainers go | ||
through the following steps: | ||
1. Bump the version number in the `package.json` file according to the changes | ||
made since the latest release. | ||
2. Add a section in the [changelog](CHANGES.md) for the version being released, | ||
and make sure that a separate item is present for each change. | ||
3. Send a pull request with those changes against the master branch. | ||
4. If the PR is approved by at least one other maintainer, merge the changes | ||
using the new version number as the complete commit message. For instance, if | ||
you're releasing version 2.5.1, the commit message should only be "2.5.1", | ||
without the quotes. | ||
5. Once that pull request is merged, pull those changes in the master branch of | ||
your local clone of the Git repoository, run `make cutarelease` and follow the | ||
instructions. | ||
## License | ||
@@ -657,0 +675,0 @@ |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
103489
2026
679
3