2 node-http-proxy.js: http proxy for node.js
4 Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Marak Squires, Fedor Indutny
6 Permission is hereby granted, free of charge, to any person obtaining
7 a copy of this software and associated documentation files (the
8 "Software"), to deal in the Software without restriction, including
9 without limitation the rights to use, copy, modify, merge, publish,
10 distribute, sublicense, and/or sell copies of the Software, and to
11 permit persons to whom the Software is furnished to do so, subject to
12 the following conditions:
14 The above copyright notice and this permission notice shall be
15 included in all copies or substantial portions of the Software.
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 var events = require('events'),
28 http = require('http'),
29 util = require('util'),
31 httpProxy = require('../node-http-proxy');
34 // @private {RegExp} extractPort
35 // Reusable regular expression for getting the
36 // port from a host string.
38 var extractPort = /:(\d+)$/;
41 // ### function HttpProxy (options)
42 // #### @options {Object} Options for this instance.
43 // Constructor function for new instances of HttpProxy responsible
44 // for managing the life-cycle of streaming reverse proxyied HTTP requests.
59 var HttpProxy = exports.HttpProxy = function (options) {
60 if (!options || !options.target) {
61 throw new Error('Both `options` and `options.target` are required.');
64 events.EventEmitter.call(this);
69 // Setup basic proxying options:
71 // * forward {Object} Options for a forward-proxy (if-any)
72 // * target {Object} Options for the **sole** proxy target of this instance
74 this.forward = options.forward;
75 this.target = options.target;
76 this.timeout = options.timeout;
79 // Setup the necessary instances instance variables for
80 // the `target` and `forward` `host:port` combinations
81 // used by this instance.
83 // * agent {http[s].Agent} Agent to be used by this instance.
84 // * protocol {http|https} Core node.js module to make requests with.
85 // * base {Object} Base object to create when proxying containing any https settings.
87 function setupProxy (key) {
88 self[key].agent = httpProxy._getAgent(self[key]);
89 self[key].protocol = httpProxy._getProtocol(self[key]);
90 self[key].base = httpProxy._getBase(self[key]);
95 setupProxy('forward');
99 // Setup opt-in features
101 this.enable = options.enable || {};
102 this.enable.xforward = typeof this.enable.xforward === 'boolean'
103 ? this.enable.xforward
106 // if event listener is set then use it else unlimited.
107 this.eventListenerCount = typeof options.eventListenerCount === 'number'? options.eventListenerCount : 0 ;
110 // Setup additional options for WebSocket proxying. When forcing
111 // the WebSocket handshake to change the `sec-websocket-location`
112 // and `sec-websocket-origin` headers `options.source` **MUST**
113 // be provided or the operation will fail with an `origin mismatch`
116 this.source = options.source || { host: 'localhost', port: 80 };
117 this.source.https = this.source.https || options.https;
118 this.changeOrigin = options.changeOrigin || false;
121 // Inherit from events.EventEmitter
122 util.inherits(HttpProxy, events.EventEmitter);
125 // ### function proxyRequest (req, res, buffer)
126 // #### @req {ServerRequest} Incoming HTTP Request to proxy.
127 // #### @res {ServerResponse} Outgoing HTTP Request to write proxied data to.
128 // #### @buffer {Object} Result from `httpProxy.buffer(req)`
130 HttpProxy.prototype.proxyRequest = function (req, res, buffer) {
133 outgoing = new(this.target.base),
137 // If this is a DELETE request then set the "content-length"
138 // header (if it is not already set)
139 if (req.method === 'DELETE') {
140 req.headers['content-length'] = req.headers['content-length'] || '0';
144 // Add common proxy headers to the request so that they can
145 // be availible to the proxy target server. If the proxy is
146 // part of proxy chain it will append the address:
148 // * `x-forwarded-for`: IP Address of the original request
149 // * `x-forwarded-proto`: Protocol of the original request
150 // * `x-forwarded-port`: Port of the original request.
152 if (this.enable.xforward && req.connection && req.socket) {
153 if (req.headers['x-forwarded-for']) {
154 var addressToAppend = "," + req.connection.remoteAddress || req.socket.remoteAddress;
155 req.headers['x-forwarded-for'] += addressToAppend;
158 req.headers['x-forwarded-for'] = req.connection.remoteAddress || req.socket.remoteAddress;
161 if (req.headers['x-forwarded-port']) {
162 var portToAppend = "," + getPortFromHostHeader(req);
163 req.headers['x-forwarded-port'] += portToAppend;
166 req.headers['x-forwarded-port'] = getPortFromHostHeader(req);
169 if (req.headers['x-forwarded-proto']) {
170 var protoToAppend = "," + getProto(req);
171 req.headers['x-forwarded-proto'] += protoToAppend;
174 req.headers['x-forwarded-proto'] = getProto(req);
179 req.socket.setTimeout(this.timeout);
183 // Emit the `start` event indicating that we have begun the proxy operation.
185 this.emit('start', req, res, this.target);
188 // If forwarding is enabled for this instance, foward proxy the
189 // specified request to the address provided in `this.forward`
192 this.emit('forward', req, res, this.forward);
193 this._forwardRequest(req);
197 // #### function proxyError (err)
198 // #### @err {Error} Error contacting the proxy target
199 // Short-circuits `res` in the event of any error when
200 // contacting the proxy target at `host` / `port`.
202 function proxyError(err) {
206 // Emit an `error` event, allowing the application to use custom
207 // error handling. The error handler should end the response.
209 if (self.emit('proxyError', err, req, res)) {
213 res.writeHead(500, { 'Content-Type': 'text/plain' });
215 if (req.method !== 'HEAD') {
217 // This NODE_ENV=production behavior is mimics Express and
220 if (process.env.NODE_ENV === 'production') {
221 res.write('Internal Server Error');
224 res.write('An error has occurred: ' + JSON.stringify(err));
229 catch (ex) { console.error("res.end error: %s", ex.message) }
233 // Setup outgoing proxy with relevant properties.
235 outgoing.host = this.target.host;
236 outgoing.hostname = this.target.hostname;
237 outgoing.port = this.target.port;
238 outgoing.socketPath = this.target.socketPath;
239 outgoing.agent = this.target.agent;
240 outgoing.method = req.method;
241 outgoing.path = url.parse(req.url).path;
242 outgoing.headers = req.headers;
245 // If the changeOrigin option is specified, change the
246 // origin of the host header to the target URL! Please
247 // don't revert this without documenting it!
249 if (this.changeOrigin) {
250 outgoing.headers.host = this.target.host;
251 // Only add port information to the header if not default port
252 // for this protocol.
253 // See https://github.com/nodejitsu/node-http-proxy/issues/458
254 if (this.target.port !== 443 && this.target.https ||
255 this.target.port !== 80 && !this.target.https) {
256 outgoing.headers.host += ':' + this.target.port;
261 // Open new HTTP request to internal resource with will act
262 // as a reverse proxy pass
264 reverseProxy = this.target.protocol.request(outgoing, function (response) {
266 // Process the `reverseProxy` `response` when it's received.
268 if (req.httpVersion === '1.0') {
269 if (req.headers.connection) {
270 response.headers.connection = req.headers.connection
272 response.headers.connection = 'close'
274 } else if (!response.headers.connection) {
275 if (req.headers.connection) { response.headers.connection = req.headers.connection }
277 response.headers.connection = 'keep-alive'
281 // Remove `Transfer-Encoding` header if client's protocol is HTTP/1.0
282 // or if this is a DELETE request with no content-length header.
283 // See: https://github.com/nodejitsu/node-http-proxy/pull/373
284 if (req.httpVersion === '1.0' || (req.method === 'DELETE'
285 && !req.headers['content-length'])) {
286 delete response.headers['transfer-encoding'];
289 if ((response.statusCode === 301 || response.statusCode === 302)
290 && typeof response.headers.location !== 'undefined') {
291 location = url.parse(response.headers.location);
292 if (location.host === req.headers.host) {
293 if (self.source.https && !self.target.https) {
294 response.headers.location = response.headers.location.replace(/^http\:/, 'https:');
296 if (self.target.https && !self.source.https) {
297 response.headers.location = response.headers.location.replace(/^https\:/, 'http:');
303 // When the `reverseProxy` `response` ends, end the
304 // corresponding outgoing `res` unless we have entered
305 // an error state. In which case, assume `res.end()` has
306 // already been called and the 'error' event listener
310 response.on('close', function () {
311 if (!ended) { response.emit('end') }
315 // After reading a chunked response, the underlying socket
316 // will hit EOF and emit a 'end' event, which will abort
317 // the request. If the socket was paused at that time,
318 // pending data gets discarded, truncating the response.
319 // This code makes sure that we flush pending data.
321 response.connection.on('end', function () {
322 if (response.readable && response.resume) {
327 response.on('end', function () {
331 catch (ex) { console.error("res.end error: %s", ex.message) }
333 // Emit the `end` event now that we have completed proxying
334 self.emit('end', req, res, response);
338 // Allow observer to modify headers or abort response
339 try { self.emit('proxyResponse', req, res, response) }
345 // Set the headers of the client response
346 if (res.sentHeaders !== true) {
347 Object.keys(response.headers).forEach(function (key) {
348 res.setHeader(key, response.headers[key]);
350 res.writeHead(response.statusCode);
353 function ondata(chunk) {
355 // Only pause if the underlying buffers are full,
356 // *and* the connection is not in 'closing' state.
357 // Otherwise, the pause will cause pending data to
358 // be discarded and silently lost.
359 if (false === res.write(chunk) && response.pause
360 && response.connection.readable) {
366 response.on('data', ondata);
369 if (response.readable && response.resume) {
374 res.on('drain', ondrain);
377 // allow unlimited listeners ...
378 reverseProxy.setMaxListeners(this.eventListenerCount);
381 // Handle 'error' events from the `reverseProxy`. Setup timeout override if needed
383 reverseProxy.once('error', proxyError);
385 // Set a timeout on the socket if `this.timeout` is specified.
386 reverseProxy.once('socket', function (socket) {
388 socket.setTimeout(self.timeout);
393 // Handle 'error' events from the `req` (e.g. `Parse Error`).
395 req.on('error', proxyError);
398 // If `req` is aborted, we abort our `reverseProxy` request as well.
400 req.on('aborted', function () {
401 reverseProxy.abort();
405 // For each data `chunk` received from the incoming
406 // `req` write it to the `reverseProxy` request.
408 req.on('data', function (chunk) {
410 var flushed = reverseProxy.write(chunk);
413 reverseProxy.once('drain', function () {
415 catch (er) { console.error("req.resume error: %s", er.message) }
419 // Force the `drain` event in 100ms if it hasn't
420 // happened on its own.
422 setTimeout(function () {
423 reverseProxy.emit('drain');
430 // When the incoming `req` ends, end the corresponding `reverseProxy`
431 // request unless we have entered an error state.
433 req.on('end', function () {
439 //Aborts reverseProxy if client aborts the connection.
440 req.on('close', function () {
442 reverseProxy.abort();
447 // If we have been passed buffered data, resume it.
457 // ### function proxyWebSocketRequest (req, socket, head, buffer)
458 // #### @req {ServerRequest} Websocket request to proxy.
459 // #### @socket {net.Socket} Socket for the underlying HTTP request
460 // #### @head {string} Headers for the Websocket request.
461 // #### @buffer {Object} Result from `httpProxy.buffer(req)`
462 // Performs a WebSocket proxy operation to the location specified by
465 HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, upgradeHead, buffer) {
467 outgoing = new(this.target.base),
471 //copy upgradeHead to avoid retention of large slab buffers used in node core
472 head = new Buffer(upgradeHead.length);
473 upgradeHead.copy(head);
476 // WebSocket requests must have the `GET` method and
477 // the `upgrade:websocket` header
479 if (req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket') {
481 // This request is not WebSocket request
483 return socket.destroy();
487 // Add common proxy headers to the request so that they can
488 // be availible to the proxy target server. If the proxy is
489 // part of proxy chain it will append the address:
491 // * `x-forwarded-for`: IP Address of the original request
492 // * `x-forwarded-proto`: Protocol of the original request
493 // * `x-forwarded-port`: Port of the original request.
495 if (this.enable.xforward && req.connection) {
496 if (req.headers['x-forwarded-for']) {
497 var addressToAppend = "," + req.connection.remoteAddress || socket.remoteAddress;
498 req.headers['x-forwarded-for'] += addressToAppend;
501 req.headers['x-forwarded-for'] = req.connection.remoteAddress || socket.remoteAddress;
504 if (req.headers['x-forwarded-port']) {
505 var portToAppend = "," + getPortFromHostHeader(req);
506 req.headers['x-forwarded-port'] += portToAppend;
509 req.headers['x-forwarded-port'] = getPortFromHostHeader(req);
512 if (req.headers['x-forwarded-proto']) {
513 var protoToAppend = "," + (req.connection.pair ? 'wss' : 'ws');
514 req.headers['x-forwarded-proto'] += protoToAppend;
517 req.headers['x-forwarded-proto'] = req.connection.pair ? 'wss' : 'ws';
521 self.emit('websocket:start', req, socket, head, this.target);
524 // Helper function for setting appropriate socket values:
525 // 1. Turn of all bufferings
526 // 2. For server set KeepAlive
528 function _socket(socket, keepAlive) {
529 socket.setTimeout(0);
530 socket.setNoDelay(true);
533 if (socket.setKeepAlive) {
534 socket.setKeepAlive(true, 0);
536 else if (socket.pair.cleartext.socket.setKeepAlive) {
537 socket.pair.cleartext.socket.setKeepAlive(true, 0);
543 // Setup the incoming client socket.
545 _socket(socket, true);
548 // On `upgrade` from the Agent socket, listen to
549 // the appropriate events.
551 function onUpgrade (reverseProxy, proxySocket) {
559 // Any incoming data on this WebSocket to the proxy target
560 // will be written to the `reverseProxy` socket.
562 proxySocket.on('data', listeners.onIncoming = function (data) {
563 if (reverseProxy.incoming.socket.writable) {
565 self.emit('websocket:outgoing', req, socket, head, data);
566 var flushed = reverseProxy.incoming.socket.write(data);
569 reverseProxy.incoming.socket.once('drain', function () {
570 try { proxySocket.resume() }
571 catch (er) { console.error("proxySocket.resume error: %s", er.message) }
575 // Force the `drain` event in 100ms if it hasn't
576 // happened on its own.
578 setTimeout(function () {
579 reverseProxy.incoming.socket.emit('drain');
590 // Any outgoing data on this Websocket from the proxy target
591 // will be written to the `proxySocket` socket.
593 reverseProxy.incoming.socket.on('data', listeners.onOutgoing = function (data) {
595 self.emit('websocket:incoming', reverseProxy, reverseProxy.incoming, head, data);
596 var flushed = proxySocket.write(data);
598 reverseProxy.incoming.socket.pause();
599 proxySocket.once('drain', function () {
600 try { reverseProxy.incoming.socket.resume() }
601 catch (er) { console.error("reverseProxy.incoming.socket.resume error: %s", er.message) }
605 // Force the `drain` event in 100ms if it hasn't
606 // happened on its own.
608 setTimeout(function () {
609 proxySocket.emit('drain');
619 // Helper function to detach all event listeners
620 // from `reverseProxy` and `proxySocket`.
623 proxySocket.destroySoon();
624 proxySocket.removeListener('end', listeners.onIncomingClose);
625 proxySocket.removeListener('data', listeners.onIncoming);
626 reverseProxy.incoming.socket.destroySoon();
627 reverseProxy.incoming.socket.removeListener('end', listeners.onOutgoingClose);
628 reverseProxy.incoming.socket.removeListener('data', listeners.onOutgoing);
632 // If the incoming `proxySocket` socket closes, then
633 // detach all event listeners.
635 listeners.onIncomingClose = function () {
636 reverseProxy.incoming.socket.destroy();
639 // Emit the `end` event now that we have completed proxying
640 self.emit('websocket:end', req, socket, head);
644 // If the `reverseProxy` socket closes, then detach all
647 listeners.onOutgoingClose = function () {
648 proxySocket.destroy();
652 proxySocket.on('end', listeners.onIncomingClose);
653 proxySocket.on('close', listeners.onIncomingClose);
654 reverseProxy.incoming.socket.on('end', listeners.onOutgoingClose);
655 reverseProxy.incoming.socket.on('close', listeners.onOutgoingClose);
658 function getPort (port) {
660 return port - 80 === 0 ? '' : ':' + port;
664 // Get the protocol, and host for this request and create an instance
665 // of `http.Agent` or `https.Agent` from the pool managed by `node-http-proxy`.
667 var agent = this.target.agent,
668 protocolName = this.target.https ? 'https' : 'http',
669 portUri = getPort(this.source.port),
670 remoteHost = this.target.host + portUri;
673 // Change headers (if requested).
675 if (this.changeOrigin) {
676 req.headers.host = remoteHost;
677 req.headers.origin = protocolName + '://' + remoteHost;
681 // Make the outgoing WebSocket request
683 outgoing.host = this.target.host;
684 outgoing.port = this.target.port;
685 outgoing.agent = agent;
686 outgoing.method = 'GET';
687 outgoing.path = req.url;
688 outgoing.headers = req.headers;
689 outgoing.agent = agent;
691 var reverseProxy = this.target.protocol.request(outgoing);
694 // On any errors from the `reverseProxy` emit the
695 // `webSocketProxyError` and close the appropriate
698 function proxyError (err) {
699 reverseProxy.destroy();
701 process.nextTick(function () {
703 // Destroy the incoming socket in the next tick, in case the error handler
704 // wants to write to it.
709 self.emit('webSocketProxyError', err, req, socket, head);
713 // Here we set the incoming `req`, `socket` and `head` data to the outgoing
714 // request so that we can reuse this data later on in the closure scope
715 // available to the `upgrade` event. This bookkeeping is not tracked anywhere
716 // in nodejs core and is **very** specific to proxying WebSockets.
718 reverseProxy.incoming = {
725 // Here we set the handshake `headers` and `statusCode` data to the outgoing
726 // request so that we can reuse this data later.
728 reverseProxy.handshake = {
734 // If the agent for this particular `host` and `port` combination
735 // is not already listening for the `upgrade` event, then do so once.
736 // This will force us not to disconnect.
738 // In addition, it's important to note the closure scope here. Since
739 // there is no mapping of the socket to the request bound to it.
741 reverseProxy.on('upgrade', function (res, remoteSocket, head) {
743 // Prepare handshake response 'headers' and 'statusCode'.
745 reverseProxy.handshake = {
746 headers: res.headers,
747 statusCode: res.statusCode,
751 // Prepare the socket for the reverseProxy request and begin to
752 // stream data between the two sockets. Here it is important to
753 // note that `remoteSocket._httpMessage === reverseProxy`.
755 _socket(remoteSocket, true);
756 onUpgrade(remoteSocket._httpMessage, remoteSocket);
760 // If the reverseProxy connection has an underlying socket,
761 // then execute the WebSocket handshake.
763 reverseProxy.once('socket', function (revSocket) {
764 revSocket.on('data', function handshake (data) {
769 // If the handshake statusCode 101, concat headers.
771 if (reverseProxy.handshake.statusCode && reverseProxy.handshake.statusCode == 101) {
773 'HTTP/1.1 101 Switching Protocols',
774 'Upgrade: websocket',
775 'Connection: Upgrade',
776 'Sec-WebSocket-Accept: ' + reverseProxy.handshake.headers['sec-websocket-accept']
779 headers = headers.concat('', '').join('\r\n');
783 // Ok, kind of harmfull part of code. Socket.IO sends a hash
784 // at the end of handshake if protocol === 76, but we need
785 // to replace 'host' and 'origin' in response so we split
786 // data to printable data and to non-printable. (Non-printable
787 // will come after double-CRLF).
789 var sdata = data.toString();
791 // Get the Printable data
792 sdata = sdata.substr(0, sdata.search(CRLF + CRLF));
794 // Get the Non-Printable data
795 data = data.slice(Buffer.byteLength(sdata), data.length);
797 if (self.source.https && !self.target.https) {
799 // If the proxy server is running HTTPS but the client is running
800 // HTTP then replace `ws` with `wss` in the data sent back to the client.
802 sdata = sdata.replace('ws:', 'wss:');
807 // Write the printable and non-printable data to the socket
808 // from the original incoming request.
810 self.emit('websocket:handshake', req, socket, head, sdata, data);
811 // add headers to the socket
812 socket.write(headers + sdata);
813 var flushed = socket.write(data);
816 socket.once('drain', function () {
817 try { revSocket.resume() }
818 catch (er) { console.error("reverseProxy.socket.resume error: %s", er.message) }
822 // Force the `drain` event in 100ms if it hasn't
823 // happened on its own.
825 setTimeout(function () {
826 socket.emit('drain');
832 // Remove data listener on socket error because the
833 // 'handshake' has failed.
835 revSocket.removeListener('data', handshake);
836 return proxyError(ex);
840 // Remove data listener now that the 'handshake' is complete
842 revSocket.removeListener('data', handshake);
847 // Handle 'error' events from the `reverseProxy`.
849 reverseProxy.on('error', proxyError);
852 // Handle 'error' events from the `req` (e.g. `Parse Error`).
854 req.on('error', proxyError);
858 // Attempt to write the upgrade-head to the reverseProxy
859 // request. This is small, and there's only ever one of
860 // it; no need for pause/resume.
862 // XXX This is very wrong and should be fixed in node's core
864 reverseProxy.write(head);
865 if (head && head.length === 0) {
866 reverseProxy._send('');
870 return proxyError(ex);
874 // If we have been passed buffered data, resume it.
884 // ### function close()
885 // Closes all sockets associated with the Agents
886 // belonging to this instance.
888 HttpProxy.prototype.close = function () {
889 [this.forward, this.target].forEach(function (proxy) {
890 if (proxy && proxy.agent) {
891 for (var host in proxy.agent.sockets) {
892 proxy.agent.sockets[host].forEach(function (socket) {
901 // ### @private function _forwardRequest (req)
902 // #### @req {ServerRequest} Incoming HTTP Request to proxy.
903 // Forwards the specified `req` to the location specified
904 // by `this.forward` ignoring errors and the subsequent response.
906 HttpProxy.prototype._forwardRequest = function (req) {
908 outgoing = new(this.forward.base),
912 // Setup outgoing proxy with relevant properties.
914 outgoing.host = this.forward.host;
915 outgoing.port = this.forward.port,
916 outgoing.agent = this.forward.agent;
917 outgoing.method = req.method;
918 outgoing.path = req.url;
919 outgoing.headers = req.headers;
922 // Open new HTTP request to internal resource with will
923 // act as a reverse proxy pass.
925 forwardProxy = this.forward.protocol.request(outgoing, function (response) {
927 // Ignore the response from the forward proxy since this is a 'fire-and-forget' proxy.
928 // Remark (indexzero): We will eventually emit a 'forward' event here for performance tuning.
933 // Add a listener for the connection timeout event.
935 // Remark: Ignoring this error in the event
936 // forward target doesn't exist.
938 forwardProxy.once('error', function (err) { });
941 // Chunk the client request body as chunks from
942 // the proxied request come in
944 req.on('data', function (chunk) {
945 var flushed = forwardProxy.write(chunk);
948 forwardProxy.once('drain', function () {
950 catch (er) { console.error("req.resume error: %s", er.message) }
954 // Force the `drain` event in 100ms if it hasn't
955 // happened on its own.
957 setTimeout(function () {
958 forwardProxy.emit('drain');
964 // At the end of the client request, we are going to
965 // stop the proxied request
967 req.on('end', function () {
972 function getPortFromHostHeader(req) {
974 if ((match = extractPort.exec(req.headers.host))) {
975 return parseInt(match[1]);
978 return getProto(req) === 'https' ? 443 : 80;
981 function getProto(req) {
982 return req.isSpdy ? 'https' : (req.connection.pair ? 'https' : 'http');