restocat/restocat

View on GitHub
lib/http/ServerResponse.js

Summary

Maintainability
A
1 hr
Test Coverage
const http = require('http');
const JSON_CONTENT_TYPE_REG = /application\/json/;

const ServerResponse = http.ServerResponse;

/**
 * Headers that cannot be multi-values.
 *
 * @see http://tools.ietf.org/html/rfc6265#section-3
 */
var HEADER_ARRAY_BLACKLIST = {
  'set-cookie': true,
  'content-type': true,
  'content-length': true
};

/*
 * CUSTOM METHODS AND REWRITE
 */

/**
 * returns ms since epoch when request was setup.
 * @public
 * @function getTime
 * @returns {Number} time
 */
ServerResponse.prototype.getTime = function getTime() {
  return this.time;
};

/**
 * sets the cache-control header. `type` defaults to _public_,
 * and options currently only takes maxAge.
 * @public
 * @function cache
 * @param    {String} type    value of the header
 * @param    {Object} options an options object
 * @returns  {String}         the value set to the header
 */
ServerResponse.prototype.cache = function cache(type, options) {
  if (typeof (type) !== 'string') {
    options = type;
    type = 'public';
  }

  if (options && options.maxAge !== undefined) {
    type += `, max-age=${options.maxAge}`;
  }

  return this.setHeader('Cache-Control', type);
};

/**
 * turns off all cache related headers.
 * @public
 * @function noCache
 * @returns  {Object} self, the response object
 */
ServerResponse.prototype.noCache = function noCache() {
  // HTTP 1.1
  this.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');

  // HTTP 1.0
  this.setHeader('Pragma', 'no-cache');

  // Proxies
  this.setHeader('Expires', '0');

  return this;
};

/**
 * Appends the provided character set to the response's Content-Type.
 * e.g., res.charSet('utf-8');
 * @public
 * @function charSet
 * @param    {String} type char-set value
 * @returns  {Object} self, the response object
 */
ServerResponse.prototype.charSet = function charSet(type) {
  this._charSet = type;

  return this;
};

/**
 * get char set
 * @public
 * @function getCharSet
 * @returns  {String} char set
 */
ServerResponse.prototype.getCharSet = function getCharSet() {
  return this._charSet;
};

/**
 * retrieves all headers off the response.
 * @public
 * @function getHeaders
 * @returns  {Object} headers
 */
ServerResponse.prototype.getHeaders = function getHeaders() {
  return (this._headers || {});
};

/* eslint no-underscore-dangle: 0 */
ServerResponse.prototype._setHeader = ServerResponse.prototype.setHeader;

/**
 * sets headers on the response.
 * @public
 * @function header
 * @param    {String} name  the name of the header
 * @param    {String} value the value of the header
 * @returns  {Object} value
 */
ServerResponse.prototype.setHeader = function header(name, value) {
  if (value === undefined) {
    return null;
  }

  if (value instanceof Date) {
    value = value.toUTCString();
  }

  const current = this.getHeader(name);

  if (current && !(name.toLowerCase() in HEADER_ARRAY_BLACKLIST)) {
    if (Array.isArray(current)) {
      current.push(value);
      value = current;
    } else {
      value = [current, value];
    }
  }

  this._setHeader(name, value);
  return value;
};

/**
 * short hand method for:
 *     res.contentType = 'json';
 *     res.send({hello: 'world'});
 *
 * @public
 * @function json
 * @param    {Number} code    http status code
 * @param    {Object} object  value to json.stringify
 * @param    {Object} headers headers to set on the response
 * @returns  {Object} self, the response object
 */
ServerResponse.prototype.json = function json(code, object, headers) {
  if (!JSON_CONTENT_TYPE_REG.test(this.getHeader('content-type'))) {
    this.setHeader('Content-Type', 'application/json');
  }

  return this.send(code, object, headers);
};

/**
 * sets the link header.
 *
 * @public
 * @function setLinkHeader
 * @param    {String} linkKey   the link key
 * @param    {String} rel the link value
 * @returns  {String}     the header value set to res
 */
ServerResponse.prototype.setLinkHeader = function setLinkHeader(linkKey, rel) {
  return this.setHeader('Link', `<${linkKey}}>; rel="${rel}"`);
};

/**
 * sends the response object. convenience method that handles:
 *     writeHead(), write(), end()
 * @public
 * @function send
 * @param    {Number} code                      http status code
 * @param    {Object | Buffer | Error} body     the content to send
 * @param    {Object}                  headers  any add'l headers to set
 * @returns  {Object}                           self, the response object
 */
ServerResponse.prototype.send = function send(code, body, headers) {
  if (code === undefined) {
    this.statusCode = 200;
  } else if (code.constructor.name === 'Number') {
    this.statusCode = code;

    if (body instanceof Error) {
      body.statusCode = this.statusCode;
    }
  } else {
    headers = body;
    body = code;
    code = null;
  }

  headers = headers || {};

  this._data = body;

  Object.keys(headers).forEach(k => this.setHeader(k, headers[k]));

  this.writeHead(this.statusCode);

  if (this._data && !(this.statusCode === 204 || this.statusCode === 304)) {
    this.write(this._data);
  }

  this.end();

  return this;
};

/**
 * sets the http status code on the response.
 * @public
 * @function setStatus
 * @param    {Number} code http status code
 * @returns  {Number}     the status code passed in
 */
ServerResponse.prototype.setStatus = function setStatus(code) {
  this.statusCode = code;

  return code;
};

/**
 * toString() serialization.
 * @public
 * @function toString
 * @returns  {String} string of response
 */
ServerResponse.prototype.toString = function toString() {
  return `HTTP/1.1 ${this.statusCode} ${http.STATUS_CODES[this.statusCode]}`;
};

/* eslint no-underscore-dangle: 0 */
ServerResponse.prototype._writeHead = ServerResponse.prototype.writeHead;

/**
 * pass through to native response.writeHead()
 * @public
 * @function writeHead
 * @returns {void}
 */
ServerResponse.prototype.writeHead = function restifyWriteHead(...args) {
  this.emit('header');

  if (this.statusCode === 204 || this.statusCode === 304) {
    this.removeHeader('Content-Length');
    this.removeHeader('Content-MD5');
    this.removeHeader('Content-Type');
    this.removeHeader('Content-Encoding');
  }

  this._writeHead(...args);
};

/**
 * http redirect
 * @public
 * @function redirect
 * @param {String} uri URI
 * @param {Number} statusCode Status code
 * @returns {void}
 */
ServerResponse.prototype.redirect = function redirect(uri, statusCode) {
  this.setHeader('Location', uri);
  this.setHeader('Content-Length', 0);
  this.send(statusCode || 302);
};

module.exports = ServerResponse;