diff --git a/lib/cors-anywhere.js b/lib/cors-anywhere.js index ebd12b6..b7b45c1 100644 --- a/lib/cors-anywhere.js +++ b/lib/cors-anywhere.js @@ -84,12 +84,9 @@ function isForbidden(host) { * @param req {ServerRequest} Incoming http request * @param res {ServerResponse} Outgoing (proxied) http request * @param proxy {HttpProxy} - * @param location {object} See parseURL - * @param requestState.proxyBaseUrl {string} Base URL of the CORS API endpoint. - * @param requestState.maxRedirects {number} Maximum number of redirects - * @param requestState.redirectCount_ {number} Internally used to count redirects */ -function proxyRequest(req, res, proxy, location, requestState) { +function proxyRequest(req, res, proxy) { + var location = req.corsAnywhereRequestState.location; if (isForbidden(location.hostname)) { res.writeHead(403, 'Refused to visit', withCORS({'Location': location.full_url}, req)); return; @@ -99,42 +96,6 @@ function proxyRequest(req, res, proxy, location, requestState) { // Let the "Host" header be the host part of the path (including port, if specified). req.headers.host = location.host; - // "Allow observer to modify headers or abort response" - // https://github.com/nodejitsu/node-http-proxy/blob/ebbba73e/lib/node-http-proxy/http-proxy.js#L321-L322 - proxy.once('proxyResponse', function(req, res, response) { - - var statusCode = response.statusCode; - // Handle redirects - if (statusCode === 301 || statusCode === 302 || statusCode === 303 || statusCode === 307 || statusCode === 308) { - var locationHeader = response.headers['location']; - if (locationHeader) { - locationHeader = url.resolve(location.full_url, locationHeader); - - if (statusCode === 301 || statusCode === 302 || statusCode === 303) { - // Exclude 307 & 308, because they are rare, and require preserving the method + request body - requestState.redirectCount_ = requestState.requestState_ + 1 || 1; - if (requestState.redirectCount_ <= requestState.maxRedirects) { - req.method = 'GET'; - proxyRequest(req, res, proxy, parseURL(locationHeader), requestState); - - response.end(); - // The proxyResponse event is wrapped in a try-catch, throwing an error - // prevents the response from being passed to the client. - throw new Error('Prevent current response from being passed through.'); - } - } - response.headers['location'] = requestState.proxyBaseUrl + '/' + locationHeader; - } - } - withCORS(response.headers, req); - - // Don't slip through cookies - delete response.headers['set-cookie']; - delete response.headers['set-cookie2']; - - response.headers['x-request-url'] = location.full_url; - }); - // Start proxying the request proxy.proxyRequest(req, res, { host: location.hostname, @@ -143,9 +104,67 @@ function proxyRequest(req, res, proxy, location, requestState) { https: location.isHttps } }); - if (requestState.redirectCount_) { - req.emit('end'); +} + + +/** + * "Allow observer to modify headers or abort response" + * https://github.com/nodejitsu/node-http-proxy/blob/ebbba73e/lib/node-http-proxy/http-proxy.js#L321-L322 + * + * This method modifies the response headers of the proxied response. + * If a redirect is detected, the response is not sent to the client, + * and a new request is initiated. + * + * @param req {IncomingMessage} Incoming HTTP request, augmented with property corsAnywhereRequestState + * @param req.corsAnywhereRequestState {object} + * @param req.corsAnywhereRequestState.location {object} See parseURL + * @param req.corsAnywhereRequestState.proxyBaseUrl {string} Base URL of the CORS API endpoint + * @param req.corsAnywhereRequestState.maxRedirects {number} Maximum number of redirects + * @param req.corsAnywhereRequestState.redirectCount_ {number} Internally used to count redirects + * @param res {ServerResponse} Outgoing (proxied) HTTP request + * @param response {ClientRequest} The + * + * @this {HttpProxy} + */ +function onProxyResponse(req, res, response) { + /* jshint validthis:true */ + var proxy = this; + var requestState = req.corsAnywhereRequestState; + + var statusCode = response.statusCode; + // Handle redirects + if (statusCode === 301 || statusCode === 302 || statusCode === 303 || statusCode === 307 || statusCode === 308) { + var locationHeader = response.headers['location']; + if (locationHeader) { + locationHeader = url.resolve(requestState.location.full_url, locationHeader); + + if (statusCode === 301 || statusCode === 302 || statusCode === 303) { + // Exclude 307 & 308, because they are rare, and require preserving the method + request body + requestState.redirectCount_ = requestState.requestState_ + 1 || 1; + if (requestState.redirectCount_ <= requestState.maxRedirects) { + req.method = 'GET'; + requestState.location = parseURL(locationHeader); + proxyRequest(req, res, proxy); + // TODO: Is it possible to trigger reverseProxy.end() (from node-http-proxy) without + // manually emiting the "event" event on req? + req.emit('end'); + + response.end(); + // The proxyResponse event is wrapped in a try-catch, throwing an error + // prevents the response from being passed to the client. + throw new Error('Prevent current response from being passed through.'); + } + } + response.headers['location'] = requestState.proxyBaseUrl + '/' + locationHeader; + } } + withCORS(response.headers, req); + + // Don't slip through cookies + delete response.headers['set-cookie']; + delete response.headers['set-cookie2']; + + response.headers['x-request-url'] = requestState.location.full_url; } @@ -265,10 +284,19 @@ var getHandler = exports.getHandler = function(options) { delete req.headers[header]; }); - proxyRequest(req, res, proxy, location, { + + req.corsAnywhereRequestState = { + location: location, maxRedirects: corsAnywhere.maxRedirects, proxyBaseUrl: proxyBaseUrl - }); + }; + + if (!proxy.hasCorsAnywhereResponseHandler) { // Runs once per HttpProxy instance + proxy.on('proxyResponse', onProxyResponse); + proxy.hasCorsAnywhereResponseHandler = true; + } + + proxyRequest(req, res, proxy); }; };