lib/HTTP.js

/** @module http */
const fetch = require('node-fetch');

const Config = require('../config/Config.js');
const Logger = require('./Logger.js');

const defaultMaxRetries = /prod\w*/i.test(Config.environment) ? 3 : 0;
const defaultBackoff = 60000;

/**
 * Extract the response body from a response.
 * Make to check that the response has a body before this.
 * @param {object} response - HTTP response object
 * @return {string} http response body
 */
function extractResponseBody(response) {
  const contentType = response.headers.get('content-type');
  if (/json/.test(contentType)) {
    return response.json();
  } else if (/text/.test(contentType)) {
    // Assumes utf-8 for an attempt to guess encoding use response.textConverted()
    // https://github.com/bitinn/node-fetch/blob/master/UPGRADE-GUIDE.md#text-no-longer-tries-to-detect-encoding
    return response.text();
  } else {
    return response.body;
  }
}

/**
 * @param {object} response - HTTP response object
 * @param {string} payload -  body of the HTTP request
 * @param {string} url - URL the request was made to
 * @return {string/object} http response body. If no body the response object itself.
 */
function processResponse(response, payload, url) {
  const status = response.status;
  const logLevel = status < 400 ? 'info' : 'error';

  /**
   * log responses to console
   * @param {string} r - response text to log
   */
  function log(r) {
    Logger.log(logLevel, {
      message: 'HTTP response',
      Status: status,
      URL: url,
      Payload: payload,
      Response: r,
    });
  }

  if (response.body) {
    const body = extractResponseBody(response);
    Promise.resolve(body).then((JSONobj) => log(JSON.stringify(JSONobj)));
    return body;
  } else {
    log('No response body');
    return response;
  }
}

/**
* Generalize making http requests with node fetch
* retry failed requests with exponential backoff
* @param {string} url the request url
* @param {object} httpArguments
{
  method The HTTP method e.g POST or GET; default GET,
  headers HTTP request headers,
  body The request payload/HTTP request body
}
* @param {string} rejectionText
* @param {int} maxRetries Number of times to retry a failed request; default 3
* @param {Integer} backoff
* @return {object} http response object
*/
function request(
  url,
  {method, headers, body},
  rejectionText,
  maxRetries = defaultMaxRetries,
  backoff = defaultBackoff
) {
  return fetch(url, {method, headers, body})
    .then((response) => {
      if (response.status < 500) {
        return processResponse(response, body, url);
      } else {
        // 5xx retry
        if (maxRetries > 0) {
          // retry
          setTimeout(
            () =>
              request(
                url,
                {method, headers, body},
                rejectionText,
                maxRetries - 1,
                backoff * 2
              ),
            backoff
          );
        } else {
          return processResponse(response, body, url);
        }
      }
    })
    .catch((reason) => Logger.logRejectedPromise(rejectionText + reason));
}

module.exports = request;