@themost/web
Version:
MOST Web Framework 2.0 - Web Server Module
248 lines (234 loc) • 8.85 kB
JavaScript
/**
* @license
* MOST Web Framework 2.0 Codename Blueshift
* Copyright (c) 2017, THEMOST LP All rights reserved
*
* Use of this source code is governed by an BSD-3-Clause license that can be
* found in the LICENSE file at https://themost.io/license
*/
var HttpNotFoundError = require('@themost/common/errors').HttpNotFoundError;
var HttpServerError = require('@themost/common/errors').HttpServerError;
var HttpForbiddenError = require('@themost/common/errors').HttpForbiddenError;
var TraceUtils = require('@themost/common/utils').TraceUtils;
var fs = require('fs');
var url = require('url');
var path = require("path");
var crypto = require('crypto');
var _ = require('lodash');
/**
* Static File Handler
* @class
* @implements ValidateRequestHandler
* @implements MapRequestHandler
* @implements PostMapRequestHandler
* @implements ProcessRequestHandler
* @param {string=} rootDir
* @constructor
*/
function StaticHandler(rootDir) {
this.rootDir = rootDir;
}
StaticHandler.prototype.validateRequest = function(context, callback) {
callback = callback || function() {};
callback.call(context);
};
/*
* Maps the current request handler with the underlying HTTP request.
* */
StaticHandler.prototype.mapRequest = function(context, callback)
{
callback = callback || function() {};
try {
if (typeof this.rootDir !== 'string') {
return callback();
}
if (_.isEmpty(this.rootDir)) {
return callback();
}
//get file path
var uri = url.parse(context.request.url);
var filePath = path.join(this.rootDir ,uri.pathname);
fs.exists(filePath, function(exists) {
if (!exists) {
callback(null);
}
else {
fs.stat(filePath, function(err, stats) {
if (err) {
callback(err);
}
else {
//if file exists
if (stats && stats.isFile()) {
//set request current handler
context.request.currentHandler = new StaticHandler();
//set current execution path
context.request.currentExecutionPath = filePath;
//set file stats
context.request.currentExecutionFileStats = stats;
}
callback(null);
}
});
}
});
} catch (e) {
callback(e);
}
};
/**
*
* @param {HttpContext} context
* @param {string} executionPath
* @param {Function} callback
*/
StaticHandler.prototype.unmodifiedRequest = function(context, executionPath, callback) {
try {
var requestETag = context.request.headers['if-none-match'];
if (typeof requestETag === 'undefined' || requestETag === null) {
callback(null, false);
return;
}
fs.exists(executionPath, function(exists) {
try {
if (exists) {
fs.stat(executionPath, function(err, stats) {
if (err) {
callback(err);
}
else {
if (!stats.isFile()) {
callback(null, false);
}
else {
//validate if-none-match
var md5 = crypto.createHash('md5');
md5.update(stats.mtime.toString());
var responseETag = md5.digest('base64');
return callback(null, (requestETag===responseETag));
}
}
});
}
else {
callback(null, false);
}
}
catch (err) {
TraceUtils.error(err);
callback(null, false);
}
});
}
catch (err) {
TraceUtils.error(err);
callback(null, false);
}
};
StaticHandler.prototype.preflightRequest = function(context, callback) {
try {
if (context && (context.request.currentHandler instanceof StaticHandler)) {
var headerNames = context.response["_headerNames"] || { };
if (typeof headerNames["access-control-allow-origin"] === 'undefined') {
if (context.request.headers.origin) {
context.response.setHeader("Access-Control-Allow-Origin", context.request.headers.origin);
}
else {
context.response.setHeader("Access-Control-Allow-Origin", "*");
}
}
if (typeof headerNames["access-control-allow-headers"] === 'undefined')
context.response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Content-Language, Accept, Accept-Language, Authorization");
if (typeof headerNames["access-control-allow-credentials"] === 'undefined')
context.response.setHeader("Access-Control-Allow-Credentials", "true");
if (typeof headerNames["access-control-allow-methods"] === 'undefined')
context.response.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
}
return callback();
}
catch(e) {
callback(e);
}
};
StaticHandler.prototype.postMapRequest = function(context, callback) {
return StaticHandler.prototype.preflightRequest.call(this, context, callback);
};
/**
* @param {HttpContext} context
* @param {Function} callback
*/
StaticHandler.prototype.processRequest = function(context, callback)
{
callback = callback || function() {};
try {
if (context.is('OPTIONS')) {
//do nothing
return callback();
}
//get current execution path and validate once again file presence and MIME type
var stats = context.request.currentExecutionFileStats;
if (typeof stats === 'undefined' || stats === null) {
callback(new HttpServerError('Invalid request handler.'));
return;
}
if (!stats.isFile()) {
return callback(new HttpNotFoundError());
}
else {
//get if-none-match header
var requestETag = context.request.headers['if-none-match'];
//generate responseETag
var md5 = crypto.createHash('md5');
md5.update(stats.mtime.toString());
var responseETag = md5.digest('base64');
if (requestETag)
if (requestETag===responseETag) {
//context.response.writeHead(304, { 'Last-Modified':stats.mtime.toUTCString() });
context.response.writeHead(304, { });
context.response.end();
return callback.call(context);
}
//get file extension
var extensionName = path.extname(context.request.currentExecutionPath);
//get MIME collection
var mimes = context.getApplication().getConfiguration().mimes;
var contentType = null, contentEncoding=null;
//find MIME type by extension
var mime =mimes.filter(function(x) { return x.extension===extensionName; })[0];
if (mime) {
contentType = mime.type;
if (mime.encoding)
contentEncoding = mime.encoding;
}
//throw exception (MIME not found or access denied)
if (contentType===null) {
callback(new HttpForbiddenError())
}
else {
//create stream
var source = fs.createReadStream(context.request.currentExecutionPath);
//write headers
context.response.writeHead(200, {'Content-Type': contentType + (contentEncoding ? ';charset=' + contentEncoding : ''), 'ETag' : responseETag});
//response file
source.pipe(context.response);
//handle end
source.on('end', function() {
callback();
});
//handle error
source.on('error', function(err) {
callback(err);
});
}
}
}
catch (e) {
callback.call(context, e);
}
};
if (typeof exports !== 'undefined') {
module.exports.StaticHandler = StaticHandler;
module.exports.createInstance = function() {
return new StaticHandler();
};
}