1 | |
---|
2 | /** |
---|
3 | * Module dependencies |
---|
4 | */ |
---|
5 | var url = require('url'), |
---|
6 | join = require('path').join, |
---|
7 | protocols = { |
---|
8 | http: require('http'), |
---|
9 | https: require('https') |
---|
10 | }; |
---|
11 | |
---|
12 | module.exports = function(endpoint, opts) { |
---|
13 | opts = opts || {}; |
---|
14 | |
---|
15 | var parsedUrl = url.parse(endpoint); |
---|
16 | |
---|
17 | return function simpleHttpProxy(req, res, next) { |
---|
18 | // Get our forwarding info |
---|
19 | var hostInfo = req.headers.host.split(":"); |
---|
20 | |
---|
21 | // Remove the host header |
---|
22 | delete req.headers.host; |
---|
23 | |
---|
24 | // Optionally delete cookie |
---|
25 | if (opts.cookies === false) { |
---|
26 | delete req.headers.cookie; |
---|
27 | } |
---|
28 | |
---|
29 | // Should we keep the trailing slash? |
---|
30 | var trailingSlash = req.originalUrl[req.originalUrl.length-1] === "/"; |
---|
31 | |
---|
32 | // Setup the options |
---|
33 | var options = { |
---|
34 | hostname: parsedUrl.hostname, |
---|
35 | auth: parsedUrl.auth, |
---|
36 | port: parsedUrl.port, |
---|
37 | headers: req.headers, |
---|
38 | path: join(parsedUrl.pathname, trailingSlash ? req.url : req.url.substring(1)), |
---|
39 | method: req.method |
---|
40 | }; |
---|
41 | |
---|
42 | // Enable forwarding headers |
---|
43 | if(opts.xforward) { |
---|
44 | // Get the path at which the middleware is mounted |
---|
45 | var resPath = req.originalUrl.replace(req.url, ""); |
---|
46 | |
---|
47 | // We'll need to add a / if it's not on there |
---|
48 | if (resPath.indexOf("/") !== 0) { |
---|
49 | resPath = join("/", resPath); |
---|
50 | } |
---|
51 | |
---|
52 | // Pass along our headers |
---|
53 | options.headers[opts.xforward.proto || "x-forwarded-proto"] = req.connection.encrypted ? "https" : "http"; |
---|
54 | options.headers[opts.xforward.host || "x-forwarded-host"] = hostInfo[0]; |
---|
55 | options.headers[opts.xforward.path || "x-forwarded-path"] = resPath; |
---|
56 | |
---|
57 | if (hostInfo[1]) { |
---|
58 | options.headers[opts.xforward.port || "x-forwarded-port"] = hostInfo[1]; |
---|
59 | } |
---|
60 | } |
---|
61 | |
---|
62 | /** |
---|
63 | * Hack for nginx backends where node, by default, sends no 'content-length' |
---|
64 | * header if no data is sent with the request and sets the 'transfer-encoding' |
---|
65 | * to 'chunked'. Nginx will then send a 411 because the body contains a '0' |
---|
66 | * which is the length of the chunk |
---|
67 | * |
---|
68 | * GET /proxy |
---|
69 | * transfer-encoding: chunked |
---|
70 | * |
---|
71 | * 0 |
---|
72 | * |
---|
73 | */ |
---|
74 | if( ["POST", "DELETE"].indexOf(req.method) !== -1 && options.headers['transfer-encoding'] !== 'chunked') { |
---|
75 | options.headers['content-length'] = options.headers['content-length'] || '0'; |
---|
76 | } |
---|
77 | |
---|
78 | // Make the request with the correct protocol |
---|
79 | var request = protocols[(parsedUrl.protocol || 'http').replace(":", "")].request(options, function(response) { |
---|
80 | |
---|
81 | // Send down the statusCode and headers |
---|
82 | res.writeHead(response.statusCode, response.headers); |
---|
83 | |
---|
84 | // Pipe the response |
---|
85 | response.pipe(res); |
---|
86 | }); |
---|
87 | |
---|
88 | // Handle any timeouts that occur |
---|
89 | request.setTimeout(opts.timeout || 10000, function() { |
---|
90 | // Clean up the socket |
---|
91 | // TODO is there a better way to do this? There's a 'socket hang up' error being emitted... |
---|
92 | request.setSocketKeepAlive(false); |
---|
93 | request.socket.destroy(); |
---|
94 | |
---|
95 | // Pass down the error |
---|
96 | var err = new Error("Proxy to '"+endpoint+"' timed out"); |
---|
97 | request.emit("error", err); |
---|
98 | }); |
---|
99 | |
---|
100 | // Pipe the client request upstream |
---|
101 | req.pipe(request); |
---|
102 | |
---|
103 | // Pass on our errors |
---|
104 | request.on('error', next); |
---|
105 | }; |
---|
106 | }; |
---|