latte_web3
Version:
735 lines (722 loc) • 20.9 kB
JavaScript
(function(define) {//'use strict'
define("latte_web/server/index", ["require", "exports", "module", "window"],
function(require, exports, module, window) {
var Cluster = require("cluster")
, latte_lib = require("latte_lib")
, Rpc = Cluster.isMaster ? require("./rpc/master") : require("./rpc/slave")
, Querystring = require("querystring")
, Domain = require("domain")
, Path = require("path")
, Url = require("url")
, Mines = require("../mines")
, Modules= require("latte_require")
, Post = require("../post")
, Fs = latte_lib.fs
, Ios = require("../io")
, latte_watch = require("latte_watch");
var getErrorString = function(err) {
if(err.stack) {
return err.stack.toString();
}else if(latte_lib.isString(err)) {
return err.toString();
}else{
var errorString;
try {
errorString = JSON.stringify(err);
}catch(e){
var Util = require("util");
errorString = Util.inspect(err);
}
return errorString;
}
}
/**
@class ServerConfig
@module server
@namespace latte_web
*/
var defaultConfig = {};
(function() {
/**
@property {Int} cpus
@type Int
*/
this.cpus = require("os").cpus().length;
/**
@property {Int} shedulingPolicy
@type Int
*/
this.shedulingPolicy = Cluster.SCHED_RR;
/**
@property {Int} port
@type Int
*/
this.port = 10086;
/**
@property {String} path
@type String
@default "html/"
*/
this.path = "html/";
/**
@property {String} uploadDir
@type String
*/
this.uploadDir = "tmp/";
/**
@property {Object} proxys
@type {Object}
*/
this.proxys = {};
/**
@property {Boolean} nginx
@type {Boolean}
@default false
*/
this.nginx = false;
/**
@property {Boolean} cache
@type {Boolean}
@default false
*/
this.cache = false;
/**
@property {Boolean} gzip
@type {Boolean}
@default false
*/
this.gzip = false;
/*
@property {Boolean} memoryFile
@type {Boolean}
@default false
*/
//this.memoryFile = false;
/**
@property {Boolean} log
@type {Boolean}
@default false
*/
this.log = false;
/**
* @property {} type
@type {String}
@default http
*/
this.type = "http";
}).call(defaultConfig);
var Server = function(config) {
this.config = latte_lib.merger(defaultConfig, config);
this.gets = {};
this.posts = {};
this.getRegs = [];
this.postRegs = [];
this.befores = [];
this.afters = [];
this.rpc = new Rpc(this.config.rpc);
var self = this;
this.cache = {};
if(this.config.web) {
this.reloadWeb();
}
this.httpCount = 0;
};
latte_lib.inherits(Server, latte_lib.events);
(function() {
/*
this.reloadWeb = function(path) {
var self = this;
this.gets = {};
this.posts = {};
var oldRequire = this.webRequire;
delete (oldRequire) ;
this.webRequire = null;
path = path || this.config.web;
this.webRequire = Modules.create(path+"/.latte");
this.loadWeb(path);
if(this.webWatcher) {
this.webWatcher.close();
this.webWatcher = null;
}
if(this.config.webWatch) {
this.webWatcher = Fs.watch(path, function() {
self.reloadWeb(path);
});
}
}
*/
this.reloadWeb = function(path) {
var self = this;
this.gets = {};
this.posts = {};
var oldRequire = this.webRequire;
delete(oldRequire);
this.webRequire = null;
this.config.web = path = path || this.config.web;
this.webRequire = Modules.create("./");
if(this.webWatcher) {
this.webWatcher.close();
this.webWatcher = null;
}
this.webWatcher = latte_watch.create(path);
this.webWatcher.on("addDir", function(addDirName ) {
self.loadDir(addDirName);
});
this.webWatcher.on("unlink" , function() {
self.reloadWeb();
});
this.webWatcher.on("unlinkDir", function(){
self.reloadWeb();
});
this.webWatcher.on("add", function(filename) {
self.loadFile(filename);
});
this.webWatcher.on("change", function() {
self.reloadWeb();
});
this.loadDir(path);
/**
if(!this.config.webWatch) {
this.webWatcher.on("ready", function() {
console.log("ready");
self.webWatcher && self.webWatcher.close();
self.webWatcher = null;
});
}
*/
}
/**
* @method loadWeb
* 可能存在的问题
* 就是eval 是否可以生成一个json对象
*/
/**
this.loadWeb = function() {
var self = this;
this.loadDir(".");
}
this.loadDir = function(path) {
var self = this;
var p = Path.join(this.config.web, path);
var files = latte_lib.fs.readdirSync(p);
files.forEach(function(filename) {
var stat = latte_lib.fs.statSync(p + "/" + filename);
if(stat.isDirectory()) {
self.loadDir(path + "/" + filename);
}else{
self.loadFile(path + "/"+ filename);
}
});
}*/
this.loadFile = function(path) {
var self = this;
var o ;
try {
o = self.webRequire.require("./"+path);
}catch(err) {
if(self.config.log) {
var filename = "./logs/loadWeb/"+latte_lib.format.dateFormat()+".log";
latte_lib.fs.writeFile(filename, latte_lib.getErrorString(err));
}else{
throw err;
}
return ;
}
if(o.get) {
var args = [o.path];
if(latte_lib.isArray(o.get)) {
args = args.concat(o.get);
}else{
args.push(o.get);
}
self.get.apply(this, args);
}
if(o.post) {
var args = [o.path];
if(latte_lib.isArray(o.post)) {
args = args.concat(o.post);
}else{
args.push(o.post);
}
self.post.apply(this, args);
}
}
this.loadDir = function(path) {
var self = this;
var files = latte_lib.fs.readdirSync(path);
files.forEach(function(filename) {
var stat = latte_lib.fs.statSync(path + "/" + filename);
if(stat.isFile()) {
self.loadFile(path + "/"+ filename);
}else if(stat.isDirectory()) {
self.loadDir(path + "/" + filename);
}
});
}
this.getIo = function(path) {
return this.ios.get(path);
}
this.io = function(path, opts, fn) {
this.ios = this.ios || new Ios();
if(!this.config.nginx && this.config.cpus != 1) {
throw new Error("you config error about")
}
return this.ios.add(path, opts, fn);
}
this.doMaster = function(fn) {
if(Cluster.isMaster) {
fn();
}
}
this.doSlave = function(fn) {
if(!Cluster.isMaster) {
fn();
}
}
/**
@method before
@param {Function} fn
var Server = require("latte_web");
var server = new Server({});
server.before(function(req, res, next) {
});
*/
this.before = function(fn) {
if(latte_lib.isFunction(fn)) {
this.befores.push(fn);
}
}
/**
@method after
@param {Function} fn
@example
var Server = require("latte_web");
var server = new Server({});
server.after(function(req, res, next) {
});
*/
this.after = function(fn) {
if(latte_lib.isFunction) {
this.afters.push(fn);
}
}
this.use = function(opts) {
this.before(opts.before.bind(opts));
this.after(opts.after.bind(opts));
}
this.set = function(type, path) {
var self = this;
var handles = Array.prototype.slice.call(arguments, 2);
handles.forEach(function(fn) {
if(!latte_lib.isFunction(fn)) {
throw new Error(path + "," + type + "handle has not function !!");
}
});
if(this[type + "s"]) {
this[type+"s"][path] = function(req, res) {
var fns = handles.map(function(fn) {
return function(callback) {
fn(req, res, callback);
}
});
latte_lib.async.series(fns, function(err, result) {
if(err) {
res.end(err);
}
});
}
}
};
var _self = this;
["get", "post"].forEach(function(type) {
_self[type] = function(path) {
var args = Array.prototype.slice.call(arguments);
args.unshift(type);
_self.set.apply(this, args);
}
});
this.onRequestSend = function(data, req, res) {
var self = this;
var fns = this.afters.map(function(fn) {
return function(callback) {
fn(req, res, callback);
};
});
latte_lib.async.parallel(fns, function(err) {
if(err) {
throw err;
}
res.end(data.toString());
});
}
this.request = function(fn, req, res) {
var self = this;
res.on("close" , function() {
self.httpCount--;
});
res.send = function(data){
self.onRequestSend(data, req, res);
};
var fns = this.befores.map(function(f) {
return function(callback) {
f(req, res, callback);
};
});
latte_lib.async.parallel(fns, function(err) {
if(err) {
throw err;
}
//console.log(fn.toString());
fn.call(self,req, res);
});
}
var regs =["getReg", "postReg"];
regs.forEach(function(type) {
_self[type] = function(path) {
var args = Array.prototype.slice.call(arguments);
args.unshift(type);
_self.setReg.apply(this, args);
}
});
this.pathReg = function(path) {
var keys = [];
path = path.concat("/?")
.replace(/\/\(/g,"(?:/")
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?(\*)?/g, function(_, slash, format, key, capture, optional, star) {
slash = slash || "";
keys.push(key);
return ""
+ (optional? "": slash)
+ "(?:"
+ (optional? slash: "")
+ (format || "") + (capture || (format && "([^/.]+?)" || "([^/]+?)")) + ")"
+ (optional || "")
+ (star? "(/*)?": "");
})
.replace(/([\/.])/g, "\\$1")
.replace(/\*/g, "(.*)");
return {
keys : keys,
regexp: new RegExp("^" + path + "$")
};
}
this.setReg = function(type, path) {
var self = this;
var handles = Array.prototype.slice.call(arguments, 2);
handles.forEach(function(fn) {
if(!latte_lib.isFunction(fn)) {
throw new Error("setReg" + path + "," + type + "handle has not function!!");
}
});
if(this[type+ "s"]) {
var object = this.pathReg(path);
object.action = function(req, res) {
var fns = handles.map(function(fn) {
return function(callback) {
fn(req, res, callback);
}
latte_lib.async.series(fns, function(err, result) {
if(err) {
res.end(err);
}
});
});
};
this[type+"s"].push(object);
}
}
this.onRequest = function(req, res) {
var self = this;
var url = Url.parse(req.url);
var pathname = decodeURIComponent(url.pathname);
if(self.ios && self.ios.all[pathname]) {
req.gets = Querystring.parse(url.query);
self.ios.all[pathname].handleRequest(req, res);
return;
}
this.httpCount++;
switch(req.method.toLowerCase()) {
case "post":
if(self.posts[pathname]) {
this.getPostData(req, function(err, data) {
if(err) { throw err; }
req.posts = data;
req.gets = Querystring.parse(url.query);
self.request(self.posts[pathname], req, res);
});
}else{
for(var i = 0, len = this.postRegs.length ; i < len; i++) {
var regs = this.postRegs[i];
var matched = regs.regexp.exec(pathname);
if(matched) {
return this.getPostData(req, function(err, data) {
req.posts = data;
req.gets = Querystring.parse(url.query);
var keys = regs.keys;
for(var i = 0, l = keys.length; i < l; i++) {
var value =matched[ i + 1];
if(value) {
req.gets[keys[i]] = value;
}
}
self.request(reqs.action, req, res);
});
}
}
this.proxyWeb(pathname, req, res);
}
break;
case "get":
if(self.gets[pathname]) {
req.gets = Querystring.parse(url.query);
req.posts = {};
return self.request(self.gets[pathname], req, res);
}else{
for(var i =0, len = this.postRegs.length; i < len; i++) {
var regs = this.getRegs[i];
var matched = regs.regexp.exec(pathname);
if(matched) {
req.posts = {};
req.gets = Querystring.parse(url.query);
var keys = regs.keys;
for(var i = 0, l = keys.length; i < l; i++) {
var value = matched[i + 1];
if(value) {
req.gets[keys[i]] = value;
}
}
return self.request(regs.action, req, res);
}
}
self.staticFile(pathname, req, res);
}
break;
}
}
this.proxyWeb = function(pathname, req, res) {
var onError = function() {
res.writeHead(404);
res.end("not find");
}
if(this.config.proxyUrl) {
var type = req.method.toLowerCase();
latte_lib.xhr[type](
this.config.proxyUrl+pathname,
req[type+"s"], {
headers: req.headers
}, function(data, headers) {
res.end();
}, function(error) {
onError()
}).on("chunk", function(data) {
res.write(data);
}).on("headers", function(headers) {
for(var i in headers) {
res.setHeader(i, headers[i]);
}
});
}else{
onError();
return;
}
}
this.findFile = function(path, callback) {
var self = this;
var last = path.slice(-1);
if(last == "/" || last == "\\") {
var indexs = this.config.indexs || ["index.html"];
var funs = indexs.map(function(obj) {
return function(cb) {
var p = path + obj;
latte_lib.fs.exists(p, function(exist) {
if(exist) {
return cb(p);
}
cb();
});
}
});
latte_lib.async.series(funs, function(file) {
callback(file);
});
} else {
latte_lib.fs.exists(path, function(exist) {
if(exist) {
var stat = latte_lib.fs.lstatSync(path);
if(stat.isFile() || stat.isSymbolicLink()) {
return callback(path);
}else{
return self.findFile(path+"/", callback);
}
//return callback(path);
}
callback();
});
}
};
this.staticFile = function(pathname, req, res) {
var self = this;
var paths = pathname.split("/");
var proxy = this.config.proxys[paths[1]];
var mpath;
if(proxy) {
var ps = paths.splice(2);
mpath = Path.normalize(proxy + "/" + ps.join("/"));
} else{
mpath = Path.normalize(self.config.path + pathname);
}
self.findFile(mpath, function(path) {
if(!path) {
self.proxyWeb(pathname, req, res);
return
}
var fileType = Path.extname(path);
res.setHeader("Content-Type", Mines.getFileType(fileType) || "application/octet-stream");
require("fs").stat(path, function(err, stat) {
var lastModified = stat.mtime.toUTCString();
if(req.headers["If-Modified-Since"] && lastModified == req.headers["If-Modified-Since"]) {
response.statusCode = 304;
response.end();
}else{
res.setHeader("Last-Modified", lastModified);
if(self.config.cache) {
var expires = new Date();
var maxAge = self.config.cache || 0;
expires.setTime(expires.getTime() + maxAge* 1000);
res.setHeader("Expires", expires.toUTCString());
res.setHeader("Cache-Control", "max-age=" + maxAge);
}
var stream = require("fs").createReadStream(path, {
flag:"r",
autoClose: true
});
var Zlib = require("zlib");
if(self.config.gzip &&
stat.size > self.config.gzip ) {
var acceptEncoding = req.headers['accept-encoding']
if(acceptEncoding.match(/\bgzip\b/)) {
res.setHeader("Content-Encoding", "gzip");
stream.pipe(Zlib.createGzip().pipe(res));
}else if(acceptEncoding.match(/\bdeflate\b/)) {
res.setHeader("Content-Encoding", "deflate");
stream.pipe(Zlib.createDeflate()).pipe(res);
}
}else{
stream.pipe(res);
}
}
});
});
}
this.getPostData = function(req, callback) {
var self = this
, post = new Post({
uploadDir: self.config.uploadDir
})
,posts = {
_files :[]
};
post
.on("error", function(err) {
callback(err);
})
.on("field", function(field, value) {
posts[field] = value;
})
.on("file", function(filename, file) {
posts._files.push(file);
})
.on("text", function(data) {
posts._text = data;
})
.on("end", function() {
console.log(posts);
callback(null, posts);
});
post.parse(req);
}
/**
@method _run
@private
*/
this._run = function() {
var self = this
, serverDomain = Domain.create();
serverDomain.on("error", function(err) {
if(self.config.log) {
var filename = "./logs/otherError/"+latte_lib.format.dateFormat()+".log";
latte_lib.fs.writeFile(filename, getErrorString(err));
}else{
throw err;
}
});
serverDomain.run(function() {
var Http = require(self.config.type);
var server = self.server = Http.createServer(function(req, res) {
var reqd = Domain.create();
reqd.on("error", function(err) {
res.setHeader("Content-Type", "text/plan");
res.end(getErrorString(err));
if(self.config.log) {
var filename = "./logs/webError/" + latte_lib.format.dateFormat() + ".log";
latte_lib.fs.writeFile(filename, [
req.url ,
"gets:" + latte_lib.format.jsonFormat(req.gets),
"posts:" + latte_lib.format.jsonFormat(req.posts),
getErrorString(err)
].join("\n")
);
} else {
throw err;
}
});
reqd.run(function() {
self.onRequest(req, res);
});
});
server.timeout = self.config.timeout;
server.on("upgrade", function(req, socket, head) {
self.onUpgrade(req, socket, head);
});
server.listen(self.config.port, function() {
latte_lib.debug.info(self.config.port, "start");
});
});
}
this.onUpgrade = function(req, socket, head) {
var url = Url.parse(req.url);
var pathname = url.pathname;
var self = this;
if(self.ios.all[pathname]) {
req.gets = Querystring.parse(url.query);
self.ios.all[pathname].handleUpgrade(req, socket, head);
}
}
this.createWorker = function() {
var worker = Cluster.fork();
this.emit("start", worker);
this.rpc.addWorker(worker);
return worker;
}
this.run = function() {
if(this.server) { return; }
if(Cluster.isMaster) {
var self = this;
for(var i = 0, len = this.config.cpus; i < len ; i++) {
var worker = self.createWorker();
}
Cluster.on("exit", function(worker) {
self.rpc.removeWorker(worker);
if(self.config.restart) {
var now = self.createWorker();
self.emit("restart", worker,now);
}
});
}else{
this._run();
}
}
}).call(Server.prototype);
module.exports = Server;
});
})(typeof define === "function"? define: function(name, reqs, factory) {factory(require, exports, module); });