Permalink
Browse files

refactor tunnel middleware into two phases

By splitting the tunnel logic into a detection phase and a handling phase,
applications are free to override the handling logic.

In order to maintain backwards-compatibility, instead of refactoring the
existing tunnel middleware, a tunnel-demux middleware to detect the type of
tunnel request, and tunnel-(rpc|specs|type) middleware to handle each type of
request have been added. Mojito itself will now use these new middleware in
place of the single tunnel middleware, which will remain for those applications
that are specifying it in their configuration.
  • Loading branch information...
1 parent e61c900 commit 9ed57ebc5512743d932301a5d89d4f5b25676320 @ekashida ekashida committed Mar 21, 2013
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved.
+ * Copyrights licensed under the New BSD License.
+ * See the accompanying LICENSE file for terms.
+ */
+
+/*global require, module*/
+/*jslint sloppy:true, nomen:true*/
+
+
+var liburl = require('url'),
+ RE_REPEATING_SLASH = /\/{2,}/g;
+
+function trimSlash(str) {
+ if (str.charAt(0) === '/') {
+ str = str.substring(1, str.length);
+ }
+ if (str.charAt(str.length - 1) === '/') {
+ str = str.substring(0, str.length - 1);
+ }
+ return str;
+}
+
+
+/**
+ * Export a function which can create the handler.
+ * @param {Object} config Data to configure the handler.
+ * @return {Object} The newly constructed handler.
+ */
+module.exports = function (config) {
+ var that = this,
+ appConfig = config.store.getAppConfig({}) || {},
+ staticPrefix,
+ tunnelPrefix;
+
+ staticPrefix = appConfig.staticHandling && appConfig.staticHandling.prefix;
+ tunnelPrefix = appConfig.tunnelPrefix;
+
+ if (staticPrefix) {
+ staticPrefix = '/' + trimSlash(staticPrefix);
+ }
+ if (tunnelPrefix) {
+ tunnelPrefix = '/' + trimSlash(tunnelPrefix);
+ }
+
+ this.staticPrefix = staticPrefix || '/static';
+ this.tunnelPrefix = tunnelPrefix || '/tunnel';
+
+ return function (req, res, next) {
+ var hasTunnelPrefix = req.url.indexOf(that.tunnelPrefix) === 0,
+ hasTunnelHeader = req.headers['x-mojito-header'] === 'tunnel',
+ name,
+ type,
+ url,
+ parts;
+
+ // If we are not tunneling get out of here fast!
+ if (!hasTunnelPrefix && !hasTunnelHeader) {
+ return next();
+ }
+
+ req._tunnel = {};
+
+ /**
+ Tunnel examples
+
+ RPC tunnel:
+ /tunnel (or it could just have the tunnel header)
+
+ Type tunnel:
+ /static/{type}/definition.json
+ /{tunnelPrefix}/{type}/definition.json // custom prefix
+ /tunnel/static/{type}/definition.json // according to a UT
+
+ Spec tunnel:
+ /static/{type}/specs/default.json
+ /{staticPrefix}/{type}/specs/default.json // custom prefix
+ /tunnel/static/{type}/specs/default.json // according to a UT
+ **/
+
+ url = req.url.split('?')[0];
+
+ // Normalization step to handle `/{tunnelPrefix}`, `/{staticPrefix}`,
+ // and `/{tunnelPrefix}/{staticPrefix}` URLs.
+ url = url.replace(that.staticPrefix, '')
+ .replace(that.tunnelPrefix, '')
+ .replace(RE_REPEATING_SLASH, '/');
+
+ parts = url.split('/');
+
+ if (parts.length) {
+ name = parts[parts.length - 1];
+ type = parts[1];
+
+ // Spec tunnel
+ if (parts[parts.length - 2] === 'specs') {
+ req._tunnel.specsReq = {
+ type: type,
+ name: name
+ };
+ return next();
+ }
+ // Type tunnel
+ if (name === 'definition.json') {
+ req._tunnel.typeReq = {
+ type: type
+ };
+ return next();
+ }
+ }
+
+ // RPC tunnel
+ if (req.url === that.tunnelPrefix && req.method === 'POST') {
+ req._tunnel.rpcReq = {};
+ return next();
+ }
+
+ return next();
+ };
+};
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved.
+ * Copyrights licensed under the New BSD License.
+ * See the accompanying LICENSE file for terms.
+ */
+
+/*global exports, module*/
+/*jslint sloppy:true, nomen:true*/
+
+
+function sendData(res, data, code) {
+ res.writeHead((code || 200), {
+ 'content-type': 'application/json; charset="utf-8"'
+ });
+ res.end(JSON.stringify(data, null, 4));
+}
+
+function sendError(res, msg, code) {
+ sendData(res, {error: msg}, (code || 500));
+}
+
+
+/**
+ * Exports a middleware factory that can handle RPC tunnel requests.
+ *
+ * @param {Object} config The configuration.
+ * @return {Function} The handler.
+ */
+module.exports = function (config) {
+ return function (req, res, next) {
+ var command = req.body,
+ instance = command.instance;
+
+ command.context = command.context || {};
+
+ if (!req._tunnel || !req._tunnel.rpcReq) {
+ return next();
+ }
+
+ // When switching from the client context to the server context, we
+ // have to override the runtime.
+ command.context.runtime = 'server';
+
+ // All we need to do is expand the instance given within the RPC call
+ // and attach it within a "tunnelCommand", which will be handled by
+ // Mojito instead of looking up a route for it.
+ config.store.expandInstance(instance, command.context, function (err, instance) {
+ // Replace with the expanded instance.
+ command.instance = instance;
+ req.command = {
+ action: command.action,
+ instance: {
+ // Magic here to delegate to tunnelProxy.
+ base: 'tunnelProxy'
+ },
+ params: {
+ body: {
+ proxyCommand: command
+ }
+ },
+ context: command.context
+ };
+ return next();
+ });
+ };
+};
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved.
+ * Copyrights licensed under the New BSD License.
+ * See the accompanying LICENSE file for terms.
+ */
+
+/*global module*/
+/*jslint sloppy:true, nomen:true*/
+
+
+function sendData(res, data, code) {
+ res.writeHead((code || 200), {
+ 'content-type': 'application/json; charset="utf-8"'
+ });
+ res.end(JSON.stringify(data, null, 4));
+}
+
+function sendError(res, msg, code) {
+ sendData(res, {error: msg}, (code || 500));
+}
+
+
+/**
+ * Exports a middleware factory that can handle spec tunnel requests.
+ *
+ * @param {Object} config The configuration.
+ * @return {Function} The handler.
+ */
+module.exports = function (config) {
+ return function (req, res, next) {
+ var specsReq = req._tunnel && req._tunnel.specsReq,
+ instance = {},
+ type,
+ name;
+
+ if (!specsReq) {
+ return next();
+ }
+
+ type = specsReq.type;
+ name = specsReq.name;
+ name = name && name.split('.').slice(0, -1).join('.');
+
+ if (!type || !name) {
+ return sendError(res, 'Not found: ' + req.url, 500);
+ }
+
+ instance.base = type;
+
+ if (name !== 'default') {
+ instance.base += ':' + name;
+ }
+
+ config.store.expandInstanceForEnv('client', instance, req.context,
+ function (err, data) {
+ if (err) {
+ return sendError(
+ res,
+ 'Error opening: ' + req.url + '\n' + err,
+ 500
+ );
+ }
+ return sendData(res, data);
+ });
+ };
+};
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved.
+ * Copyrights licensed under the New BSD License.
+ * See the accompanying LICENSE file for terms.
+ */
+
+/*global module*/
+/*jslint sloppy:true, nomen:true*/
+
+
+function sendData(res, data, code) {
+ res.writeHead((code || 200), {
+ 'content-type': 'application/json; charset="utf-8"'
+ });
+ res.end(JSON.stringify(data, null, 4));
+}
+
+function sendError(res, msg, code) {
+ sendData(res, {error: msg}, (code || 500));
+}
+
+
+/**
+ * Exports a middleware factory that can handle type tunnel requests.
+ *
+ * @param {Object} config The configuration.
+ * @return {Function} The handler.
+ */
+module.exports = function (config) {
+ return function (req, res, next) {
+ var typeReq = req._tunnel && req._tunnel.typeReq,
+ instance = {};
+
+ if (!typeReq) {
+ return next();
+ }
+
+ if (!typeReq.type) {
+ return sendError(res, 'Not found: ' + req.url, 500);
+ }
+
+ instance.type = typeReq.type;
+
+ config.store.expandInstanceForEnv('client', instance, req.context,
+ function (err, data) {
+ if (err) {
+ return sendError(
+ res,
+ 'Error opening: ' + req.url + '\n' + err,
+ 500
+ );
+ }
+ return sendData(res, data);
+ });
+ };
+};
View
@@ -90,7 +90,10 @@ MojitoServer.MOJITO_MIDDLEWARE = [
'mojito-parser-body',
'mojito-parser-cookies',
'mojito-contextualizer',
- 'mojito-handler-tunnel',
+ 'mojito-handler-tunnel-demux',
+ 'mojito-handler-tunnel-rpc',
+ 'mojito-handler-tunnel-specs',
+ 'mojito-handler-tunnel-type',
'mojito-router',
'mojito-handler-dispatcher'
];

0 comments on commit 9ed57eb

Please sign in to comment.