lib/rocky.js

Summary

Maintainability
A
30 mins
Test Coverage
const router = require('router')
const Route = require('./route')
const Base = require('./base')
const server = require('./server')
const assign = require('lodash').assign
const toArray = require('./helpers').toArray
const routeHandler = require('./protocols/http/route-handler')

module.exports = Rocky

/**
 * Rocky implements an HTTP and WebSocket proxy with built-in router
 * and hierarchical middleware layer.
 *
 * @param {Object} opts
 * @class Rocky
 * @extend Base
 * @constructor
 */

function Rocky (opts) {
  if (!(this instanceof Rocky)) return new Rocky(opts)
  opts = opts || {}
  Base.call(this, opts)

  this.server = null
  this.router = router(opts.router)
  this._setupMiddleware()
}

Rocky.prototype = Object.create(Base.prototype)

/**
 * Define the proxy networking protocol.
 * Allowed protocols are `ws` and `http`. Defaults to `http`.
 *
 * @param {String} name
 * @return {Rocky}
 * @method protocol
 */

Rocky.prototype.protocol = function (name) {
  if (name === 'ws') this.opts.ws = true
  return this
}

/**
 * Attaches an middleware function to the HTTP incoming traffic phase.
 * Middleware will be globally executed and applies to any
 * HTTP request handled by the server.
 *
 * @param {String} path
 * @method use
 * @alias useIncoming
 * @return {Rocky}
 */

Rocky.prototype.use =
Rocky.prototype.useIncoming = function () {
  this.router.use.apply(this.router, arguments)
  return this
}

/**
 * Attaches a path param based middleware function.
 * Middleware will be executed every time the given path
 * param is matched by the router.
 *
 * @param {String} param
 * @param {Function} middleware
 * @method param
 * @alias useParam
 * @return {Rocky}
 */

Rocky.prototype.param =
Rocky.prototype.useParam = function (param, middleware) {
  this.router.param.apply(this.router, arguments)
  return this
}

/**
 * Attaches a middleware function for websocket data frames.
 * Only dispatched for websocket proxies.
 *
 * @param {Function} middleware
 * @return {Rocky}
 * @method useWs
 * @return {Rocky}
 */

Rocky.prototype.useWs = function (middleware) {
  this.useFor('ws', arguments)
  return this
}

/**
 * Exposes a connect/express compatible middleware function.
 *
 * @return {Function}
 * @method middleware
 */

Rocky.prototype.middleware = function () {
  return this.requestHandler.bind(this)
}

/**
 * Middleware function handler for incoming HTTP traffic.
 * This function will be triggered by the rocky router.
 *
 * Except in very specific scenarios, you don't need to use this method.
 *
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 * @return {Rocky}
 * @method requestHandler
 */

Rocky.prototype.requestHandler = function (req, res, next) {
  this.router(req, res, next || function () {})
  return this
}

/**
 * Start listening on the network, optionally passing a
 * TCP port number and/or host to bind the connection.l
 *
 * @param {Number} port
 * @param {String} host
 * @return {Rocky}
 * @method listen
 */

Rocky.prototype.listen = function (port, host) {
  const opts = this.opts
  if (port) opts.port = port
  if (host) opts.host = host
  if (opts.ws) this.routeAll()
  this.server = server(this)
  return this
}

/**
 * Stop the server, optionally passing a callback to handle the status.
 *
 * @param {Function} done
 * @return {Rocky}
 * @method close
 */

Rocky.prototype.close = function (done) {
  if (this.server) {
    this.server.close(function (err) {
      this.server = null
      if (done) done(err)
    }.bind(this))
  }
  return this
}

/**
 * Route all the incoming HTTP traffic for any route and any HTTP verb.
 *
 * @return {Route}
 * @method routeAll
 */

Rocky.prototype.routeAll = function () {
  return this.all('/*')
}

/**
 * Creates a new route for the given HTTP verb and path.
 * For better convenience you can use the verb specific method shortcuts.
 *
 * @param {String} method
 * @param {String} path
 * @return {Route}
 * @method route
 */

Rocky.prototype.route = function (method, path) {
  const route = new Route(path)
  route.proxy = this

  const handler = routeHandler(this, route)
  const middleware = [].slice.call(arguments, 1)
  const args = middleware.concat([ handler ])

  this.router[method.toLowerCase()].apply(this.router, args)
  return route
}

/**
 * Creates a new HTTP route for GET requests and the given path.
 *
 * @param {String} path
 * @return {Route}
 * @method get
 * @name get
 * @memberof Rocky
 * @instance
 */

/**
 * Creates a new HTTP route for POST requests and the given path.
 *
 * @param {String} path
 * @return {Route}
 * @method post
 * @name post
 * @memberof Rocky
 * @instance
 */

/**
 * Creates a new HTTP route for PUT requests and the given path.
 *
 * @param {String} path
 * @return {Route}
 * @method put
 * @name put
 * @memberof Rocky
 * @instance
 */

/**
 * Creates a new HTTP route for DELETE requests and the given path.
 *
 * @param {String} path
 * @return {Route}
 * @method delete
 * @name delete
 * @memberof Rocky
 * @instance
 */

/**
 * Creates a new HTTP route for PATCH requests and the given path.
 *
 * @param {String} path
 * @return {Route}
 * @method patch
 * @name patch
 * @memberof Rocky
 * @instance
 */

/**
 * Creates a new HTTP route for HEAD requests and the given path.
 *
 * @param {String} path
 * @return {Route}
 * @method head
 * @name head
 * @memberof Rocky
 * @instance
 */

/**
 * Creates a new HTTP route for TRACE requests and the given path.
 *
 * @param {String} path
 * @return {Route}
 * @method trace
 * @name trace
 * @memberof Rocky
 * @instance
 */

/**
 * Creates a new HTTP route for any verb requests and the given path.
 *
 * @param {String} path
 * @return {Route}
 * @method all
 * @name all
 * @memberof Rocky
 * @instance
 */

;['get', 'post', 'delete', 'patch', 'put', 'head', 'trace', 'all'].forEach(function (method) {
  Rocky.prototype[method] = function (path) {
    const args = [ method ].concat(toArray(arguments))
    return this.route.apply(this, args)
  }
})

/**
 * Set up generic built-in middleware function to the middleware stack.
 *
 * @method _setupMiddleware
 * @private
 */

Rocky.prototype._setupMiddleware = function () {
  const rocky = this

  function middleware (req, res) {
    const next = arguments[arguments.length - 1]
    const opts = assign({}, rocky.opts)
    req.rocky = { proxy: rocky, options: opts }
    next()
  }

  if (rocky.opts.ws) {
    return rocky.mw.use('ws', middleware)
  }

  rocky.router.use(middleware)
}