Skip to content

Commit 3ec36da

Browse files
authored
Merge pull request #81 from pseudomuto/separate_route_storage
Separate storage mechanism for routes
2 parents 67e3f21 + 58446c1 commit 3ec36da

8 files changed

+591
-287
lines changed

lib/configproxy.js

+128-92
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ var http = require('http'),
1717
util = require('util'),
1818
URL = require('url'),
1919
querystring = require('querystring'),
20-
trie = require('./trie.js');
20+
store = require('./store.js');
2121

2222
function bound (that, method) {
2323
// bind a method, to ensure `this=that` when it is called
@@ -100,10 +100,10 @@ function parse_host (req) {
100100
function ConfigurableProxy (options) {
101101
var that = this;
102102
this.options = options || {};
103-
this.trie = new trie.URLTrie();
103+
104+
this._routes = store.MemoryStore();
104105
this.auth_token = this.options.auth_token;
105106
this.includePrefix = options.includePrefix === undefined ? true : options.includePrefix;
106-
this.routes = {};
107107
this.host_routing = this.options.host_routing;
108108
this.error_target = options.error_target;
109109
if (this.error_target && this.error_target.slice(-1) !== '/') {
@@ -169,7 +169,7 @@ function ConfigurableProxy (options) {
169169
}
170170

171171
// proxy requests separately
172-
var proxy_callback = log_errors(that.handle_proxy_web);
172+
var proxy_callback = log_errors(this.handle_proxy_web);
173173
if ( this.options.ssl ) {
174174
this.proxy_server = https.createServer(this.options.ssl, proxy_callback);
175175
} else {
@@ -185,23 +185,33 @@ function ConfigurableProxy (options) {
185185

186186
util.inherits(ConfigurableProxy, EventEmitter);
187187

188-
ConfigurableProxy.prototype.add_route = function (path, data) {
188+
ConfigurableProxy.prototype.add_route = function (path, data, cb) {
189189
// add a route to the routing table
190-
path = trie.trim_prefix(path);
190+
path = this._routes.cleanPath(path);
191191
if (this.host_routing && path !== '/') {
192192
data.host = path.split('/')[1];
193193
}
194-
this.routes[path] = data;
195-
this.trie.add(path, data);
196-
this.update_last_activity(path);
194+
195+
var that = this;
196+
197+
this._routes.add(path, data, function () {
198+
that.update_last_activity(path, function () {
199+
if (typeof(cb) === "function") {
200+
cb();
201+
}
202+
});
203+
});
197204
};
198205

199-
ConfigurableProxy.prototype.remove_route = function (path) {
206+
ConfigurableProxy.prototype.remove_route = function (path, cb) {
200207
// remove a route from the routing table
201-
if (this.routes[path] !== undefined) {
202-
delete this.routes[path];
203-
this.trie.remove(path);
204-
}
208+
var routes = this._routes;
209+
210+
routes.hasRoute(path, function (result) {
211+
if (result) {
212+
routes.remove(path, cb);
213+
}
214+
});
205215
};
206216

207217
ConfigurableProxy.prototype.get_routes = function (req, res) {
@@ -211,7 +221,7 @@ ConfigurableProxy.prototype.get_routes = function (req, res) {
211221
var inactive_since = null;
212222
if (parsed.query) {
213223
var query = querystring.parse(parsed.query);
214-
224+
215225
if (query.inactive_since !== undefined) {
216226
var timestamp = Date.parse(query.inactive_since);
217227
if (isFinite(timestamp)) {
@@ -223,20 +233,24 @@ ConfigurableProxy.prototype.get_routes = function (req, res) {
223233
}
224234
}
225235
res.writeHead(200, { 'Content-Type': 'application/json' });
226-
var routes = {};
227-
if (inactive_since) {
228-
Object.keys(this.routes).map(function (path) {
229-
var route = that.routes[path];
230-
if (route.last_activity < inactive_since) {
231-
routes[path] = route;
232-
}
233-
});
234-
} else {
235-
routes = this.routes;
236-
}
237-
res.write(JSON.stringify(routes));
238-
res.end();
239-
this.statsd.increment('api.route.get', 1);
236+
237+
this._routes.getAll(function (routes) {
238+
var results = {};
239+
240+
if (inactive_since) {
241+
Object.keys(routes).forEach(function (path) {
242+
if (routes[path].last_activity < inactive_since) {
243+
results[path] = routes[path];
244+
}
245+
});
246+
} else {
247+
results = routes;
248+
}
249+
250+
res.write(JSON.stringify(results));
251+
res.end();
252+
that.statsd.increment('api.route.get', 1);
253+
});
240254
};
241255

242256
ConfigurableProxy.prototype.post_routes = function (req, res, path, data) {
@@ -250,47 +264,70 @@ ConfigurableProxy.prototype.post_routes = function (req, res, path, data) {
250264
return;
251265
}
252266

253-
this.add_route(path, data);
254-
res.writeHead(201);
255-
res.end();
256-
this.statsd.increment('api.route.add', 1);
267+
var that = this;
268+
this.add_route(path, data, function () {
269+
res.writeHead(201);
270+
res.end();
271+
that.statsd.increment('api.route.add', 1);
272+
});
257273
};
258274

259275
ConfigurableProxy.prototype.delete_routes = function (req, res, path) {
260276
// DELETE removes an existing route
261277
log.debug('DELETE', path);
262-
if (this.routes[path] === undefined) {
263-
res.writeHead(404);
264-
} else {
265-
this.remove_route(path);
266-
res.writeHead(204);
267-
}
268-
res.end();
269-
this.statsd.increment('api.route.delete', 1);
278+
279+
var that = this;
280+
this._routes.hasRoute(path, function (result) {
281+
if (result) {
282+
that.remove_route(path, function () {
283+
res.writeHead(204);
284+
res.end();
285+
that.statsd.increment('api.route.delete', 1);
286+
});
287+
} else {
288+
res.writeHead(404);
289+
res.end();
290+
that.statsd.increment('api.route.delete', 1);
291+
}
292+
});
270293
};
271294

272-
ConfigurableProxy.prototype.target_for_req = function (req) {
295+
ConfigurableProxy.prototype.target_for_req = function (req, cb) {
273296
var timer = this.statsd.createTimer('find_target_for_req');
274297
// return proxy target for a given url path
275298
var base_path = (this.host_routing) ? '/' + parse_host(req) : '';
276-
var route = this.trie.get(trie.string_to_path(base_path + decodeURIComponent(req.url)));
277-
timer.stop();
278-
if (route) {
279-
return {
280-
prefix: route.prefix,
281-
target: route.data.target,
282-
};
283-
}
299+
300+
this._routes.getTarget(base_path + decodeURIComponent(req.url), function (route) {
301+
timer.stop();
302+
if (route) {
303+
cb({
304+
prefix: route.prefix,
305+
target: route.data.target
306+
});
307+
return;
308+
}
309+
310+
cb(null);
311+
});
284312
};
285313

286-
ConfigurableProxy.prototype.update_last_activity = function (prefix) {
314+
ConfigurableProxy.prototype.update_last_activity = function (prefix, cb) {
287315
var timer = this.statsd.createTimer('last_activity_updating');
288-
// note last activity in routing table
289-
if (this.routes[prefix] !== undefined) {
290-
// route may have been deleted with open connections
291-
this.routes[prefix].last_activity = new Date();
292-
}
293-
timer.stop();
316+
var routes = this._routes;
317+
318+
routes.hasRoute(prefix, function (result) {
319+
cb = cb || function() {};
320+
321+
if (result) {
322+
routes.update(prefix, { "last_activity": new Date() }, function () {
323+
timer.stop();
324+
cb();
325+
});
326+
} else {
327+
timer.stop();
328+
cb();
329+
}
330+
});
294331
};
295332

296333
ConfigurableProxy.prototype._handle_proxy_error_default = function (code, kind, req, res) {
@@ -363,48 +400,47 @@ ConfigurableProxy.prototype.handle_proxy_error = function (code, kind, req, res)
363400
ConfigurableProxy.prototype.handle_proxy = function (kind, req, res) {
364401
// proxy any request
365402
var that = this;
403+
var args = Array.prototype.slice.call(arguments, 1);
404+
366405
// get the proxy target
367-
var match = this.target_for_req(req);
368-
if (!match) {
369-
this.handle_proxy_error(404, kind, req, res);
370-
return;
371-
}
372-
this.emit("proxy_request", req, res);
373-
var prefix = match.prefix;
374-
var target = match.target;
375-
log.debug("PROXY", kind.toUpperCase(), req.url, "to", target);
376-
if (!this.includePrefix) {
377-
req.url = req.url.slice(prefix.length);
378-
}
406+
this.target_for_req(req, function (match) {
407+
if (!match) {
408+
that.handle_proxy_error(404, kind, req, res);
409+
return;
410+
}
379411

380-
// pop method off the front
381-
var args = arguments_array(arguments);
382-
args.shift();
412+
that.emit("proxy_request", req, res);
413+
var prefix = match.prefix;
414+
var target = match.target;
415+
log.debug("PROXY", kind.toUpperCase(), req.url, "to", target);
416+
if (!that.includePrefix) {
417+
req.url = req.url.slice(prefix.length);
418+
}
383419

384-
// add config argument
385-
args.push({
386-
target: target
387-
});
420+
// add config argument
421+
args.push({ target: target });
388422

389-
// add error handling
390-
args.push(function (e) {
391-
log.error("Proxy error: ", e);
392-
that.handle_proxy_error(503, kind, req, res);
393-
});
423+
// add error handling
424+
args.push(function (e) {
425+
log.error("Proxy error: ", e);
426+
that.handle_proxy_error(503, kind, req, res);
427+
});
394428

395-
// update last activity timestamp in routing table
396-
this.update_last_activity(prefix);
429+
// update timestamp on any reply data as well (this includes websocket data)
430+
req.on('data', function () {
431+
that.update_last_activity(prefix);
432+
});
397433

398-
// update timestamp on any reply data as well (this includes websocket data)
399-
req.on('data', function () {
400-
that.update_last_activity(prefix);
401-
});
402-
res.on('data', function () {
403-
that.update_last_activity(prefix);
404-
});
434+
res.on('data', function () {
435+
that.update_last_activity(prefix);
436+
});
405437

406-
// dispatch the actual method
407-
this.proxy[kind].apply(this.proxy, args);
438+
// update last activity timestamp in routing table
439+
that.update_last_activity(prefix, function () {
440+
// dispatch the actual method
441+
that.proxy[kind].apply(that.proxy, args);
442+
});
443+
});
408444
};
409445

410446
ConfigurableProxy.prototype.handle_proxy_ws = function (req, res, head) {

lib/store.js

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
var trie = require("./trie.js");
2+
3+
var NotImplemented = function (name) {
4+
return {
5+
name: "NotImplementedException",
6+
message: "method '" + name + "' not implemented"
7+
};
8+
};
9+
10+
var BaseStore = Object.create(Object.prototype, {
11+
// "abstract" methods
12+
get: { value: function () { throw NotImplemented("get"); } },
13+
getTarget: { value: function () { throw NotImplemented("getTarget"); } },
14+
getAll: { value: function () { throw NotImplemented("getAll"); } },
15+
add: { value: function () { throw NotImplemented("add"); } },
16+
update: { value: function () { throw NotImplemented("update"); } },
17+
remove: { value: function () { throw NotImplemented("remove"); } },
18+
hasRoute: { value: function () { throw NotImplemented("hasRoute"); } },
19+
20+
cleanPath: {
21+
value: function (path) {
22+
return trie.trim_prefix(path);
23+
}
24+
},
25+
26+
notify: {
27+
value: function (cb) {
28+
if (typeof(cb) === "function") {
29+
var args = Array.prototype.slice.call(arguments, 1);
30+
cb.apply(this, args);
31+
}
32+
}
33+
}
34+
});
35+
36+
function MemoryStore () {
37+
var routes = {};
38+
var urls = new trie.URLTrie();
39+
40+
return Object.create(BaseStore, {
41+
get: {
42+
value: function (path, cb) {
43+
this.notify(cb, routes[path]);
44+
}
45+
},
46+
getTarget: {
47+
value: function (path, cb) {
48+
this.notify(cb, urls.get(path));
49+
}
50+
},
51+
getAll: {
52+
value: function (cb) {
53+
this.notify(cb, routes);
54+
}
55+
},
56+
add: {
57+
value: function (path, data, cb) {
58+
routes[path] = data;
59+
urls.add(path, data);
60+
this.notify(cb);
61+
}
62+
},
63+
update: {
64+
value: function (path, data, cb) {
65+
Object.assign(routes[path], data);
66+
this.notify(cb);
67+
}
68+
},
69+
remove: {
70+
value: function (path, cb) {
71+
delete routes[path];
72+
urls.remove(path);
73+
this.notify(cb);
74+
}
75+
},
76+
hasRoute: {
77+
value: function (path, cb) {
78+
this.notify(cb, routes.hasOwnProperty(path));
79+
}
80+
}
81+
});
82+
}
83+
84+
exports.MemoryStore = MemoryStore;

0 commit comments

Comments
 (0)