good-guy-http

Good guy HTTP is an HTTP client library based on the request module, adding the following stuff on top:
- easy promise-based interface
- caching GET and other idempotent requests, either in-memory or using your chosen cache
- this automatically obeys 'Cache-control' headers, but you can provide defaults for when it's missing
- retrying failed requests
- collapsing identical requests made at the same time into one
- reporting HTTP error statuses as errors (promise rejections)
- sane but strict defaults regarding timeouts etc.
- implementation of the circuit breaker pattern
- optional postprocessing of response to cache expensive parsing/munging operations
- supports everything request supports by passing all the options to it

All of this is optional and you can opt-out of some or all of it.
Usage
var goodGuy = require('good-guy-http')();
goodGuy('http://news.ycombinator.com').then(function(response) {
console.log(response.body);
});
That's the basics. If you want to change the default behaviour, pass a configuration object:
var goodGuy = require('good-guy-http')({
maxRetries: 2,
collapseIdenticalRequests: true,
allowServingStale: true,
cache: ...,
cacheResponseTimeout: 500
errorLogger: console.error,
postprocess: false,
usePromise: require('bluebird'),
defaultCaching: {
cached: true,
timeToLive: 5000,
mustRevalidate: false
},
clientErrorCaching: {
cached: true,
timeToLive: 60000,
mustRevalidate: false
},
circuitBreaking: {
errorThreshold: 50
}
});
You can also pass options to the request
module through this configuration object. Any options good guy doesn't
recognize will be used to configure the underlying request
object:
var goodGuy = require('good-guy-http')({
timeout: 100
});
Good guy objects can also be reconfigured on the fly by adding good guy options to the request:
goodGuy({url: 'http://extremely-flaky-server.org', maxRetries: 10}).then(...);
The goodguy interface
Mirrors what request
does almost exactly. Any object that the request
module can handle can also be passed to good-guy-http
.
All options will be passed onto request. The get
, post
, etc. convenience methods are also present.
All functions support both a promise-based interface (when no callback is passed) and a traditional callback-based one
(when a callback function is passed as the second parameter).
The response object that you will receive will not be a http.IncomingMessage, since those are difficult to cache. Instead, you will get a plain old object with statusCode
, headers
, body
and httpVersion
in all the same places they would be in normal responses.
Caches
Any object that has these methods can be used as a cache:
store(key, object)
- returning a promise that resolves when the object is storedretrieve(key)
- returning a promise that resolves with the previously stored object, or undefined if no object is found
By default, an in-memory cache limited to the 500 most recently used requests is used, but you can easily override this:
var goodGuyLib = require('good-guy-http');
var goodGuy = goodGuyLib({cache: goodGuyLib.inMemoryCache(10)});
var goodGuy = goodGuyLib({cache: false});
var goodGuy = goodGuyLib({cache: customCache});
Circuit breaker
To avoid overloading external services that are having trouble coping with the load, good-guy-http uses a circuit breaker
(based on Yammer's circuit-breaker-js) by default. Each host is treated as a separate service and
has a separate circuit breaker.
Once the breaker trips (too many failures), your requests will start failing with a CircuitBrokenError. It can be easily
identified by having the code
property set to ECIRCUIT
. Once the situation improves, requests will start going
through normally again.
You can configure the error threshold or turn the whole feature off:
var goodGuyLib = require('good-guy-http');
var goodGuy = goodGuyLib({
circuitBreaking: { errorThreshold: 75 };
});
var goodGuy = goodGuyLib({circuitBreaking: false});