UNPKG

replay

Version:

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

144 lines (119 loc) 4.11 kB
# 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. assert = require("assert") HTTP = require("http") Stream = require("stream") URL = require("url") # HTTP client request that captures the request and sends it down the processing chain. class ProxyRequest extends HTTP.ClientRequest constructor: (options = {}, @proxy)-> @method = (options.method || "GET").toUpperCase() [host, port] = (options.host || options.hostname).split(":") @url = URL.parse("http://#{host || "localhost"}:#{options.port || port || 80}#{options.path || "/"}") @headers = {} if options.headers for n,v of options.headers @headers[n.toLowerCase()] = v setHeader: (name, value)-> assert !@ended, "Already called end" assert !@body, "Already wrote body parts" @headers[name.toLowerCase()] = value getHeader: (name)-> return @headers[name.toLowerCase()] removeHeader: (name)-> assert !@ended, "Already called end" assert !@body, "Already wrote body parts" delete @headers[name.toLowerCase()] setTimeout: (timeout, callback)-> @timeout = [timeout, callback] return setNoDelay: (nodelay = true)-> @nodelay = [nodelay] return setSocketKeepAlive: (enable = false, initial)-> @keepAlive = [enable, initial] return write: (chunk, encoding)-> assert !@ended, "Already called end" @body ||= [] @body.push [chunk, encoding] return end: (data, encoding)-> assert !@ended, "Already called end" if data @write data, encoding @ended = true @proxy this, (error, captured)=> process.nextTick => if error @emit "error", error else if captured response = new ProxyResponse(captured) @emit "response", response process.nextTick -> response.resume() else error = new Error("#{@method} #{URL.format(@url)} refused: not recording and no network access") error.code = "ECONNREFUSED" error.errno = "ECONNREFUSED" @emit "error", error return abort: -> clone = (object)-> result = {} for x, y of object result[x] = y return result # HTTP client response that plays back a captured response. class ProxyResponse extends Stream constructor: (captured)-> @httpVersion = captured.version || "1.1" @statusCode = captured.status || "200" @headers = clone(captured.headers) @trailers = clone(captured.trailers) @_body = captured.body.slice(0) @readable = true pause: -> @_paused = true resume: -> @_paused = false process.nextTick => return if @_paused || !@_body part = @_body.shift() if part if @_encoding chunk = new Buffer(part).toString(@_encoding) else chunk = part @emit "data", chunk @resume() else @_body = null @readable = false @_done = true @emit "end" setEncoding: (@_encoding)-> @notFound: (url)-> return new ProxyResponse(status: 404, body: ["No recorded request/response that matches #{URL.format(url)}"]) module.exports = ProxyRequest