UNPKG

nocca

Version:
318 lines (221 loc) 10 kB
'use strict'; var $extend = require('extend'); var $q = require('q'); module.exports = RequestContextFactory; function RequestContextFactory (Nocca) { var self = this; self.logger = Nocca.logger.child({ module: 'RequestContextFactory' }); var parentLogger = self.logger; self.createInstance = createInstance; function createInstance (httpRequest, httpResponse) { self.logger.info('---'); self.logger.info('>> incoming request: ' + httpRequest.url); var startTime = new Date().getTime(); // duplicate the options object to prevent manipulation of the main config var options = $extend({}, Nocca.config); return new RequestContext(httpRequest, httpResponse, startTime, options); } /** * * @param httpRequest the original HTTP Request coming into the server. * @param httpResponse the original HTTP Response going back to the client. * @param startTime the timestamp when this request context started processing. * @param config a copy of the configuration of Nocca (TODO: share a frozen copy between requests instead of new object). * @constructor constructs a new RequestContext which will be passed along the request throughout the etinre handler chain. */ function RequestContext (httpRequest, httpResponse, startTime, config) { var self = this; this.httpRequest = httpRequest; this.httpResponse = httpResponse; this.requestStartTime = startTime; this.config = config; this.start = start; // getters and setters so actual property doesn't matter this.getHttpMessage = getHttpMessage; this.setClientRequest = setClientRequest; this.getClientRequest = getClientRequest; this.setPlaybackResponse = setPlaybackResponse; this.getPlaybackResponse = getPlaybackResponse; this.setProxyRequest = setProxyRequest; this.getProxyRequest = getProxyRequest; this.setProxyResponse = setProxyResponse; this.getProxyResponse = getProxyResponse; this.setClientResponse = setClientResponse; this.getClientResponse = getClientResponse; this.generateKey = generateKey; this.replayIfDesired = replayIfDesired; this.forwardIfDesired = forwardIfDesired; this.recordIfDesired = recordIfDesired; this.errors = { throwNoEndpointError: throwNoEndpointError, throwForwardingFailedError: throwForwardingFailedError, throwRequestExtractionError: throwRequestExtractionError, throwNoClientResponseError: throwNoClientResponseError }; this.requests = { // as received by Nocca || incoming request clientReq: undefined, // as sent to endpoint by Nocca || outgoing request proxyReq: undefined, // as received by Nocca from endpoint || incoming response proxyRes: undefined, // as gathered from cache by Nocca || incoming (prerecorded) response playbackRes: undefined, // as provided to Nocca by Node from client || outgoing response clientRes: undefined }; /** * An implementation of an EndpointSelector must fill the endpoint property with the matched endpoint definition. * @type {undefined} */ this.endpoint = undefined; /** * An implementation of a KeyGenerator must fill the requestKey property with a string which identifies the incoming * request. * @type {undefined} */ this.requestKey = undefined; /** * An implementation of a RequestMatcher may fill in the playbackResponse property if it has determined a recorded * response should be returned to the client. * @type {undefined} */ this.playbackResponse = undefined; /** * Any component along the way may fill the statusCode, errorCode and errorMessage fields to indicate the request * could not be completed. Providing these will override returning any forwarded or pre-recorded response and will * write a response based on these properties. * @type {undefined} */ this.statusCode = undefined; function start () { var deferred = $q.defer(); // Allow the request chainer to set up a promise chain Nocca.requestChainer(deferred.promise, self); // Resolve the initial promise to kick off the chain deferred.resolve(self); } function getHttpMessage (httpMessageKey) { var httpMessage; switch (httpMessageKey) { case Nocca.constants.HTTP_MESSAGE_TYPES.CLIENT_REQUEST: { httpMessage = self.getClientRequest(); break; } case Nocca.constants.HTTP_MESSAGE_TYPES.PROXY_REQUEST: { httpMessage = self.getProxyRequest(); break; } case Nocca.constants.HTTP_MESSAGE_TYPES.PROXY_RESPONSE: { httpMessage = self.getProxyResponse(); break; } case Nocca.constants.HTTP_MESSAGE_TYPES.PLAYBACK_RESPONSE: { httpMessage = self.getPlaybackResponse(); break; } case Nocca.constants.HTTP_MESSAGE_TYPES.CLIENT_RESPONSE: { httpMessage = self.getClientResponse(); break; } default: { throw 'Unknown message type: ' + httpMessageKey; } } return httpMessage; } function setClientRequest (HttpMessage) { self.requests.clientReq = _transformIfDesired(HttpMessage, Nocca.constants.HTTP_MESSAGE_TYPES.CLIENT_REQUEST); } function getClientRequest () { return self.requests.clientReq; } function setPlaybackResponse (HttpMessage) { self.requests.playbackRes = _transformIfDesired(HttpMessage, Nocca.constants.HTTP_MESSAGE_TYPES.PLAYBACK_RESPONSE); } function getPlaybackResponse () { return self.requests.playbackRes; } function setProxyRequest (HttpMessage) { self.requests.proxyReq = _transformIfDesired(HttpMessage, Nocca.constants.HTTP_MESSAGE_TYPES.PROXY_REQUEST); } function getProxyRequest () { return self.requests.proxyReq; } function setProxyResponse (HttpMessage) { self.requests.proxyRes = _transformIfDesired(HttpMessage, Nocca.constants.HTTP_MESSAGE_TYPES.PROXY_RESPONSE); } function getProxyResponse () { return self.requests.proxyRes; } function setClientResponse (HttpMessage) { self.requests.clientRes = _transformIfDesired(HttpMessage, Nocca.constants.HTTP_MESSAGE_TYPES.CLIENT_RESPONSE); } function getClientResponse () { return self.requests.clientRes; } function _transformIfDesired (httpMessage, httpMessageKey) { if (typeof self.config.httpMessageTransformations !== 'undefined' && typeof self.config.httpMessageTransformations[httpMessageKey] !== 'undefined') { parentLogger.info('Transforming message with type ' + httpMessageKey); // transformation requested httpMessage = Nocca.usePlugin(self.config.httpMessageTransformations[httpMessageKey])(self, httpMessage); } return httpMessage; } function generateKey (reqContext) { return Nocca.usePlugin(reqContext.config.keyGenerator)(reqContext); } /** * Replaying should be performed when forwarding is set to none * (because if you're not forwarding, then you should be responding from cache, no?) * * Or when forwarding is set to missing * (because when there's a cache, there's no forwarding, so we should reply with the cache) */ function replayIfDesired (reqContext) { var deferred = $q.defer(); if (reqContext.config.playback) { deferred.resolve(Nocca.playback(reqContext)); } else { deferred.resolve(reqContext); } return deferred.promise; } function forwardIfDesired (reqContext) { var deferred = $q.defer(); if (reqContext.config.forward === Nocca.constants.FORWARDING_FORWARD_ALL || reqContext.config.forward === Nocca.constants.FORWARDING_FORWARD_MISSING) { // Forwarding is on, either for all requests, or just MISSING requests. deferred.resolve(Nocca.forwarder(reqContext)); } else { deferred.resolve(reqContext); } return deferred.promise; } function recordIfDesired (reqContext) { var deferred = $q.defer(); if (reqContext.config.record) { deferred.resolve(Nocca.recorder(reqContext)); } else { deferred.resolve(reqContext); } return deferred.promise; } function throwNoEndpointError () { throw 'No endpoint found'; } function throwForwardingFailedError () { throw 'Forwarding to endpoint failed'; } function throwRequestExtractionError () { throw 'Could not read the incoming request'; } function throwNoClientResponseError () { throw 'Could not respond to request because no playback or proxy response was defined'; } } }