1 | var path = require('path');
|
2 | var fs = require('fs');
|
3 | var eventStream = require('event-stream');
|
4 | var express = require('express');
|
5 | var request = require('request');
|
6 | var httpProxy = require('http-proxy');
|
7 | var logger = require('js-logger');
|
8 | var s = require('underscore.string');
|
9 | var _ = require('lodash');
|
10 | var uri = require('urijs');
|
11 | var tiny_lr = require('tiny-lr');
|
12 | var liveReload = require('connect-livereload');
|
13 | var body = require('body-parser');
|
14 | var runningAsScript = require.main === module;
|
15 | var configFile = process.env.HAWTIO_CONFIG_FILE || 'config.js';
|
16 |
|
17 | var config = {
|
18 |
|
19 | port: 2772,
|
20 |
|
21 | logLevel: logger.INFO,
|
22 |
|
23 | proxy: '/proxy',
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 | staticProxies: [],
|
34 |
|
35 | staticAssets: [
|
36 | {
|
37 | path: '/',
|
38 | dir: '.'
|
39 | }
|
40 | ],
|
41 | fallback: null,
|
42 | liveReload: {
|
43 | enabled: false,
|
44 | port: 35729
|
45 | }
|
46 | };
|
47 | if (fs.existsSync(configFile)) {
|
48 | var conf = require(configFile);
|
49 | _.assign(config, conf);
|
50 | }
|
51 | logger.useDefaults(config.logLevel);
|
52 | if (runningAsScript) {
|
53 | logger.get('hawtio-backend').info("Running as script");
|
54 | }
|
55 |
|
56 | var HawtioBackend;
|
57 | (function (HawtioBackend) {
|
58 | HawtioBackend.log = logger.get('hawtio-backend');
|
59 | HawtioBackend.app = express();
|
60 | HawtioBackend.proxyRoutes = {};
|
61 | var startupTasks = [];
|
62 | var listening = false;
|
63 | function getTargetURI(options) {
|
64 | var target = new uri({
|
65 | protocol: options.proto,
|
66 | hostname: options.hostname,
|
67 | port: options.port,
|
68 | path: options.path
|
69 | });
|
70 | target.query(options.query);
|
71 | var targetURI = target.toString();
|
72 | HawtioBackend.log.debug("Target URI: ", targetURI);
|
73 | return targetURI;
|
74 | }
|
75 | HawtioBackend.getTargetURI = getTargetURI;
|
76 | function addStartupTask(cb) {
|
77 | HawtioBackend.log.debug("Adding startup task");
|
78 | startupTasks.push(cb);
|
79 | if (listening) {
|
80 | cb();
|
81 | }
|
82 | }
|
83 | HawtioBackend.addStartupTask = addStartupTask;
|
84 | function setConfig(newConfig) {
|
85 | _.assign(config, newConfig);
|
86 | HawtioBackend.log.setLevel(config.logLevel);
|
87 | }
|
88 | HawtioBackend.setConfig = setConfig;
|
89 | var server = null;
|
90 | var lr = null;
|
91 | var lrServer = null;
|
92 | function reload() {
|
93 | return eventStream.map(function (file, callback) {
|
94 | if (lr) {
|
95 | lr.changed({
|
96 | body: {
|
97 | files: file.path
|
98 | }
|
99 | });
|
100 | }
|
101 | return callback(null, file);
|
102 | });
|
103 | }
|
104 | HawtioBackend.reload = reload;
|
105 | function use(path, func) {
|
106 | HawtioBackend.app.use(path, func);
|
107 | }
|
108 | HawtioBackend.use = use;
|
109 | function listen(cb) {
|
110 | var lrPort = config.liveReload.port || 35729;
|
111 | if (config.liveReload.enabled) {
|
112 | HawtioBackend.app.use(liveReload({ port: lrPort }));
|
113 | }
|
114 | listening = true;
|
115 | startupTasks.forEach(function (cb) {
|
116 | HawtioBackend.log.debug("Executing startup task");
|
117 | cb();
|
118 | });
|
119 | if (config.fallback) {
|
120 | HawtioBackend.app.use(function (req, res, next) {
|
121 | fs.createReadStream(config.fallback).pipe(res);
|
122 | });
|
123 | }
|
124 | server = HawtioBackend.app.listen(config.port, function () {
|
125 | if (config.liveReload.enabled) {
|
126 | lr = tiny_lr();
|
127 | lrServer = lr.listen(lrPort, function () {
|
128 | HawtioBackend.log.info("Started livereload, port :", lrPort);
|
129 | });
|
130 | }
|
131 | cb(server);
|
132 | });
|
133 | server.on('upgrade', function (req, socket, head) {
|
134 |
|
135 | var targetUri = new uri(req.url);
|
136 | var targetPath = targetUri.path();
|
137 | _.forIn(HawtioBackend.proxyRoutes, function (config, route) {
|
138 | if (s.startsWith(targetPath, route)) {
|
139 |
|
140 | if (!config.httpProxy) {
|
141 | var proxyConfig = config.proxyConfig;
|
142 | var target = new uri().protocol(proxyConfig.proto).host(proxyConfig.hostname).port(proxyConfig.port).path(proxyConfig.targetPath).query({}).toString();
|
143 | console.log("Creating websocket proxy to target: ", target);
|
144 | config.proxy = httpProxy.createProxyServer({
|
145 | target: target,
|
146 | secure: false,
|
147 | ws: true
|
148 | });
|
149 | }
|
150 | targetPath = targetPath.substring(route.length);
|
151 | req.url = targetUri.path(targetPath).toString();
|
152 | config.proxy.ws(req, socket, head);
|
153 | }
|
154 | });
|
155 | });
|
156 | return server;
|
157 | }
|
158 | HawtioBackend.listen = listen;
|
159 | function stop(cb) {
|
160 | if (lrServer) {
|
161 | lrServer.close(function () {
|
162 | HawtioBackend.log.info("Stopped livereload port");
|
163 | });
|
164 | lrServer = null;
|
165 | }
|
166 | if (server) {
|
167 | server.close(function () {
|
168 | listening = false;
|
169 | if (cb) {
|
170 | cb();
|
171 | }
|
172 | });
|
173 | server = null;
|
174 | }
|
175 | }
|
176 | HawtioBackend.stop = stop;
|
177 | function getServer() {
|
178 | return server;
|
179 | }
|
180 | HawtioBackend.getServer = getServer;
|
181 | if (runningAsScript) {
|
182 | server = listen(function (server) {
|
183 | var host = server.address().address;
|
184 | var port = server.address().port;
|
185 | HawtioBackend.log.info("started at ", host, ":", port);
|
186 | });
|
187 | }
|
188 | })(HawtioBackend || (HawtioBackend = {}));
|
189 | (module).exports = HawtioBackend;
|
190 |
|
191 | var HawtioBackend;
|
192 | (function (HawtioBackend) {
|
193 | function proxy(uri, req, res) {
|
194 | function handleError(e) {
|
195 | res.status(500).end('error proxying to "' + uri + '": ' + e);
|
196 | }
|
197 | var r = request({ method: req.method, uri: uri, json: req.body });
|
198 | req.on('error', handleError)
|
199 | .pipe(r)
|
200 | .on('error', handleError)
|
201 | .on('response', function (res2) {
|
202 | if (res2.statusCode === 401 || res2.statusCode === 403) {
|
203 | HawtioBackend.log.info("Authentication failed on remote server:", res2.statusCode, res2.statusMessage, uri);
|
204 | HawtioBackend.log.debug("Response headers:\n", res2.headers);
|
205 | res.header(res2.headers).sendStatus(res2.statusCode);
|
206 | }
|
207 | else {
|
208 | res2.pipe(res).on('error', handleError);
|
209 | }
|
210 | });
|
211 | }
|
212 | HawtioBackend.addStartupTask(function () {
|
213 | var index = 0;
|
214 | config.staticProxies.forEach(function (proxyConfig) {
|
215 | index = index + 1;
|
216 | _.defaults(proxyConfig, {
|
217 | path: '/proxy-' + index,
|
218 | hostname: 'localhost',
|
219 | port: 80,
|
220 | proto: 'http',
|
221 | targetPath: '/proxy-' + index
|
222 | });
|
223 | HawtioBackend.log.debug("adding static proxy config: \n", proxyConfig);
|
224 | var router = express.Router();
|
225 | router.use('/', function (req, res, next) {
|
226 | var path = [s.rtrim(proxyConfig.targetPath, '/'), s.ltrim(req.path, '/')].join('/');
|
227 | var uri = HawtioBackend.getTargetURI({
|
228 | proto: proxyConfig.proto,
|
229 | hostname: proxyConfig.hostname,
|
230 | port: proxyConfig.port,
|
231 | path: path,
|
232 | query: req.query
|
233 | });
|
234 | proxy(uri, req, res);
|
235 | });
|
236 | HawtioBackend.app.use(proxyConfig.path, router);
|
237 | HawtioBackend.proxyRoutes[proxyConfig.path] = {
|
238 | proxyConfig: proxyConfig,
|
239 | router: router
|
240 | };
|
241 | });
|
242 | });
|
243 |
|
244 | var proxyRouter = express.Router();
|
245 | proxyRouter.param('proto', function (req, res, next, proto) {
|
246 | HawtioBackend.log.debug("requesting proto: ", proto);
|
247 | switch (proto.toLowerCase()) {
|
248 | case 'http':
|
249 | case 'https':
|
250 | next();
|
251 | break;
|
252 | default:
|
253 | res.status(406).send('Invalid protocol: "' + proto + '"');
|
254 | }
|
255 | });
|
256 | proxyRouter.param('hostname', function (req, res, next, hostname) {
|
257 | HawtioBackend.log.debug("requesting hostname: ", hostname);
|
258 | next();
|
259 | });
|
260 | proxyRouter.param('port', function (req, res, next, port) {
|
261 | HawtioBackend.log.debug("requesting port: ", port);
|
262 | var portNumber = s.toNumber(port);
|
263 | HawtioBackend.log.debug("parsed port number: ", portNumber);
|
264 | if (isNaN(portNumber)) {
|
265 | res.status(406).send('Invalid port number: "' + port + '"');
|
266 | }
|
267 | else {
|
268 | next();
|
269 | }
|
270 | });
|
271 | proxyRouter.use('/', function (req, res, next) {
|
272 | if (req.path === '') {
|
273 | res.status(200).end();
|
274 | }
|
275 | else {
|
276 | next();
|
277 | }
|
278 | });
|
279 | proxyRouter.use('/:proto/:hostname/:port/', function (req, res, next) {
|
280 | var uri = HawtioBackend.getTargetURI({
|
281 | proto: req.params.proto,
|
282 | hostname: req.params.hostname,
|
283 | port: req.params.port,
|
284 | path: req.path,
|
285 | query: req.query
|
286 | });
|
287 | proxy(uri, req, res);
|
288 | });
|
289 | HawtioBackend.addStartupTask(function () {
|
290 | HawtioBackend.log.debug("Setting dynamic proxy mount point: ", config.proxy);
|
291 | HawtioBackend.app.use(config.proxy, proxyRouter);
|
292 | });
|
293 | })(HawtioBackend || (HawtioBackend = {}));
|
294 |
|
295 | var HawtioBackend;
|
296 | (function (HawtioBackend) {
|
297 | function mountAsset(mount, dir) {
|
298 | HawtioBackend.app.use(mount, express.static(path.normalize(dir)));
|
299 | }
|
300 | HawtioBackend.mountAsset = mountAsset;
|
301 | HawtioBackend.addStartupTask(function () {
|
302 | config.staticAssets.forEach(function (asset) {
|
303 | HawtioBackend.log.info("Mounting static asset: ", asset);
|
304 | mountAsset(asset.path, asset.dir);
|
305 | });
|
306 | });
|
307 | })(HawtioBackend || (HawtioBackend = {}));
|