/**
* MOST Web Framework
* A JavaScript Web Framework
* http://themost.io
*
* Copyright (c) 2014, Kyriakos Barbounakis k.barbounakis@gmail.com, Anthi Oikonomou anthioikonomou@gmail.com
*
* Released under the BSD3-Clause license
* Date: 2014-05-07
*/
/**
* Routing HTTP Handler
*/
/**
* @ignore
*/
var app = require('./index'),
array = require('most-array'),
url = require('url'),
util = require('util'),
fs = require('fs'),
route = require('./http-route.js'),
xml = require('most-xml'),
path=require('path'),
S = require('string');
/**
* @class ViewHandler
* @constructor
* @augments HttpHandler
*/
function ViewHandler() {
//
}
/**
*
* @param ctor
* @param superCtor
*/
Object.inherits = function (ctor, superCtor) {
if (!ctor.super_) {
ctor.super_ = superCtor;
while (superCtor) {
var superProto = superCtor.prototype;
var keys = Object.keys(superProto);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (typeof ctor.prototype[key] === 'undefined')
ctor.prototype[key] = superProto[key];
}
superCtor = superCtor.super_;
}
}
}
ViewHandler.STR_CONTROLLERS_FOLDER = 'controllers';
ViewHandler.STR_CONTROLLER_FILE = './%s-controller.js';
ViewHandler.STR_CONTROLLER_RELPATH = '/controllers/%s-controller.js';
ViewHandler.queryControllerClass = function(controllerName, context, callback) {
if (typeof controllerName === 'undefined' || controllerName==null) {
callback();
}
else {
//get controller class path and model (if any)
var controllerPath = app.current.mapPath(util.format(ViewHandler.STR_CONTROLLER_RELPATH, S(controllerName).dasherize().chompLeft('-').toString())),
controllerModel = context.model(controllerName);
//if controller does not exists
fs.exists(controllerPath, function(exists){
try {
//if controller class file does not exist in /controllers/ folder
if (!exists) {
//try to find if current controller has a model defined
if (controllerModel) {
var controllerType = controllerModel.type || 'data';
//try to find controller based on the model's type in controllers folder (e.g. /library-controller.js)
controllerPath = app.current.mapPath(util.format(ViewHandler.STR_CONTROLLER_RELPATH, controllerType));
fs.exists(controllerPath, function(exists) {
if (!exists) {
//get controller path according to related model's type (e.g ./data-controller)
controllerPath = util.format(ViewHandler.STR_CONTROLLER_FILE, controllerType);
//if controller does not exist
controllerPath = path.join(__dirname, controllerPath);
fs.exists(controllerPath, function(exists) {
if (!exists)
callback(null, require('./base-controller'));
else
callback(null, require(controllerPath));
});
}
else {
callback(null, require(controllerPath));
}
});
}
else {
var ControllerCtor = context.application.config.controllers[controllerName] || require('./base-controller');
callback(null, ControllerCtor);
}
}
else {
//return controller class
callback(null, require(controllerPath));
}
}
catch (e) {
callback(e);
}
});
}
};
//ViewHandler.prototype.beginRequest = function (context, callback) {
// //angularjs compatibility headers
// if (context.response) {
// context.response.setHeader("Access-Control-Allow-Origin", "*");
// context.response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
// context.response.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
// }
// callback();
//};
ViewHandler.RestrictedLocations = [
{ "path":"^/controllers/", "description":"Most web framework server controllers" },
{ "path":"^/models/", "description":"Most web framework server models" },
{ "path":"^/extensions/", "description":"Most web framework server extensions" },
{ "path":"^/handlers/", "description":"Most web framework server handlers" },
{ "path":"^/views/", "description":"Most web framework server views" }
];
ViewHandler.prototype.authorizeRequest = function (context, callback) {
try {
var uri = url.parse(context.request.url);
for (var i = 0; i < ViewHandler.RestrictedLocations.length; i++) {
/**
* @type {*|LocationSetting}
*/
var location = ViewHandler.RestrictedLocations[i], re = new RegExp(location.path,'ig');
if (re.test(uri.pathname)) {
callback(new app.common.HttpException(403, 'Forbidden'))
return;
}
}
callback();
}
catch(e) {
callback(e);
}
};
/**
* @param {HttpContext} context
*/
ViewHandler.prototype.mapRequest = function (context, callback) {
callback = callback || function () {
};
//try to map request
try {
//first of all check if a request handler is already defined
if (typeof context.request.currentHandler !== 'undefined') {
//do nothing (exit mapping)
callback();
return;
}
var requestUri = url.parse(context.request.url);
/**
* find route by querying application routes
* @type {HttpRoute}
*/
var currentRoute = queryRoute(requestUri);
if (typeof currentRoute === 'undefined' || currentRoute == null) {
//do nothing
callback();
return;
}
fs.stat(app.current.mapPath(context.request.url), function(err, stats) {
if (stats && stats.isFile()) {
//do nothing
return callback();
}
else {
try {
//query controller
var arr = currentRoute.routeData.filter(function(x) { return (x.name==":controller"); });
var controllerName = (arr.length>0) ? arr[0].value : queryController(requestUri);
if (controllerName != null) {
//try to find controller class
ViewHandler.queryControllerClass(controllerName, context, function(err, ControllerClass) {
if (err) {
callback(err)
}
else {
try {
//initialize controller
var controller = new ControllerClass();
//set controller's name
controller.name = controllerName.toLowerCase();
//set controller's context
controller.context = context;
//set request handler
var handler = new ViewHandler();
handler.controller = controller;
context.request.currentHandler = handler;
//set route data
context.request.route = currentRoute;
context.request.routeData = currentRoute.routeData;
//set route data as params
for (var i = 0; i < currentRoute.routeData.length; i++) {
var item = currentRoute.routeData[i], name = item.name.substr(1);
context.params[name] = item.value;
}
callback.call(context);
}
catch(e) {
callback(e);
}
}
});
}
else {
callback.call(context);
}
}
catch(e) {
callback(e);
}
}
});
}
catch (e) {
callback(e);
}
};
/**
* @param {HttpContext} context
* @param {Function} callback
*/
ViewHandler.prototype.postMapRequest = function (context, callback) {
try {
var model, obj;
if (context.params)
if (context.params.controller)
model = context.model(context.params.controller);
ViewHandler.prototype.preflightRequest.call(this, context, function(err) {
if (err) { return callback(err); }
if (context.is('POST')) {
if (context.format=='xml') {
//get current model
if (context.request.body) {
//load xml
try {
var doc = xml.loadXML(context.request.body);
obj = xml.deserialize(doc.documentElement);
context.params.data = obj;
}
catch (e) {
return callback(e);
}
}
}
else if (context.format=='json') {
if (typeof context.request.body === 'string') {
//parse json data
try {
obj = JSON.parse(context.request.body);
//set context data
context.params.data = obj;
}
catch(e) {
//otherwise raise error
app.common.log(e);
return callback(new Error('Invalid JSON data.'));
}
}
}
}
return callback();
});
}
catch(e) {
callback(e);
}
};
ViewHandler.prototype.preflightRequest = function (context, callback) {
try {
if (context && (context.request.currentHandler instanceof ViewHandler)) {
context.response.setHeader("Access-Control-Allow-Origin", "*");
context.response.setHeader("Access-Control-Allow-Credentials", "true");
context.response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Content-Language, Accept, Accept-Language, Authorization");
if (context.request.route && context.request.route.allow) {
context.response.setHeader('Access-Control-Allow-Methods', context.request.route.allow);
}
else {
context.response.setHeader('Access-Control-Allow-Methods', "GET, OPTIONS, PUT, POST, DELETE");
}
}
return callback();
}
catch(e) {
callback(e);
}
};
/**
* @param {HttpContext} context
* @param {Function} callback
*/
ViewHandler.prototype.processRequest = function (context, callback) {
var self = this;
callback = callback || function () { };
try {
if (context.is('OPTIONS')) {
//do nothing
return callback();
}
var common = require('./common'), app = require('./index');
//validate request controller
var controller = self.controller;
if (controller) {
/**
* try to find action
* @type {String}
*/
var action = context.data['action'];
if (action) {
//execute action
var fn = controller[action];
if (typeof fn !== 'function') {
fn = controller[S(action).camelize().toString()];
if (typeof fn !== 'function')
fn = controller.action;
}
if (typeof fn !== 'function') {
return callback(new app.common.HttpNotFoundException());
}
//enumerate params
var params = common.getFunctionParams(fn);
params = [];
/**
* @type HttpResult
* */
params.push(function (err, result) {
if (err) {
//throw error
callback.call(context, err);
}
else {
//execute http result
result.execute(context, callback);
}
});
//invoke controller method
fn.apply(controller, params);
return;
}
}
callback.call(context);
}
catch (e) {
callback.call(context, e);
}
};
/**
*
* @param {String} requestUrl
* @returns {HttpRoute}
* @private
*/
function queryRoute(requestUri) {
try {
var array = require('most-array'),
util = require('util'),
route = require('./http-route.js'),
app = require('./index');
/**
* @type Array
* */
var routes = app.current.config.routes;
//enumerate registered routes
var httpRoute = null;
array(routes).each(function (item) {
//initialize HttpRoute object
/**
* @type HttpRoute
*/
httpRoute = route.createInstance(item.url, item.route);
util._extend(httpRoute, item);
//if uri path is matched
if (httpRoute.isMatch(requestUri.pathname)) {
//parse route
httpRoute.parse(requestUri.pathname);
//get or set controller
var param = array(httpRoute.routeData).firstOrDefault(function (x) {
return x.name == ":controller";
});
if (!param) {
if (item.controller) {
httpRoute.routeData.push({name: ":controller", value: item.controller})
}
else {
httpRoute.routeData.push({name: ":controller", value: queryController(requestUri)})
}
}
//get or set action
param = array(httpRoute.routeData).firstOrDefault(function (x) {
return x.name == ":action";
});
if (!param) httpRoute.routeData.push({name: ":action", value: item.action});
if (item.mime) {
httpRoute.routeData.push({name: ":mime", value: item.mime });
}
else {
var mime = app.current.resolveMime(requestUri.pathname);
if (mime)
httpRoute.routeData.push({name: ":mime", value: mime.type });
}
//exit loop
return false;
}
httpRoute = null;
});
return httpRoute;
}
catch (e) {
throw e;
}
}
/**
* Gets the controller of the given url
* @param requestUrl {Url} - A string that represents the url we want to parse.
* @private
* */
function queryController(requestUri) {
try {
if (requestUri === undefined)
return null;
//split path
var segments = requestUri.pathname.split('/');
//put an exception for root controller
//maybe this is unnecessary exception but we need to search for root controller e.g. /index.html, /about.html
if (segments.length == 2)
return 'root';
else
//e.g /pages/about where segments are ['','pages','about']
//and the controller of course is always the second segment.
return segments[1];
//todo:validate workspaces (e.g. /my-workspace/pages/about) where controller segment differs based on the workspace url.
}
catch (e) {
throw e;
}
}
/**
* Gets the action of the given url
* @param requestUrl {Url} - A string that represents the url we want to parse.
* @private
* */
function queryAction(requestUri) {
try {
if (requestUri === undefined)
return null;
//split path
var segments = requestUri.pathname.split('/');
//put an exception for root controller
//maybe this is unnecessary exception but we need to search for root controller e.g. /index.html, /about.html
if (segments.length == 2)
return 'root';
else
//e.g /pages/about where segments are ['','pages','about']
//and the controller of course is always the second segment.
return segments[1];
//todo:validate workspaces (e.g. /my-workspace/pages/about) where controller segment differs based on the workspace url.
}
catch (e) {
throw e;
}
}
/**
* @returns ViewHandler
* */
ViewHandler.prototype.createInstance = function () {
return new ViewHandler();
};
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') module.exports = ViewHandler.prototype.createInstance();