UNPKG

yakbak-enhanced

Version:

Record and playback HTTP responses

124 lines (103 loc) 3.74 kB
// Copyright 2019 SmugMug, Inc. // Licensed under the terms of the MIT license. Please see LICENSE file in the project root for terms. var Promise = require('bluebird'); var messageHash = require('incoming-message-hash'); var assert = require('assert'); var mkdirp = require('mkdirp'); var path = require('path'); var buffer = require('./lib/buffer'); var proxy = require('./lib/proxy'); var record = require('./lib/record'); var curl = require('./lib/curl'); var debug = require('debug')('yakbak:server'); var fs = require('fs'); /** * Returns a new yakbak proxy middleware. * @param {String} host The hostname to proxy to * @param {Object} opts * @param {String} opts.dirname The tapes directory * @param {Boolean} opts.noRecord if true, requests will return a 404 error if the tape doesn't exist * @param {Boolean} opts.recordOnlySuccess if true, only successful requests will be recorded * @param {Boolean} opts.override if true, the filename will always be overridden * @returns {Function} */ module.exports = function (host, opts) { assert(opts.dirname, 'You must provide opts.dirname'); return function (req, res) { mkdirp.sync(opts.dirname); debug('req', req.url); return buffer(req).then(function (body) { var file = path.join(opts.dirname, tapename(req, body)); var successfulResCodePattern = /^[2][0|2][0-8]$/; return Promise.try(function () { if (opts.override === true) { try { fs.deleteSync(file); } catch (e) { // no file found } var err = new Error('File removed'); err.code = 'MODULE_NOT_FOUND'; throw err; } else { return require.resolve(file); } }).catch(ModuleNotFoundError, function (/* err */) { if (opts.noRecord) { throw new RecordingDisabledError('Recording Disabled'); } else { return proxy(req, body, host).then(function (pres) { if (opts.recordOnlySuccess === true) { if (successfulResCodePattern.test(pres.statusCode)) { return record(pres.req, pres, file); } else { throw new RecordingDisabledError('Only Successful responses will be recorded'); } } else { return record(pres.req, pres, file); } }); } }); }).then(function (file) { return require(file); }).then(function (tape) { return tape(req, res); }).catch(RecordingDisabledError, function (err) { /* eslint-disable no-console */ console.log(err.message); console.log(curl.request(req)); /* eslint-enable no-console */ res.statusCode = err.status; res.end(err.message); }); }; /** * Returns the tape name for `req`. * @param {http.IncomingMessage} req * @param {Array.<Buffer>} body * @returns {String} */ function tapename(req, body) { var hash = opts.hash || messageHash.sync; return hash(req, Buffer.concat(body)) + '.js'; } }; /** * Bluebird error predicate for matching module not found errors. * @param {Error} err * @returns {Boolean} */ function ModuleNotFoundError(err) { return err.code === 'MODULE_NOT_FOUND'; } /** * Error class that is thrown when an unmatched request * is encountered in noRecord mode or when a request failed in recordOnlySuccess mode * @constructor */ function RecordingDisabledError(message) { this.message = message; this.status = 404; } RecordingDisabledError.prototype = Object.create(Error.prototype);