UNPKG

replay

Version:

When API testing slows you down: record and replay HTTP responses like a boss

203 lines (160 loc) 6.37 kB
'use strict'; var _assign = require('babel-runtime/core-js/object/assign'); var _assign2 = _interopRequireDefault(_assign); var _setImmediate2 = require('babel-runtime/core-js/set-immediate'); var _setImmediate3 = _interopRequireDefault(_setImmediate2); var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // A proxy is a function that receives two arguments, a request object and a callback. // // If it can generate a respone, it calls callback with null and the response object. Otherwise, either calls callback // with no arguments, or with an error to stop the processing chain. // // The request consists of: // url - URL object // method - Request method (lower case) // headers - Headers object (names are lower case) // body - Request body, an array of body part/encoding pairs // // The response consists of: // version - HTTP version // status - Status code // headers - Headers object (names are lower case) // body - Array of body parts // trailers - Trailers object (names are lower case) // // This file defines ProxyRequest, which acts as an HTTP ClientRequest that captures the request and passes it to the // proxy chain, and ProxyResponse, which acts as an HTTP ClientResponse, playing back a response it received from the // proxy. // // No actual proxies defined here. const assert = require('assert'); var _require = require('events'); const EventEmitter = _require.EventEmitter; const HTTP = require('http'); const HTTPS = require('https'); const Stream = require('stream'); const URL = require('url'); // HTTP client request that captures the request and sends it down the processing chain. module.exports = class ProxyRequest extends HTTP.IncomingMessage { constructor(options = {}, proxy) { super(); this.proxy = proxy; this.method = (options.method || 'GET').toUpperCase(); const protocol = options.protocol || options._defaultAgent && options._defaultAgent.protocol || 'http:'; var _split = (options.host || options.hostname).split(':'), _split2 = (0, _slicedToArray3.default)(_split, 2); const host = _split2[0], port = _split2[1]; const realPort = options.port || port || (protocol === 'https:' ? 443 : 80); this.url = URL.parse(`${protocol}//${host || 'localhost'}:${realPort}${options.path || '/'}`, true); this.auth = options.auth; this.agent = options.agent || (protocol === 'https:' ? HTTPS.globalAgent : HTTP.globalAgent); this.cert = options.cert; this.key = options.key; this.headers = {}; if (options.headers) for (let name in options.headers) { let value = options.headers[name]; if (value != null) this.headers[name.toLowerCase()] = value.toString(); } } flushHeaders() {} setHeader(name, value) { assert(!this.ended, 'Already called end'); assert(!this.body, 'Already wrote body parts'); this.headers[name.toLowerCase()] = value; } getHeader(name) { return this.headers[name.toLowerCase()]; } removeHeader(name) { assert(!this.ended, 'Already called end'); assert(!this.body, 'Already wrote body parts'); delete this.headers[name.toLowerCase()]; } addTrailers(trailers) { this.trailers = trailers; } setTimeout(timeout, callback) { if (callback) (0, _setImmediate3.default)(callback); } setNoDelay() /*nodelay = true*/{} setSocketKeepAlive() /*enable = false, initial*/{} write(chunk, encoding, callback) { assert(!this.ended, 'Already called end'); this.body = this.body || []; this.body.push([chunk, encoding]); if (callback) (0, _setImmediate3.default)(callback); } end(data, encoding, callback) { assert(!this.ended, 'Already called end'); if (typeof data === 'function') { ; callback = data; data = null; } else if (typeof encoding === 'function') { ; callback = encoding; encoding = null; }if (data) { this.body = this.body || []; this.body.push([data, encoding]); } this.ended = true; if (callback) (0, _setImmediate3.default)(callback); this.proxy(this, (error, captured) => { // We're not asynchronous, but clients expect us to callback later on (0, _setImmediate3.default)(() => { if (error) this.emit('error', error);else if (captured) { const response = new ProxyResponse(captured); this.emit('response', response); response.resume(); } else { const error = new Error(`${this.method} ${URL.format(this.url)} refused: not recording and no network access`); error.code = 'ECONNREFUSED'; error.errno = 'ECONNREFUSED'; this.emit('error', error); } }); }); } flush() {} abort() {} }; // HTTP client response that plays back a captured response. class ProxyResponse extends Stream.Readable { constructor(captured) { super(); this.once('end', () => { this.emit('close'); }); this.httpVersion = captured.version || '1.1'; this.httpVersionMajor = this.httpVersion.split('.')[0]; this.httpVersionMinor = this.httpVersion.split('.')[1]; this.statusCode = parseInt(captured.statusCode || 200, 10); this.statusMessage = captured.statusMessage || HTTP.STATUS_CODES[this.statusCode] || ''; this.headers = (0, _assign2.default)({}, captured.headers); this.rawHeaders = captured.rawHeaders || [].slice(0); this.trailers = (0, _assign2.default)({}, captured.trailers); this.rawTrailers = (captured.rawTrailers || []).slice(0); // Not a documented property, but request seems to use this to look for HTTP parsing errors this.connection = new EventEmitter(); this._body = captured.body.slice(0); this.client = { authorized: true }; } _read() { const part = this._body.shift(); if (part) this.push(part[0], part[1]);else this.push(null); } setTimeout(msec, callback) { if (callback) (0, _setImmediate3.default)(callback); } static notFound(url) { return new ProxyResponse({ status: 404, body: [`No recorded request/response that matches ${URL.format(url)}`] }); } } //# sourceMappingURL=proxy.js.map