1 | var httpNative = require('http'),
|
---|
2 | httpsNative = require('https'),
|
---|
3 | web_o = require('./web-outgoing'),
|
---|
4 | common = require('../common'),
|
---|
5 | followRedirects = require('follow-redirects');
|
---|
6 |
|
---|
7 | web_o = Object.keys(web_o).map(function(pass) {
|
---|
8 | return web_o[pass];
|
---|
9 | });
|
---|
10 |
|
---|
11 | var nativeAgents = { http: httpNative, https: httpsNative };
|
---|
12 |
|
---|
13 | /*!
|
---|
14 | * Array of passes.
|
---|
15 | *
|
---|
16 | * A `pass` is just a function that is executed on `req, res, options`
|
---|
17 | * so that you can easily add new checks while still keeping the base
|
---|
18 | * flexible.
|
---|
19 | */
|
---|
20 |
|
---|
21 |
|
---|
22 | module.exports = {
|
---|
23 |
|
---|
24 | /**
|
---|
25 | * Sets `content-length` to '0' if request is of DELETE type.
|
---|
26 | *
|
---|
27 | * @param {ClientRequest} Req Request object
|
---|
28 | * @param {IncomingMessage} Res Response object
|
---|
29 | * @param {Object} Options Config object passed to the proxy
|
---|
30 | *
|
---|
31 | * @api private
|
---|
32 | */
|
---|
33 |
|
---|
34 | deleteLength: function deleteLength(req, res, options) {
|
---|
35 | if((req.method === 'DELETE' || req.method === 'OPTIONS')
|
---|
36 | && !req.headers['content-length']) {
|
---|
37 | req.headers['content-length'] = '0';
|
---|
38 | delete req.headers['transfer-encoding'];
|
---|
39 | }
|
---|
40 | },
|
---|
41 |
|
---|
42 | /**
|
---|
43 | * Sets timeout in request socket if it was specified in options.
|
---|
44 | *
|
---|
45 | * @param {ClientRequest} Req Request object
|
---|
46 | * @param {IncomingMessage} Res Response object
|
---|
47 | * @param {Object} Options Config object passed to the proxy
|
---|
48 | *
|
---|
49 | * @api private
|
---|
50 | */
|
---|
51 |
|
---|
52 | timeout: function timeout(req, res, options) {
|
---|
53 | if(options.timeout) {
|
---|
54 | req.socket.setTimeout(options.timeout);
|
---|
55 | }
|
---|
56 | },
|
---|
57 |
|
---|
58 | /**
|
---|
59 | * Sets `x-forwarded-*` headers if specified in config.
|
---|
60 | *
|
---|
61 | * @param {ClientRequest} Req Request object
|
---|
62 | * @param {IncomingMessage} Res Response object
|
---|
63 | * @param {Object} Options Config object passed to the proxy
|
---|
64 | *
|
---|
65 | * @api private
|
---|
66 | */
|
---|
67 |
|
---|
68 | XHeaders: function XHeaders(req, res, options) {
|
---|
69 | if(!options.xfwd) return;
|
---|
70 |
|
---|
71 | var encrypted = req.isSpdy || common.hasEncryptedConnection(req);
|
---|
72 | var values = {
|
---|
73 | for : req.connection.remoteAddress || req.socket.remoteAddress,
|
---|
74 | port : common.getPort(req),
|
---|
75 | proto: encrypted ? 'https' : 'http'
|
---|
76 | };
|
---|
77 |
|
---|
78 | ['for', 'port', 'proto'].forEach(function(header) {
|
---|
79 | req.headers['x-forwarded-' + header] =
|
---|
80 | (req.headers['x-forwarded-' + header] || '') +
|
---|
81 | (req.headers['x-forwarded-' + header] ? ',' : '') +
|
---|
82 | values[header];
|
---|
83 | });
|
---|
84 |
|
---|
85 | req.headers['x-forwarded-host'] = req.headers['x-forwarded-host'] || req.headers['host'] || '';
|
---|
86 | },
|
---|
87 |
|
---|
88 | /**
|
---|
89 | * Does the actual proxying. If `forward` is enabled fires up
|
---|
90 | * a ForwardStream, same happens for ProxyStream. The request
|
---|
91 | * just dies otherwise.
|
---|
92 | *
|
---|
93 | * @param {ClientRequest} Req Request object
|
---|
94 | * @param {IncomingMessage} Res Response object
|
---|
95 | * @param {Object} Options Config object passed to the proxy
|
---|
96 | *
|
---|
97 | * @api private
|
---|
98 | */
|
---|
99 |
|
---|
100 | stream: function stream(req, res, options, _, server, clb) {
|
---|
101 |
|
---|
102 | // And we begin!
|
---|
103 | server.emit('start', req, res, options.target || options.forward);
|
---|
104 |
|
---|
105 | var agents = options.followRedirects ? followRedirects : nativeAgents;
|
---|
106 | var http = agents.http;
|
---|
107 | var https = agents.https;
|
---|
108 |
|
---|
109 | if(options.forward) {
|
---|
110 | // If forward enable, so just pipe the request
|
---|
111 | var forwardReq = (options.forward.protocol === 'https:' ? https : http).request(
|
---|
112 | common.setupOutgoing(options.ssl || {}, options, req, 'forward')
|
---|
113 | );
|
---|
114 |
|
---|
115 | // error handler (e.g. ECONNRESET, ECONNREFUSED)
|
---|
116 | // Handle errors on incoming request as well as it makes sense to
|
---|
117 | var forwardError = createErrorHandler(forwardReq, options.forward);
|
---|
118 | req.on('error', forwardError);
|
---|
119 | forwardReq.on('error', forwardError);
|
---|
120 |
|
---|
121 | (options.buffer || req).pipe(forwardReq);
|
---|
122 | if(!options.target) { return res.end(); }
|
---|
123 | }
|
---|
124 |
|
---|
125 | // Request initalization
|
---|
126 | var proxyReq = (options.target.protocol === 'https:' ? https : http).request(
|
---|
127 | common.setupOutgoing(options.ssl || {}, options, req)
|
---|
128 | );
|
---|
129 |
|
---|
130 | // Enable developers to modify the proxyReq before headers are sent
|
---|
131 | proxyReq.on('socket', function(socket) {
|
---|
132 | if(server && !proxyReq.getHeader('expect')) {
|
---|
133 | server.emit('proxyReq', proxyReq, req, res, options);
|
---|
134 | }
|
---|
135 | });
|
---|
136 |
|
---|
137 | // allow outgoing socket to timeout so that we could
|
---|
138 | // show an error page at the initial request
|
---|
139 | if(options.proxyTimeout) {
|
---|
140 | proxyReq.setTimeout(options.proxyTimeout, function() {
|
---|
141 | proxyReq.abort();
|
---|
142 | });
|
---|
143 | }
|
---|
144 |
|
---|
145 | // Ensure we abort proxy if request is aborted
|
---|
146 | req.on('aborted', function () {
|
---|
147 | proxyReq.abort();
|
---|
148 | });
|
---|
149 |
|
---|
150 | // handle errors in proxy and incoming request, just like for forward proxy
|
---|
151 | var proxyError = createErrorHandler(proxyReq, options.target);
|
---|
152 | req.on('error', proxyError);
|
---|
153 | proxyReq.on('error', proxyError);
|
---|
154 |
|
---|
155 | function createErrorHandler(proxyReq, url) {
|
---|
156 | return function proxyError(err) {
|
---|
157 | if (req.socket.destroyed && err.code === 'ECONNRESET') {
|
---|
158 | server.emit('econnreset', err, req, res, url);
|
---|
159 | return proxyReq.abort();
|
---|
160 | }
|
---|
161 |
|
---|
162 | if (clb) {
|
---|
163 | clb(err, req, res, url);
|
---|
164 | } else {
|
---|
165 | server.emit('error', err, req, res, url);
|
---|
166 | }
|
---|
167 | }
|
---|
168 | }
|
---|
169 |
|
---|
170 | (options.buffer || req).pipe(proxyReq);
|
---|
171 |
|
---|
172 | proxyReq.on('response', function(proxyRes) {
|
---|
173 | if(server) { server.emit('proxyRes', proxyRes, req, res); }
|
---|
174 |
|
---|
175 | if(!res.headersSent && !options.selfHandleResponse) {
|
---|
176 | for(var i=0; i < web_o.length; i++) {
|
---|
177 | if(web_o[i](req, res, proxyRes, options)) { break; }
|
---|
178 | }
|
---|
179 | }
|
---|
180 |
|
---|
181 | if (!res.finished) {
|
---|
182 | // Allow us to listen when the proxy has completed
|
---|
183 | proxyRes.on('end', function () {
|
---|
184 | if (server) server.emit('end', req, res, proxyRes);
|
---|
185 | });
|
---|
186 | // We pipe to the response unless its expected to be handled by the user
|
---|
187 | if (!options.selfHandleResponse) proxyRes.pipe(res);
|
---|
188 | } else {
|
---|
189 | if (server) server.emit('end', req, res, proxyRes);
|
---|
190 | }
|
---|
191 | });
|
---|
192 | }
|
---|
193 |
|
---|
194 | };
|
---|