UNPKG

rocky

Version:

Full-featured, middleware-oriented, hackable HTTP and WebSocket proxy

293 lines (251 loc) 6.14 kB
const MwPool = require('midware-pool') const assign = require('lodash').assign const middleware = require('./middleware') const Emitter = require('events').EventEmitter module.exports = Base /** * Base generic interface implementing HTTP shared logic and features * inherited across different HTTP abstract entities. * * @param {Object} opts * @class Base * @extend EventEmitter * @constructor */ function Base (opts) { Emitter.call(this) this.replays = [] this.opts = opts || {} this.mw = new MwPool() } Base.prototype = Object.create(Emitter.prototype) /** * Target URI to foward the incoming traffic. * * @param {String} url * @param {Object} opts * @return {Base} * @method target * @alias forward * @alias forwardTo */ Base.prototype.target = Base.prototype.forward = Base.prototype.forwardTo = function (url, opts) { if (Array.isArray(url)) { return this.balance(url) } this.opts.target = url assign(this.opts, opts) return this } /** * Defines multiple URLs to balance the icnoming traffic. * * @param {Array} urls * @return {Base} * @method balance */ Base.prototype.balance = function (urls) { if (Array.isArray(urls)) { this.opts.balance = urls } return this } /** * Defines a target replay server URL. * You can optionally pass a replay only custom options. * * Call this method multiple times to replay * traffic to multiple targets. * * @param {String} url * @param {Object} opts * @return {Base} * @method replay * @alias replayTo */ Base.prototype.replay = Base.prototype.replayTo = function (url, opts) { url = typeof url === 'string' ? { target: url } : url const replay = assign({}, url, opts) this.replays.push(replay) return this } /** * Defines custom options, optionally overriden the existent ones. * * @param {Object} opts * @return {Base} * @method options */ Base.prototype.options = function (opts) { assign(this.opts, opts) return this } /** * Enable first forward, and then if all success, replay operation mode, optionally passing * a middleware-like function to determine if the forward mode * should be applied or not to the current HTTP request. * * @param {Function} filter * @return {Base} * @method sequential * @alias replayAfterForward */ Base.prototype.sequential = Base.prototype.replayAfterForward = function (filter) { this.bufferBody(filter) this.opts.replayAfterForward = true return this } /** * Enable sequential replay mode. In case that one replay server fails * the subsequent pending replay request will be canceled. * * @param {Function} filter * @return {Base} * @method replaySequentially */ Base.prototype.replaySequentially = function (filter) { this.bufferBody(filter) this.opts.replaySequentially = true return this } /** * Enable retry/backoff logic for HTTP traffic. * The resilient algorithm will retry with a simple exponential backoff * logic multiple times (configurable), until the server * replies with a proper status. * * Enabling this implies that all the request payload data will be buffered in heap memory. * Don't use it when dealing with large payloads. * * @param {Object} opts * @param {Function} filter * @return {Base} * @method retry */ Base.prototype.retry = function (opts, filter) { this.bufferBody(filter) this.opts.retry = opts return this } /** * Shortcut method to stop replaying HTTP traffic. * * @return {Base} * @method stopReplay */ Base.prototype.stopReplay = function () { this.replays.splice(0) return this } /** * Adds custom HTTP headers. * * @param {Object} headers * @return {Base} * @method headers */ Base.prototype.headers = function (headers) { this.use(middleware.headers(headers)) return this } /** * Adds query params. * * @param {Object} query * @return {Base} * @method query */ Base.prototype.query = function (query) { this.use(middleware.query(query)) return this } /** * Specifies a custom request timeout in miliseconds. Defaults to `30000`. * * @param {Number} ms * @return {Base} * @method timeout */ Base.prototype.timeout = function (ms) { this.opts.timeout = ms return this } /** * Waits and buffer all the request payload data buffer in heap before forwaring/replaing it. * Useful for intercepting and modifying payloads. * * @param {Function} filter * @return {Base} * @method bufferBody * @alias inteceptBody */ Base.prototype.bufferBody = Base.prototype.interceptBody = function (filter) { function pass (req, res, next) { next() } this.use(middleware.requestBody(pass, filter)) return this } /** * Attaches a custom middleware function to the given phase. * * @param {String} phase * @param {Function} middleware * @return {Base} * @method useFor */ Base.prototype.useFor = function () { this.mw.use.apply(this.mw, arguments) return this } /** * Attaches a custom middleware function to the forward incoming phase. * * @param {Function} middleware * @return {Base} * @method useForward */ Base.prototype.useForward = function () { this.useFor('forward', arguments) return this } /** * Attaches a custom middleware function to the replay incoming phase. * * @param {Function} middleware * @return {Base} * @method useReplay */ Base.prototype.useReplay = function () { this.useFor('replay', arguments) return this } /** * Attaches a custom middleware function to the outgoing phase. * * @param {Function} middleware * @return {Base} * @method useResponse * @alias useOutgoing */ Base.prototype.useOutgoing = Base.prototype.useResponse = function () { const stack = this.mw.stack('response') if (!stack || stack.length < 2) { this.use(middleware.responseBody(function dispatch (req, res, next) { // If body was already intercepted, just continue with it if (res._alreadyIntercepted) return next() // Flag the response as intercepted res._alreadyIntercepted = true // Run the response middleware this.mw.run('response', req, res, next) }.bind(this))) } this.useFor('response', arguments) return this }