npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

request-plus

v2.0.0

Published

advanced promisified http client with retries, EventEmitter, cache-manager, prom-client and logging

Downloads

5,408

Readme

build status Coverage Status

request-plus

If you like request and/or request-promise-*, then you are good to go with this add-on!

It is a set of wrappers around request-promise-any module, adding the following features (all are optional and mostly independent):

... and you can add your own wrappers too!

The only depency is request-promise-any, which has request as a peer dependecy, so you need to install separately the request version you like.

Basic Example

npm install request request-plus
const request = require('request-promise')({
  event: true,
  retry: true,
  log: true,
});

request('http://example.com/some/api')
  .then(body => { console.log('response was: ' + body)})
  .catch(error => { /*...*/ });

Advanced Example

Let's say we want to get JSON data from some resource, which fails sometimes and is in general quite slow. So we want to cache its results and do retries if it fails with a timeout or typical server errors. To implement caching we need to install additionally cache-manager

npm install request request-plus cache-manager --save
// setup a cache object
const cacheManager = require('cache-manager');
const cache = cacheManager.caching({
  store: 'memory',
  max: 500 // keep maximum 500 different URL responses
});

const rp = require('request-plus');

// create a concrete wrapper
// you have can multiple in one project with different settings
const request = rp({
  // use retry wrapper
  retry: {
    attempts: 3
  },

  // use cache wrapper
  cache: {
    cache: cache,
    cacheOptions: {
      ttl: 3600 * 4 // 4 hours
    }
  }
});

// you can use all the options available in basic request module
request({
  uri: "http://some.service/providing/data",
  json: true
})
  .then(data => {
    // we get here if we got a 200 response within 3 retries
  })
  .catch(error => {
    // well get here if the URL failed with 4xx errors,
    // or 3 retry attempts failed
  });

Wrappers

The wrappers can be specified in options when creating a new requestPlus wrapper (simple way), or you can add them one by one (advanced)

When specified in options, the wrappers will be added in a particular (common sense) order, namely: event, retry, cache, prom, log. Another limitation here: you can have only one wrapper of each type.

Sample:

const rp = require('request-plus');
const request = rp({
  event: true,
  retry: true,
  prom: {
    metric: myMetric
  }
});

When adding one by one you have full control of the order, and you may add wrappers of the same type.

const rp = require('request-plus');
const request = rp()
  .plus.wrap('prom', {
    metric: rawRequestHistogram
  })
  .plus.wrap('retry')
  .plus.wrap('prom', {
    metric: retriedRequestHistogram
  });

Defaults Wrapper

Sets default options for requests. You can use it for headers or if you know all your requets expect json.

const request = require('request-plus')({
  defaults: {
    headers: {
      'User-Agent': 'My request-plus client'
    },
    json: true
  }
});

// this will send a request with json:true preset
// and with the custom User-Agent header
request('http://some.service.com/products')
  .then(data => {
    if (data.product.length) {
      /* ... */
    }
  });

Event Wrapper

This wrapper adds emitter to the .plus container and fires basic events for each request going through:

  • request - on start of the request
  • error - on error
  • response - on successful response
const request = require('request-plus')({event: true});

// always output failed http requests to std error
// together with used request param
// independent of promise chains/catch clauses
request.plus.emitter.on('error', (uri, error) => {
  console.error('http request failed %j', uri);
})
request('http://..soooo...bad...')
.catch(() => {
  console.log("something happen, i don't know what");
})

All events have uri (which can be a string or options object) as the first parameter. Other parameters depend on the event - see source code to see additional params provided for each event

Retry Wrapper

Params (all optional):

  • attempts = 3 - number of attempt before giving up
  • delay = 500(ms) - delay between retries. You can provide a closure and calculate it to make a progressive delay, e.g. attempt => 500 * attempt * attempt
  • filterError - closure defining whether a retry should be done. By default it returns true for a timeout and statusCode in [500, 502, 503, 504]
const rp = require('request-plus');
const request = rp({
  retry: {
    attempts: 5,
    delay: 1500,

    // retry all errors
    filterErrors: error =>
      error.message === 'Error: ETIMEDOUT'
      || error.statusCode >= 400
  }
});

If there is an event wrapper initialised, then it will additionally fire events: retryRequest, retryError, retrySuccess providing the current attempt counter.

Cache Wrapper

Should be used together with a third-party module: cache-manager

Params:

  • cache (required) - an instance of cache for cache-manager module
  • cacheOptions - options used for set()
  • getKey - closure generating string cache key for given request options. By default for string param - the full URI is used as key, for an object a hash is additionally generated and added to the URI (see below)
  • hash - hash function for default getKey behavior. By default it generates a key using a very cheap algorithm, but with a significant collision probability
const rp = require('request-plus');
const cacheManager = require('cache-manager');
const memeoryCache = cacheManager.caching({store: 'memory'});
const crypto = require('crypto');
const request = rp({
  cache: {
    cache: memeoryCache,
    hash: str => crypto.createHash('md5').update(str).digest("hex")
  }
});

If there is an event wrapper initialised, then it will additionally fire events: cacheRequest and cacheMiss. You can use those to gather stats and calculate cache hits as count(hits) = count(cacheRequests) - count(cacheMisses).

Prometheus Wrapper

Should be used together with a third-party module: prom-client

The wrapper takes a prometheus metric and uses it to monitor both successful and error responses. It supports all basic metric types assuming that Counter just counts responses and Gauge, Histogram and Summary measure latency.

If the metric has status_code label, then it will be automatically set for each request.

If this wrapper doesn't meet your needs, you can add your own measurements using event wrapper (see above).

Params:

  • metric - and instance of prom-client metric
  • labels - an object with labels or a closure generating labels on the fly
const promClient = require('prom-client');
const testHistogram = new promClient.Histogram(
  'test_histogram',
  'help of test_histogram',
  ['status_code', 'nature']
  {buckets: [0.1, 1]}
);
const request = require('request-plus')({
  prom: {
    metric: testHistogram,
    labels: error => {
      if (!error) {
        return {nature: 'success'};
      }
      return error.respose.statusCode === 418
        ? {nature: 'teapot'}
        : {}
    }
  }
});

Log Wrapper

Just outputs some some of the events to stdout/stderr. Thus it requires event wrapper.

The main intention of this plugin is just to give a simple way to switch on logging when debugging. Though with some effort you can use it also in production for logging particular events

Params:

  • prefix - overrides the function used to generate the prefix preceding the log information. By default it's: eventName => '[' + eventName + ']'
  • loggers - overrides the default console log/error/warn. See source / unit tests for more details
  • events - overrides behaviour for provided events. For each event you can provide either logger name ('info', 'warn', 'error') or a function. Additionally to event parameters this function gets eventName as the first parameter.
const rp = require('request-plus');
const request = rp({
  event: true,
  log: {
    events: {
      fail: 'error',
      retryFail: (eventName, uri, attempt, error) => {
        console.error('failed despite retries: %j, on %d attempt', uri, attempt);
      }
    }
  }
});

Adding custom wrapper

function myWrapper(requester) {
  return function(uri, requestOptions, callback) {
    if (requester.plus.emitter) {
      requester.plus.emitter.emit('myEvent', 'hello from me');
    }
    console.log('the uri is %j', uri);
    return requester(uri, requestOptions, callback);
  };
}

const request = require('request-plus')()
  .plus.wrap('event')
  .plus.wrap(myWrapper);