Adding a plugin interface to request that allows modifications of request parameters and response data
npm install --save oniyi-http-client
const httpClient = require('oniyi-http-client');
httpClient.get('http://httpbin.org/headers', {}, (err, response, body) => {
if (err) {
// handle error here
return;
}
// do something with response and / or body
});
const httpClient = require('oniyi-http-client');
const customClient = httpClient.create({
defaults: {
headers: {
'custom': 'foo',
},
json: true,
}
});
customClient.get('http://httpbin.org/headers', {}, (err, response, body) => {
if (err) {
// handle error here
return;
}
console.log(body.headers.custom)
// will log `foo`
});
const httpClient = require('oniyi-http-client');
const customClient = httpClient.create({
requestPhases: ['early', 'initial', 'middle', 'final'],
responsePhases: ['initial', 'middle', 'final', 'end'],
});
"Is there really a need for another http library?" you might ask. There isn't. The actual need is for the ability to asynchronously hook into the process of making a http request or receiving a response.
I came across this requirement when working with various REST APIs, making requests with a number of
different credentials (representing users logged into my application). Since the flow within my app
provided me with an user
object that has an async method to retrieve this user's credentials
(e.g. an oauth access-token), I wanted to follow the DRY
(don't repeat yourself) pattern and not
manually resolve before invoking e.g. request
.
Instead I thought it would be much easier to pass the user along with the request options and have some other module take care of resolving and injecting credentials.
Quickly more use-cases come to mind:
Also, use-cases that require to manipulate some options based on other options (maybe even compiled by
another plugin) can be solved by this phased implementation. Some REST APIs change the resource path
depending on the type of credentials being used. E.g. when using BASIC credentials, a path might be
/api/basic/foo
while when using oauth
the path changes to /api/oauth/foo
. This can be accomplished
by using e.g. oniyi-http-plugin-format-url-template
in a late phase (final
) of the onRequest PhaseLists.
This HTTP Client supports running multiple plugins / hooks in different phases before making a request
as well as after receiving a response. Both PhaseLists
are initiated with the phases initial
and final
and zipMerged
with params.requestPhases
and params.responsePhases
respectively. That means you can add more phases
by providing them in the factory params.
with custom phases
const httpClient = require('oniyi-http-client');
const customClient = httpClient.create({
requestPhases: ['early', 'initial', 'middle', 'final'],
responsePhases: ['initial', 'middle', 'final', 'end'],
});
onRequest
is one of the (currently) two hooks that executes registered plugins in the defined phases.
After all phases have run their handlers successfully, the resulting request options from ctx.options
are used to initiate a new request.Request
. The return value from request.Request
(a readable and
writable stream) is what the returned Promise
from any of the request initiating methods from client
(makeRequest
, get
, put
, post
, ...) resolves to.
Handlers in this phaseList must comply with PluginHookHandler. The received context argument is an OnRequestContext .
onResponse
is the second hook and executes registered plugins after receiving the response from request
but before invoking callback
from the request execution. That means plugins using this hook / phases can
work with and modify err, response, body
before the app's callback
function is invoked. Here you can do
things like validating response's statusCode
, parsing response data (e.g. xml to json), caching, reading
set-cookie
headers and persist in async cookie jars... the possibilities are wide.
Handlers in this phaseList must comply with PluginHookHandler. The received context argument is an OnResponseContext.
Every plugin can register any number of handlers for any of the phases available onRequest
as well as onResponse
.
The following example creates a plugin named plugin-2
which adds a request-header with name and value plugin-2
.
Also, it stores some data in shared state that is re-read on response and printed.
const plugin2 = {
name: 'plugin-2',
onRequest: [{
phaseName: 'initial',
handler: (ctx, next) => {
const { options, hookState } = ctx;
// store something in the state shared across all hooks for this request
_.set(hookState, 'plugin-2.name', 'Bam Bam!');
setTimeout(() => {
_.set(options, 'headers.plugin-2', 'plugin-2');
next();
}, 500);
},
}],
onResponse: [{
phaseName: 'final',
handler: (ctx, next) => {
const { hookState } = ctx;
// read value from state again
const name = _.get(hookState, 'plugin-2.name');
setTimeout(() => {
logger.info('Name in this plugin\'s store: %s', name);
next();
}, 500);
},
}],
};
client
.use(plugin2)
.get('http://httpbin.org/headers', (err, response, body) => {
if (err) {
logger.warn('got an error');
if (err.stack) {
logger.error(err.stack);
} else {
logger.error(err);
}
process.exit(0);
}
if (response) {
logger.debug('statusCode: %d', response.statusCode);
logger.debug('headers: ', response.headers);
logger.debug('body: ', body);
}
process.exit(0);
});
HttpClient
The default HttpClient instance. Can be used without any further configuration
Example (Use default instance)
const httpClient = require('oniyi-http-client');
httpClient.get('http://httpbin.org/headers', {}, (err, response, body) => {
if (err) {
// handle error here
return;
}
// do something with response and / or body
});
HttpClient
Create a new HttpClient instance. Use this method to create your own client instances and mount plugins for your specific request scenarios
Kind: inner method of oniyi-http-client
Returns: HttpClient
- The newly created HttpClient instance
Param | Type | Default | Description |
---|---|---|---|
[options] | Object |
{} |
|
[options.defaults] | Object |
default request options for the new instance. Will be merged into options provided with each request via _.defaultsDeep() | |
[options.requestPhases] | Array.<String> |
complete list of phase names for the onRequest phaseList. must include the names initial and final |
|
[options.responsePhases] | Array.<String> |
complete list of phase names for the onResponse phaseList. must include the names initial and final |
Example (with request defaults)
const httpClient = require('oniyi-http-client');
const customClient = httpClient.create({
defaults: {
headers: {
'custom': 'foo',
},
json: true,
}
});
customClient.get('http://httpbin.org/headers', {}, (err, response, body) => {
if (err) {
// handle error here
return;
}
console.log(body.headers.custom)
// will log `foo`
});
Example (with custom phases)
const httpClient = require('oniyi-http-client');
const customClient = httpClient.create({
requestPhases: ['early', 'initial', 'middle', 'final'],
responsePhases: ['initial', 'middle', 'final', 'end'],
});
Kind: global class
Object
Object
HttpClient
RequestPromise
RequestPromise
RequestPromise
RequestPromise
RequestPromise
RequestPromise
RequestPromise
Object
Kind: instance method of HttpClient
Returns: Object
- a clone of this instance's defaults object
Object
Create a new CookieJar with the provided
Store implementation.
Will use request.jar(store)
method
for creation when store
is not async, tough.CookieJar(store)
instead.
Kind: instance method of HttpClient
Returns: Object
- CookieJar
Param | Type | Description |
---|---|---|
[store] | Object |
tough-cookie Store |
HttpClient
Kind: instance method of HttpClient
Returns: HttpClient
- this HttpClient instance
Param | Type |
---|---|
plugin | Object |
plugin.name | String |
[plugin.onRequest] | Array.<PluginHook> |
[plugin.onResponse] | Array.<PluginHook> |
[options] | Object |
RequestPromise
make a http request with the provided arguments.
Request arguments are parsed and compiled to one options
object, merged with this instance's defaults
.
Then, the onRequest
phaseList is onvoked with mentioned options
as well as a hookState.
After all PluginHookHandler have completed, the options from OnRequestContext are used to invoke
request. The result is used to resolve this method's returned RequestPromise.
This is useful if you want to work with request's' Streaming API.
After a response is received, a OnResponseContext is created and passed through the onResponse
phaseList before finally your provided RequestArgCallback is invoked.
Kind: instance method of HttpClient
Param | Type |
---|---|
uri | RequestArgUri |
[options] | RequestArgOptions |
[callback] | RequestArgCallback |
RequestPromise
Same as #makeRequest but forces options.method
to GET
Kind: instance method of HttpClient
Param | Type |
---|---|
uri | RequestArgUri |
[options] | RequestArgOptions |
[callback] | RequestArgCallback |
RequestPromise
Same as #makeRequest but forces options.method
to PUT
Kind: instance method of HttpClient
Param | Type |
---|---|
uri | RequestArgUri |
[options] | RequestArgOptions |
[callback] | RequestArgCallback |
RequestPromise
Same as #makeRequest but forces options.method
to POST
Kind: instance method of HttpClient
Param | Type |
---|---|
uri | RequestArgUri |
[options] | RequestArgOptions |
[callback] | RequestArgCallback |
RequestPromise
Same as #makeRequest but forces options.method
to DELETE
Kind: instance method of HttpClient
Param | Type |
---|---|
uri | RequestArgUri |
[options] | RequestArgOptions |
[callback] | RequestArgCallback |
RequestPromise
Same as #makeRequest but forces options.method
to HEAD
Kind: instance method of HttpClient
Param | Type |
---|---|
uri | RequestArgUri |
[options] | RequestArgOptions |
[callback] | RequestArgCallback |
RequestPromise
Same as #makeRequest but forces options.method
to OPTIONS
Kind: instance method of HttpClient
Param | Type |
---|---|
uri | RequestArgUri |
[options] | RequestArgOptions |
[callback] | RequestArgCallback |
Object
Kind: global typedef
Properties
Name | Type | Description |
---|---|---|
phaseName | string |
Name of the phase that handler should be executed in. value can include pseudo-phase postfix ':before' or ':after' (e.g. 'initial:after' where 'initial' is the actual phaseName and ':after' the pseudo phase) |
handler | PluginHookHandler |
handler function that is invoked when running through the according phase |
function
Kind: global typedef
Param | Type | Description |
---|---|---|
context | Object |
An object with the currently available request context. Hooks in the onRequest phaseList receive an OnRequestContext while hooks that run in the onResponse phaseList receive an OnResponseContext |
next | function |
callback function that must be invoked once the handler function completed it's operations |
Object
A Hookstate instance is created for each request and shared across all phases
in the onRequest
and onResponse
phaseLists. PluginHookHandler can
modify this plain object at will (e.g. persist a timestamp in an onRequest
phase and read it again in another handler in an onResponse
phase)
Kind: global typedef
Object
mutable context object that gets passed through all phases in the onRequest
phaseList
Kind: global typedef
Properties
Name | Type | Description |
---|---|---|
hookState | Hookstate |
|
options | Object |
request options |
Object
mutable context object that gets passed through all phases in the onResponse
phaseList
Kind: global typedef
Properties
Name | Type | Description |
---|---|---|
hookState | Hookstate |
this is the Hookstate instance from this request's OnRequestContext |
options | Object |
the options property frtom this request's OnRequestContext (request options) |
[requestError] | Error |
an error when applicable (usually from (http.ClientRequest) object) |
responseBody | Object |
the response body (String or Buffer, or JSON object if the json option is supplied) |
response | Object |
an http.IncomingMessage (http.IncomingMessage) object (Response object) |
String
| Object
The first argument can be either a url
or an
options
object. The only required option is uri; all others are optional.
Kind: global typedef
Object
| function
The sesond argument can bei either
options
object or callback
function.
Kind: global typedef
function
Callback function, Invoked at the end of response receiving (or in case of error,
when the error is generated). Receives three arguments (err
, response
, body
)
that are also available in OnResponseContext
Kind: global typedef
Promise
A Promise that resolves to the return value for request()
after all
PluginHookHandler in the onRequest
phaseList have completed.
If any of the PluginHookHandlers produces an error,
Promise is rejected with that error object.
Kind: global typedef
MIT © [Benjamin Kroeger]()