A reactive HTTP client for node.js and browsers designed for distributed systems, providing fault tolerance capabilities with transparent server fallback, dynamic server discovery (e.g: using Consul), request retry/backoff logic, optional client-side balancing based on empirical server latency and more...
Provides a simple middleware-oriented programmatic API and featured command-line interface. It has been designed to be lightweight (just ~2K SLOC. 9KB gzipped) and dependency free.
To get started, take a look to how does it work, basic usage, examples and API docs.
Resilient is conceptually similar to Ribbon, a Netflix's project and it was inspired by Chaos Engineering.
- Features
- Installation
- Environments
- Middleware layer
- Framework-specific adapters
- Related projects
- How does it work?
- Basic usage
- Middleware Layer
- Command-line interface
- API
- FAQ
- Contributing
- License
- Supports callback and promise based async requests.
- Reliable failover and error handling with transparent server fallback
- Smart network error handling covering multiple failure scenarios
- Smart balancer logic based on empirical server score (network latency, errors and succesfull requests)
- Transparent request retry cycle attempts on failure (configurable)
- Discern best servers based on scoring per read and write operations when balancing
- In/out traffic extensible middleware layer
- Configurable balancer policy by weight
- Configurable timeouts, retry loop, cache TTL, fallback behavior...)
- Avoid fallback/retry cycles per custom HTTP responses codes or verbs
- Define custom timeouts per HTTP method (e.g permissive for POST/PUT/DELETE, aggressive for GET)
- Parallel servers discovering for faster availability
- Built-in support for request/response interceptors (via middleware)
- Built-in support for servers caching to improve reliability when fallback
- Configurable external HTTP client to use as forward request proxy (instead of using the embedded one)
- Dynamic servers auto discovering (based on the resilient specification or via middleware)
- Able to plug in custom failure strategies to determine if a request was failed or not.
- Supports promiscuous errors (handles 400-499 codes as fallback errors)
- Supports pre/post request hooks via event bus API
- Supports mock/stub working mode via middleware (useful for testing)
- Reliable HTTP client (it uses internally request and lil-http for the browser)
- Round robin scheduling algorithm for traffic distribution (experimental)
- Featured cURL-inspired command-line interface
- Lightweight library (just ~2K SLOC, 9KB gzipped)
- Dependency free in browser environments (in node.js it only depends on
request
package) - Cross JS engine. ES5/6 compliant (requires
Promise
global to be available)
Via npm
npm install resilient
Via Bower
bower install resilient
Via Component
component install resilient-http/resilient.js
Or loading the script remotely
<script src="//cdn.rawgit.com/resilient-http/resilient.js/0.4.0/resilient.js"></script>
Runs in any ES5 compliant engine.
+4 | +33 | +29 | +10 | +19 | +7.1 |
- angular-resilient - Turn $http into a resilient and fault tolerant client
- resilient-server - node.js powered dummy HTTP lookup server for testing/development
See the basic usage and examples for detailed use cases
The following graph represents from a high-level point of view the internal logic encapsulated in Resilient HTTP client.
If require
is available, you must use it to fetch the module.
Otherwise it will be available as global exposed as resilient
var Resilient = require('resilient')
Define your service servers pool
var servers = [
'http://api1.server.com',
'http://api2.server.com',
'http://api3.server.com'
]
Create a new client and set the servers to balance
var client = Resilient({ service: { basePath: '/api/1.0' }})
client.setServers(servers)
Perform a request (the best available server will be used automatically)
client.get('/users').then(function (res) {
if (res.status === 200) {
console.log('Success:', res.data)
}
})
Define the lookup servers pool
var servers = [
'http://discover1.server.com',
'http://discover2.server.com',
'http://discover3.server.com'
]
Create a new client and set the discovering servers
var client = Resilient({ service: { basePath: '/api/1.0' }})
client.discoveryServers(servers)
Finally, perform the request (and that's all, Resilient will take care about everything to reach the best server)
client.get('/users').then(function (res) {
if (res.status === 200) {
console.log('Success:', res.data)
}
})
Note: you could use Consul or other HTTP server using a custom middleware as discovery server. For more information about the Resilient discovery interface, take a look at the documentation
For more usage cases take a look to the examples
From version 0.3.x
Resilient introduces support for duplex middleware.
It essentially provides an interceptor like layer to use external components to augment a specific functionality.
From a high-level point of view it's conceptually similar to an evented API approach, which is commonly used in a event-driven environment with JavaScript, but in this case it's slightly different in terms of flow control nature and relies more in data mutation compared to events.
The significant feature in Resilient middleware layer is that it provides bidirectional control flow for both incoming and outgoing HTTP traffic. This allows you to perform multiple actions before and after a request of a specific type is made by Resilient. This might be considered also as a sort hooks in aspect-oriented programming.
Since Resilient is divided in two communication live cycle layers, one for the discovery
servers and the other one for the service
end servers, middleware can be created for both layers:
- service - Default. Use this type in middleware which are oriented for final servers communication, such as request transformers, autorization...
- discovery - Use this type in middleware which are oriented only for lookup communication, for instance used as adapter for a lookup server which is not compatible with the Resilient lookup protocol.
Note: the middleware type should be defined a static member of the middleware returned function, using the type
property.
Required interface for middleware:
Function([ params ])
-> Function(options, resilient)
-> Object{ in: Function(err, res, next), out: Function(option, next) }
For non-duplex middleware you can use the following interface as well:
Function([ params ])
-> Function(options, resilient)
-> Function(err, res, next)
An example of a simple middleware implementation:
function testMiddleware(params) {
// Middleware-specific params
params = params || {}
// Do whatever you need here with the params
// Resilient will pass the Options
function middleware(options, resilient) {
// Do whatever you need here with Resilient client options
// such as defining servers
return {
'in': function (err, res, next) {
// Do something here with the err/response
next() // Don't forget to call next
},
'out': function (options, next) {
// Do something here with the out HTTP request options
next() // Don't forget to call next
}
}
}
middleware.type = 'discovery' // Default to: service
return middleware
}
An example of middleware usage:
var client = Resilient({
discovery: {
servers: ['http://server1', 'http://server2']
}
})
client.use(testMiddleware({
custom: 'options',
key: 'api-key',
timeout: 3000
}))
client.get('/').then(function (res) {
console.log(res)
}).catch(function (err) {
console.error(err)
})
For better approach you should install Resilient
as global package: npm install -g resilient
Resilient command-line HTTP client
Usage: resilient [url] [options]
Examples:
resilient http://httpbin.org/user-agent
resilient --status http://httpbin.org/status/201
resilient --info http://httpbin.org/status/204
resilient http://httpbin.org/post -x POST \
-d '{"hello":"world"}' -h "Content-Type: application/json"
resilient /api/users -s http://server1.net,http://server2.net
resilient /api/users -z http://discover1.net,http://discover2.net
resilient --discover -z http://discover1.net,http://discover2.net --discovery-timeout 500
Options:
--version, -v Show the Resilient client version
--path, -p Request path
--servers, -s Define the service servers (comma separated)
--method, -x HTTP method
--header, -h Define custom request header
--data, -d Value data to send as HTTP request body
--file, -f File path to send as HTTP request body
--retry, -r Request retry attempts [default: 0]
--timeout, -t Request timeout in miliseconds
--discover, -k Get an updated list of servers asking for discovery servers
--discovery-servers, -z Define the discovery service servers (comma separated)
--discovery-retry, -R Discovery servers retry attempts [default: 0]
--discovery-timeout, -T Discovery servers request maximum timeout in miliseconds
--info, -i Show response headers and info
--info-headers, -I Show only the response status and headers
--status, -c Print the response status code
--debug, -D Enable debug mode
--help, -H Show help
Creates a new resilient
client with custom config
The options object
supports three different configuration levels
Resilient({
service: { ... },
balancer: { ... },
discovery: { ... }
})
Specific configuration options for the end service servers pool of the Resilient client.
- servers
array<string>
- A list of valid servers URIs to reach for the given service. Defaultnull
. It's recommended to use discovery servers instead of static service servers - retry
number
- Number of times to retry if all requests failed. UseInfinity
for infinitive attemps. Default0
- waitBeforeRetry
number
- Number of milisenconds to wait before start the request retry cycle. Default to50
- discoverBeforeRetry
boolean
- Force to refresh service servers list asking to the discovery servers on each retry attempt. You must define the discovery servers in order to use this feature. Defaulttrue
- promiscuousErrors
boolean
- Enable promiscuous error handling mode. Client HTTP status errors (400-499) will be treated as failed request, retrying it until it has a valid status (whenretry
option is enabled). Defaultfalse
- omitRetryWhen
array<object>
- A collection of rules per method and status code to match in order to omit a request retry cycle. See the usage example. Defaultnull
- omitFallbackWhen
array<object>
- A collection of rules per method and status code to match in order to omit a request server fallback. See usage example. Defaultnull
- omitRetryOnMethods
array<string>
- Omit a retry cycle attempt if the request method is on the given array. Defaultnull
- omitFallbackOnMethods
array<string>
- Omit fallback to the next best available server if current HTTP method is on the given array. If you use this option, retry cycles will be disabled as well for the given methods. Defaultnull
- omitRetryOnErrorCodes
array<number>
- Omit a retry cycle attempt if the latest request response status code is one of the given array. Defaultnull
- omitFallbackOnErrorCodes
array<number>
- Omit fallback to the next best available server if the latest request response status code is one of the given array. Defaultnull
- timeouts
object
- Define custom request timeout values in miliseconds per HTTP method, useful to differ read/write requests. This option has priority over thetimeout
option. Default:null
Specific shared configuration options for the HTTP client for final service requests
- path
string
- Server request path as part of the final URL - basePath
string
- Server resource base path to share between all requests - method
string
- Request HTTP method. Default toGET
- data
mixed
- Payload data to send as body request - headers
object
- Map of strings representing HTTP headers to send to the server - params
object
- Map of strings representing the query params - timeout
number
- Request maximum timeout in miliseconds before to abort it. Default to10
seconds - auth
object
- Authentication credentials to the server. Object must have bothuser
andpassword
properties
Browser specific options
- withCredentials
boolean
- Whether to set the withCredentials flag on the XHR object. See MDN for more information - responseType
string
- Define how to handle the response data. Allowed values are:text
,arraybuffer
,blob
ordocument
Node.js specific options
See all HTTP options supported for node.js
here
- enable
boolean
- Enable/disable the smart client balancer. Default totrue
. - random
boolean
- Use random balance strategy usingMath.random()
. Default tofalse
. - balanceStrategy
function
- Custom balance strategy function. Interface to be implemented:function (servers []Server) => []Server
. Default tonull
. - disableWeight
boolean
- Disable emphirical built-in weight calculus based on server stats for balancing (latency, errors, success). - roundRobin
boolean
- Enable round robin schedule algorithm (experimental). Default:false
. - roundRobinSize
number
- Round robin round size. Useful to increase requests distribution across different servers. Default to3
servers. - weight
object
- Balacer calculus percentage weight used for best available server scoring policy:- success
number
- Percentage weight for success request. Default to15
. - error
number
- Percentage weight for failed request. Default to50
. - latency
number
- Percentage weight for request average latency. Default to35
.
- success
Specific configuration for discovery servers requests, behavior and logic
- servers
array<string>
- A list of valid server URIs to use as discovery servers - cacheEnabled
boolean
- Enable/disable discovery servers cache in case of global fallback. Useful to improve client reliability. Defaulttrue
- cacheExpiration
number
- Maximum cache time to live. Default to10
minutes - retry
number
- Number of times to retry if all requests failed. UseInfinity
for infinitive attemps. Default3
- waitBeforeRetry
number
- Number of milisenconds to wait before start the request retry cycle. Default to1000
- parallel
boolean
- Discover servers in parallel. This will improve service availability and decrement server lookup delays. Defaulttrue
- refreshInterval
number
- Servers list time to live in miliseconds. Default to2
minutes - enableRefreshServers
boolean
- Enable/disable discovery servers auto refresh. This option requiresrefreshServers
orenableSelfRefresh
has been defined. Defaulttrue
- refreshServers
array
- Servers list of refresh servers. This will enable automatically update discovery servers list asking for them selves to the following list of servers on each interval. Defaultnull
- refreshServersInterval
number
- Discovery servers list time to live in miliseconds. Default to5
minutes - enableSelfRefresh
boolean
- Enable/disable self-discovery using the discovery servers pools. This requires therefreshPath
option has been defined. Defaultfalse
- forceRefreshFirst
boolean
- Enable/disable forcing to refresh the server on the first request. This requires therefreshPath
andenableRefreshServers
options has been defined. Defaulttrue
- refreshPath
string
- Discovery refresh servers lookup path. Example:/app/name
. This options requires you defineenableSelfRefresh
totrue
. Defaultnull
- refreshOptions
object
- Custom HTTP options for discovery servers refresh. By default inherits from discovery options - promiscuousErrors
boolean
- Enable promiscuous error handling mode. Client HTTP status errors (400-499) will be treated as failed request, retrying it until it has a valid status (whenretry
option is enabled). Defaultfalse
- omitRetryWhen
array<object>
- A collection of rules per method and status code to match in order to omit a request retry cycle. See the usage example. Defaultnull
- omitFallbackWhen
array<object>
- A collection of rules per method and status code to match in order to omit a request server fallback. See usage example. Defaultnull
- omitRetryOnMethods
array<string>
- Omit a retry cycle attempt if the request HTTP method is on the given array. Defaultnull
- omitFallbackOnMethods
array<string>
- Omit fallback to the next best available server if the HTTP method is on the given array. If you use this option, retry cycles will be disabled as well for the given methods. Defaultnull
- omitRetryOnErrorCodes
array<number>
- Omit a retry cycle attempt if the latest request response status code is one of the given array. Defaultnull
- omitFallbackOnErrorCodes
array<number>
- Omit fallback to the next best available server if the latest request response status code is one of the given array. Defaultnull
- timeouts
object
- Define custom request timeout values in miliseconds per HTTP method, useful to differ read/write requests. This option has priority over thetimeout
option. Default:null
Specific shared configuration options for the HTTP client for discovering processes
- path
string
- Server request path as part of the final URL - basePath
string
- Server resource base path to share between all requests - timeout
number
- Server discovery network timeout in miliseconds. Default2
seconds - auth
object
- Authentication credentials required for the discovery server. Object must have bothuser
andpassword
properties - params
object
- Map of strings representing the query params - headers
object
- Map of strings representing HTTP headers to send to the discovery server - method
string
- Request HTTP method. Default toGET
- data
mixed
- Optional data to send as payload to discovery servers. Defaultnull
For node.js
, see all HTTP options supported here
- error Error|ResilientError - Response error, if happends. Otherwise
null
- response Object|http.IncomingMessage - Response object
- data
mixed
- Body response. If the MIME type isJSON-compatible
, it will be transparently parsed - status
number
- HTTP response status code - headers
object
- Response headers - xhr
object
- Original XHR instance - error
mixed
- Error info, usually anError
instance (in case that an error happens)
It could be an Error
or plain Object
instance with the following members
- message
string
- Human readable error message description - status
number
- Internal error code or server HTTP response status - code
number
- Optional error code (node.js only) - stack
string
- Optional stack error trace - request
object
- Original response object in case that a custom Resilient error happends. Optional - error
Error
- Original throwed Error instance (node.js only). Optional - xhr
XMLHttpRequest
- XHR native instance (browser only)
- 1000 - All requests failed. No servers available
- 1001 - Cannot update discovery servers. Empty or invalid response body
- 1002 - Missing discovery servers. Cannot resolve the server
- 1003 - Cannot resolve servers. Missing data
- 1004 - Discovery server response is invalid or empty
- 1005 - Missing servers during retry process
- 1006 - Internal state error (usually caused by an unexpected exception)
- 1007 - Error injected via middleware
Resilient client has a built-in support for internal states event dispacher and notifier to the public interface
This could be really useful while using an interceptor pattern in order to detect different states and data changes.
You can intercept and change any both request and response objects
subscribing to the pre/post request hooks.
Note that mutation is required, you should modify the object
by reference and do not lose it
// subscribe to every outgoing request before be dropped to the network
resilientClient.on('request:start', function handler(options, resilient) {
// mutate the options, adding an aditional header
options.headers['API-Token'] = 'awesome!'
// unsubscribe example
resilientClient.off('request:start', handler)
})
Arguments: options<Object>
, resilient<Resilient>
Fired before a request is created
You can intercept and modify the request options on the fly, but you must mutate the options object
Arguments: options<Object>
, resilient<Resilient>
Fired every time before a HTTP request is sent via network
You can intercept and modify the request options on the fly, but you must mutate the options object
Arguments: error<Error>
, response<Object|http.IncomingMessage>
, options<Object>
, resilient<Resilient>
Fired every time a HTTP response is received from a server
Arguments: error<Error>
, response<Object|http.IncomingMessage>
, resilient<Resilient>
Fired after a request was completed
You can intercept and modify the error/response on the fly, but you must mutate the object
Arguments: options<Object>
, servers<Servers>
Fired when a request performs a retry attempt cycle, that means all the previous requests has failed
Arguments: options<Object>
, response<Object>
Fired when any request (service or discovery) to a given server fails and therefore tries to perform the next server fallback
Arguments: servers<Array>
, resilient<Resilient>
Fired every time that service servers list is updated from discovery servers
Arguments: servers<Array>
, resilient<Resilient>
Fired every time that servers cache is updated
Arguments: servers<Array>
, resilient<Resilient>
Fired every time that discovery servers are updated form refresh servers
Performs a custom request with the given options. It's recommended using as generic interface to make multi verb requests
Return Client
Creates a GET request with optional custom options
Return Client
Creates a POST request with optional custom options
Return Client
Return Client
Creates a PUT request with optional custom options
Alias: del
| Return Client
Creates a DELETE request with optional custom options
Return Client
Creates a PATCH request with optional custom options
Return Client
Creates a HEAD request with optional custom options
Getter/setter accessor for resilient options, optionally per type. See supported options
Getter/setter accessor for service-level config options
Getter/setter accessor for discovery-level config options
Return: object
Getter/Setter accessor for balancer-level config options
Return: object
Get a map of HTTP specific options
Alias: failStrategy
Add a custom failure evaluator function
strategy in order to determine if Resilient should handle the request as failed or success status, retrying it accordingly if required.
Strategies should return a boolean
value indicating if the request failed (true
) or not (false
).
Use example:
var resilient = require('resilient')
var client = resilient()
client.addFailStrategy(function limitReached(err, res) {
return !err
&& +res.headers['x-ratelimit-remaining'] === 0
})
Return: boolean
Returns true
if servers are up-to-date. Otherwise false
Return: Servers
Return a Servers
instance with the current used servers per type. Allowed types are: service
and discovery
Return: array<string>
Return an array
of server URLs for the given type. Allowed types are: service
and discovery
Return: Resilient
Alias: resetStats
Reset servers stats score based on network latency and percentage of success and failed requests
This score is the average calculus of the total amount of sent requests from the client to each server.
This score is used in the scheduling algorithm in order
to determinate the best available server (in the case that the balance
option is enabled)
Allowed types are: service
and discovery
Return: Servers
Setter/Getter for discovery servers list
Return: Resilient
Pass to the callback an up-to-date list of servers asking to discovery servers
Passed arguments to the callback are:
- error
object
- Error, if it happend - servers
array
- Array ofstring
with the current service servers URL
Return: Resilient
Alias: getUpdatedServers
Pass to the callback an up-to-date list of servers, with or without discovery servers configured
Passed arguments to the callback are:
- error
object
- Error, if it happend - servers
array
- Array ofstring
with the current service servers URL
Force to update the servers list from discovery servers, if they are defined, optionally passing a callback to handle the result
Passed arguments to the callback are:
- error
object
- Error, if it happend - servers
array
- Array ofstring
with the current service servers URL
Register a new middleware. See the middleware documentation or examples for more information
Use a custom HTTP client as proxy instead of the embedded resilient
native HTTP client.
Useful to define use proxy for custom frameworks or libraries in your existent project when you need to deal with some complex HTTP pre/post hooks logic and exploit custom HTTP client features
If defined, all the outgoing requests through Resilient client will be proxied to it.
Arguments passed to the client function:
- options
object
- Resilient HTTP service options - callback
function
- Request status handler. Expected arguments are:error
,response
Note: error
and response
objects must be compatible with the current interface
Restore the native resilient
HTTP client
Define a mock/fake HTTP client error/response object
for all outgoing requests
resilient.mock(function (options, cb) {
if (options.url === 'http://discovery.server.me') {
// fake response
cb(null, { status: 200, data: ['http://server.net'] })
} else {
// fake unavailable status
cb(null, { status: 503 })
}
})
See also the useHttpClient()
method for custom request proxy forward, also useful for testing with stubs/fakes
Disable the mock/fake mode
Subscribe to an event. See supported events
Unsubscribe a given event and its handler. See supported events
Subscribe to an event with a given handler just once time. After fired, the handler will be removed
See supported events
Force to flush servers cache
Return: Client
Alias: http
Returns an HTTP client-only interface. Useful to provide encapsulation from public usage and avoid resilient-specific configuration methods to be called from the public API.
This is a restricted API useful to provide for high-level developers
Type: string
Current semver library version
Type: string
Current semver HTTP client library version
It uses request in node.js and lil-http in the browser
Type: object
Default config options
Create a new options store
Creates a new resilient HTTP client with public API
Useful to provide encapsulation to the resilient API and expose only the HTTP client (the common interface the developers want to consum)
Use the plain HTTP client (request in node.js and lil-http in the browser)
Definitely not. Discovery servers only will be used in the case that you configure them in your Resilient client. In that case Resilient will simply use the the static service servers to communicate with your backend
Yes. If your perform a request with a full URI schema, Resilient will treat it as plain request without applying any internal logic:
var client = Resilient({
service: {
servers: ['http://server1.me', 'http://server2.me']
}
})
// direct plain request (no balancing, no discovery, no fallback...)
client.get('http//custom.server/hello', function (err, res) {
// ...
})
// resilient powered request (with balancing, fallback, discovery server, cache...)
client.get('/hello', function (err, res) {
// ...
})
Of course you can do it. In browser environments this is a common premise, for example you need to use the custom HTTP client of the framework you are using in your application, or a custom library like zepto or jQuery that provides a simple AJAX interface
You can do that defining a function middleware to act as proxy pattern to intercept and wrap all the HTTP traffic via the Resilient client
var client = Resilient({})
// example using Zepto.js AJAX interface
client.useHttpClient(function httpProxy(options, cb) {
options.success = function (data, status, xhr) {
cb(null, { status: xhr.status, data: data, xhr: xhr })
}
options.error = function (xhr) {
cb({ status: xhr.status, xhr: xhr })
}
$.ajax(options)
})
For more information, see the API method documentation
Not yet. There are plans to support it in future versions.
Resilient was used in both web and node.js production applications.
The library is, indeed, relatively young and it will evolve with new features in future versions (in fact a full core and logic redesign is required), but the API consistency in not compromised between patch minor releases.
You can see the middleware documentation or see an example
Wanna help? Cool! It will be appreciated :)
You must add new test cases for any new feature or refactor you do, always following the same design/code patterns that already exist
Only node.js is required for development
Clone the repository
$ git clone https://github.com/resilient-http/resilient.js.git && cd resilient.js
Install development dependencies
$ npm install
Install browser dependencies
$ bower install
Generate browser bundle source
$ make browser
Run tests (in both node.js and headless browser)
$ make test
Run tests in real browsers
$ make test-browser
MIT © Tomas Aparicio and contributors