UNPKG

latte_web3

Version:
735 lines (722 loc) 20.9 kB
(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); });