UNPKG

rocky

Version:

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

198 lines (158 loc) 4.89 kB
const http = require('http') const https = require('https') const parseUrl = require('url').parse const assign = require('lodash').assign const retry = require('../retry') const ResponseStub = require('../response') const helpers = require('../../../helpers') const setupReq = require('http-proxy/lib/http-proxy/common').setupOutgoing module.exports = function replay (route, opts, req, res, next) { const replays = getReplays(route, req) if (!replays.length) return next() const targets = normalize(replays, opts) const replayer = replayRequest(route, req) useReplayStrategy(targets, opts, replayer, next) } function useReplayStrategy (targets, opts, replayer, done) { if (opts.replaySequentially) { helpers.eachSeries(targets, replayer, done) } else { helpers.eachConcurrently(targets, replayer, done) } } function getReplays (route, req) { const opts = req.rocky.options if (opts && Array.isArray(opts.replays) && opts.replays.length) { return opts.replays } if (route.replays && route.replays.length) { return route.replays } const replays = req.rocky.proxy.replays if (replays) { return replays } return [] } function replayRequest (route, req) { return function (opts, next) { next = typeof next === 'function' ? next : noop // Clone the request/response to avoid side-effects const replayRes = new ResponseStub() const replayReq = helpers.cloneRequest(req, opts) replayReq.rocky.isReplay = true // Dispatch replay middleware per each request const doReplay = replayer(route, replayReq, replayRes, opts, next) route.mw.run('replay', replayReq, replayRes, doReplay) } } function replayer (route, replayReq, replayRes, opts, next) { return function doReplay (err) { if (err) { return route.emit('replay:error', err, replayReq, replayRes) } if (replayReq.stopReplay === true) { return route.emit('replay:stop', err, replayReq, replayRes) } const params = setRequestParams(replayReq, opts) route.emit('replay:start', params, opts, replayReq) const handler = replayHandler(route, params, opts, replayReq, next) if (opts.retry) { retryRequest(route, params, opts, replayReq, replayRes, handler) } else { request(params, opts, replayReq, handler) } } } function replayHandler (route, params, opts, req, next) { return function (err) { if (err) { route.emit('replay:error', err, req) return next(err) } route.emit('replay:end', params, opts, req) next() } } function setRequestParams (req, opts) { const target = req.rocky.options.target || opts.target const url = parseUrl(target) const params = setupReq(opts.ssl || {}, opts, req) params.hostname = url.hostname params.port = url.port const forwardHost = opts.forwardHost if (typeof forwardHost === 'string') { params.headers.host = forwardHost } else if (forwardHost) { params.headers.host = url.host } if ((req.method === 'DELETE' || req.method === 'OPTIONS') && !req.headers['content-length']) { params.headers['content-length'] = '0' } if (opts.replayOriginalBody && req._originalBodyLength) { params.headers['content-length'] = req._originalBodyLength } return params } function retryRequest (route, params, opts, req, res, handler) { function task (cb) { if (res.headersSent) return cb() request(params, opts, req, cb) } function onRetry (err, res) { route.emit('replay:retry', err, req, res) } retry(opts.retry, res, task, handler, onRetry) } function request (params, opts, req, done) { const request = httpModule(opts).request(params) const timeout = +opts.proxyTimeout || +opts.timeout if (timeout) request.setTimeout(timeout) var response = null request.once('response', function (res) { response = res }) request.on('error', function (err) { done(err) clean(request) }) request.on('close', function () { done(clean(request), response) response = null }) // Write the property data in the request body const body = opts.replayOriginalBody ? req._originalBody : req.body if (!body) { return req.pipe(request) } request.write(body, req.body._newBodyEncoding) request.end() } function normalize (replays, opts) { return replays .filter(function (replay) { return replay }) .map(function (replay) { if (typeof replay === 'string') { replay = { target: replay } } return replay }) .map(function (replay) { return assign({}, opts, replay) }) } function clean (request) { request.removeAllListeners('error') request.removeAllListeners('response') request.removeAllListeners('close') } function httpModule (opts) { return opts.target.indexOf('https://') === 0 ? https : http } function noop () {}