New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

oniyi-http-client

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

oniyi-http-client

Adding a plugin interface to "request" that allows modifications of request parameters and response data

  • 2.0.0
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
3
decreased by-50%
Maintainers
1
Weekly downloads
 
Created
Source

oniyi-http-client

NPM version Dependency Status

Adding a plugin interface to request that allows modifications of request parameters and response data

Installation

$ npm install --save oniyi-http-client

Usage

Note: this module does not support streams, yet

const httpClientFactory = require('oniyi-http-client');

const client = httpClientFactory({
  defaults: {
    headers: {
      'Accept-Language': 'en-US,en;q=0.8',
      Host: 'httpbin.org',
      'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
      Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
    }
  }
});

client.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);
});

Motivation

"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.

Phases

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.

const client = httpClientFactory({
  requestPhases: ['early', 'initial', 'middle', 'final'],
  responsePhases: ['initial', 'middle', 'final', 'end'],
});

onRequest

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 are invoked with (ctx, next), where ctx has the following members:

  • hookState
  • options

onResponse

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 are invoked with (ctx, next), where ctx has the following members:

  • hookState
  • options
  • response
  • responseError
  • responseBody

Using plugins

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);
  });

License

MIT © Benjamin Kroeger

Keywords

FAQs

Package last updated on 31 Dec 2017

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc