1 |
|
2 | 'use strict';
|
3 |
|
4 |
|
5 | module.exports = function (config, renderer) {
|
6 | var pkg = require('../package.json');
|
7 | var httpProxy = require('http-proxy');
|
8 | var rewriteRedirect = config.argv['rewrite-redirect'] || false;
|
9 | var protocolRewrite = config.argv['rewrite-protocol'] || null;
|
10 | var proxy = httpProxy.createProxyServer({
|
11 | autoRewrite: rewriteRedirect,
|
12 | protocolRewrite: protocolRewrite
|
13 | });
|
14 | var deployTimestamp = require('../timestamp').value;
|
15 | var dispatch = require('./dispatch')(config);
|
16 | var statsd = require('./statsd')(config);
|
17 | var router = require('./router')(config);
|
18 |
|
19 | var proxyErr = false;
|
20 | proxy.on('proxyRes', function (proxyRes) {
|
21 | if (proxyRes.statusCode >= 400) {
|
22 | proxyErr = new Error(proxyRes.statusMessage);
|
23 | proxyErr.status = proxyRes.statusCode;
|
24 | } else {
|
25 | proxyErr = false;
|
26 | }
|
27 | });
|
28 |
|
29 | var parseJson = function (data) {
|
30 | var json = null;
|
31 | var err = null;
|
32 |
|
33 | try {
|
34 | json = JSON.parse(data);
|
35 | } catch (e) {
|
36 | err = e;
|
37 | }
|
38 |
|
39 | return {
|
40 | error: err,
|
41 | data: json
|
42 | };
|
43 | };
|
44 |
|
45 | var processor = {
|
46 | timestamp: function (req, res, next) {
|
47 | req.headers['X-Shunter-Deploy-Timestamp'] = deployTimestamp;
|
48 | next();
|
49 | },
|
50 |
|
51 | shunterVersion: function (req, res, next) {
|
52 | req.headers['X-Shunter'] = pkg.version;
|
53 | next();
|
54 | },
|
55 |
|
56 | intercept: function (req, res, next) {
|
57 | var data = [];
|
58 | var status = null;
|
59 |
|
60 | var shouldIntercept = function () {
|
61 | var type = res.getHeader('Content-type');
|
62 | var method = req.method ? req.method.toUpperCase() : 'GET';
|
63 | var acceptedType = type && (type.indexOf('x-shunter+json') !== -1);
|
64 | var acceptedMethod = method === 'GET' || method === 'POST';
|
65 |
|
66 | return acceptedType && acceptedMethod;
|
67 | };
|
68 |
|
69 | var write = function (chunk) {
|
70 | data.push(chunk);
|
71 | };
|
72 |
|
73 | var endProxyError = function () {
|
74 | res.writeHead = res.__originalWriteHead;
|
75 | res.write = res.__originalWrite;
|
76 | res.end = res.__originalEnd;
|
77 | return dispatch.send(proxyErr, '', req, res);
|
78 | };
|
79 |
|
80 | var endIntercept = function () {
|
81 | var timer = config.timer();
|
82 |
|
83 | if (req.__proxyTimingFunctionBodyReceived) {
|
84 | statsd.classifiedTiming(req.url, 'proxy_body_received', req.__proxyTimingFunctionBodyReceived('Received body ' + req.url));
|
85 | }
|
86 | var rawJson = Buffer.concat(data).toString('utf8');
|
87 | var json = parseJson(rawJson);
|
88 |
|
89 | statsd.classifiedGauge(req.url, 'json_size', Buffer.byteLength(rawJson));
|
90 | statsd.classifiedTiming(req.url, 'parsing', timer('Parsing JSON ' + req.url));
|
91 |
|
92 | res.writeHead = res.__originalWriteHead;
|
93 | res.write = res.__originalWrite;
|
94 | res.end = res.__originalEnd;
|
95 |
|
96 | if (json.error) {
|
97 | dispatch.send(json.error, '', req, res);
|
98 | } else {
|
99 | if (config.jsonViewParameter && req.query[config.jsonViewParameter]) {
|
100 | req.isJson = true;
|
101 | var jsonOutput = JSON.stringify(json.data, null, '\t');
|
102 | return dispatch.send(null, jsonOutput, req, res, status);
|
103 | }
|
104 |
|
105 | timer = config.timer();
|
106 | renderer.render(req, res, json.data, function (err, out) {
|
107 | statsd.classifiedTiming(req.url, 'rendering', timer('Rendering ' + req.url));
|
108 | dispatch.send(err, out, req, res, status);
|
109 | });
|
110 | }
|
111 | };
|
112 |
|
113 | var writeHead = function (code) {
|
114 | statsd.increment('total_requests');
|
115 |
|
116 | if (shouldIntercept()) {
|
117 | res.write = write;
|
118 | res.end = endIntercept;
|
119 | status = code;
|
120 | if (req.__proxyTimingFunctionHeadersReceived) {
|
121 | statsd.classifiedTiming(req.url, 'proxy_headers_received', req.__proxyTimingFunctionHeadersReceived('Received headers ' + req.url));
|
122 | }
|
123 | } else if (proxyErr) {
|
124 | res.write = write;
|
125 | res.end = endProxyError;
|
126 | status = code;
|
127 | } else {
|
128 | res.__originalWriteHead.apply(res, [].slice.call(arguments, 0));
|
129 | }
|
130 | };
|
131 |
|
132 | res.__originalWriteHead = res.writeHead;
|
133 | res.__originalEnd = res.end;
|
134 | res.__originalWrite = res.write;
|
135 |
|
136 | res.writeHead = writeHead;
|
137 |
|
138 | next();
|
139 | },
|
140 |
|
141 | ping: function (req, res) {
|
142 | res.writeHead(200);
|
143 | res.end('pong');
|
144 | },
|
145 |
|
146 | api: function (req, res) {
|
147 | var name = req.url.replace(/^\/+/, '').replace(/\?.*/, '').replace(/\/+/g, '__');
|
148 | var body = req.body;
|
149 | var err = null;
|
150 |
|
151 | if (!name && body.layout && body.layout.template) {
|
152 | name = body.layout.template;
|
153 | }
|
154 |
|
155 | if (name) {
|
156 | renderer.renderPartial(name, req, res, body, function (err, out) {
|
157 | dispatch.send(err, out, req, res);
|
158 | });
|
159 | } else {
|
160 | err = new Error('Template not found');
|
161 | err.status = 404;
|
162 | dispatch.send(err, null, req, res);
|
163 | }
|
164 | },
|
165 |
|
166 | proxy: function (req, res) {
|
167 | req.headers = req.headers || {};
|
168 |
|
169 | var host = req.headers.host || '';
|
170 | var route = router.map(host, req.url);
|
171 | var err = null;
|
172 |
|
173 | var rewriteRequestHeaders = function () {
|
174 | if (route.changeOrigin) {
|
175 | req.headers['X-Orig-Host'] = host;
|
176 | }
|
177 | };
|
178 |
|
179 | if (route) {
|
180 | if (route.host) {
|
181 | route.target = 'http://' + route.host + (route.port ? ':' + route.port : '');
|
182 |
|
183 | config.log.info('Proxying request for ' + host + req.url + ' to ' + route.target + req.url);
|
184 | req.__proxyTimingFunctionHeadersReceived = config.timer();
|
185 | req.__proxyTimingFunctionBodyReceived = config.timer();
|
186 |
|
187 | rewriteRequestHeaders();
|
188 | proxy.web(req, res, route, function (err) {
|
189 | err.status = (err.code === 'ECONNREFUSED') ? 502 : 500;
|
190 | dispatch.send(err, null, req, res);
|
191 | });
|
192 | } else {
|
193 | err = new Error('Route does not define a host');
|
194 | err.status = 500;
|
195 | dispatch.send(err, null, req, res);
|
196 | }
|
197 | } else {
|
198 | err = new Error('Request did not match any route');
|
199 | err.status = 404;
|
200 | dispatch.send(err, null, req, res);
|
201 | }
|
202 | }
|
203 | };
|
204 |
|
205 | return processor;
|
206 | };
|