Node.js后端框架設(shè)計(jì)構(gòu)想
后端的核心文件mass.js包含批量創(chuàng)建與刪除文件夾,MD5加密,類型識別與模塊加載等功能?,F(xiàn)在網(wǎng)站名與網(wǎng)站的路徑也還是混淆在里面,以后會獨(dú)立到一個(gè)配置文件中。只要運(yùn)行node mass.js這命令就立即從模板文件中構(gòu)建一個(gè)樣板網(wǎng)站出來。下面就是它建站的最主要代碼:
- //--------開始創(chuàng)建網(wǎng)站---------
- //你想建立的網(wǎng)站的名字(請修正這里)
- mass.appname = "jslouvre";
- //在哪個(gè)目錄下建立網(wǎng)站(請修正這里)
- mass.approot = process.cwd();
- //用于修正路徑的方法,可以傳N個(gè)參數(shù)
- mass.adjustPath = function(){
- [].unshift.call(arguments,mass.approot, mass.appname);
- return require("path").join.apply(null,arguments)
- }
- var dir = mass.adjustPath("")
- // mass.rmdirSync(dir);//......
- mass.require("http,fs,path,scaffold,intercepters",function(http,fs,path,scaffold,intercepters){
- mass.log("<code style="color:blue;">=========================</code>",true)
- if(path.existsSync(dir)){
- mass.log("<code style="color:red">此網(wǎng)站已存在</code>",true);
- }else{
- fs.mkdir(dir,0755)
- mass.log("<code style="color:green">開始利用內(nèi)部模板建立您的網(wǎng)站……</code>",true);
- }
- global.mapper = scaffold(dir);//取得路由系統(tǒng)
- http.createServer(function(req, res) {
- var arr = intercepters.concat();
- //有關(guān)HTTP狀態(tài)的解釋 http://www.cnblogs.com/rubylouvre/archive/2011/05/18/2049989.html
- req.on("err500",function(err){
- res.writeHead(500, {
- "Content-Type": "text/html"
- });
- var html = fs.readFileSync(mass.adjustPath("public/500.html"))
- var arr = []
- for(var i in err){
- arr.push("<li>"+i+" : "+err[i]+" </li>")
- }
- res.write((html+"").replace("{{url}}",arr.join("")));
- res.end();
- });
- req.on("next_intercepter",function(){
- try{
- var next = arr.shift();
- next && next.apply(null,arguments)
- }catch(err){
- req.emit("err500",err);
- }
- });
- req.emit("next_intercepter",req, res);
- }).listen(8888);
- console.log("start server in 8888 port")
- });
只要運(yùn)行mass.js,它會根據(jù)appname與approot判定目標(biāo)路徑是否存在此網(wǎng)站,沒有就創(chuàng)建相應(yīng)文件夾 fs.mkdir(dir,0755)。但更多的文件夾與文件是由scaffold.js完成的。scaffold里面?zhèn)€文件夾列表,用于讓程序從templates把相應(yīng)的文件夾拷貝到網(wǎng)站的路徑下,并建立505.html, 404.html, favicon.ico, routes.js等文件。其中最重頭的是routes,它是用來定義路由規(guī)則。
- //routes.js
- //最重要的部分,根據(jù)它生成controller, action, model, views
- mass.define("routes",function(){
- return function(map){
- //方法路由
- // map.get('/','site#index');
- // map.get('/get_comments/:post_id','site#get_comments');
- // map.post('/add_comment','site#add_comment');
- // //資源路由
- // map.resources('posts');
- // map.resources('users');
- // map.get('/view/:post_name','site#view_post');
- // map.get('/rss','site#rss');
- // map.resources('posts', {path: 'articles', as: 'stories'});
- //嵌套路由
- // map.resources('posts', function (post) {
- // post.resources('users');
- // });
- //命名空間路由
- map.namespace("tests",function(tests){
- tests.resources('comments');
- })
- // map.resources('users', {
- // only: ['index', 'show']
- // });
- //
- // map.resources('users', {
- // except: ['create', 'destroy']
- // });
- // map.resources('users', function (user) {
- // user.get('avatar', 'users#avatar');
- // });
- // map.root("home#index")
- }
- });
上面就是routes.js的所有內(nèi)容。允許建立五種路由:根路由,資源路由,方法路由(get,delete,put,post),命名空間路由,嵌套路由。其實(shí)它們統(tǒng)統(tǒng)都會歸化為資源路由,每個(gè)URL都對應(yīng)一個(gè)控制器與其下的action。它會調(diào)用router.js,讓里面的Router實(shí)例mapper調(diào)用router.js里面的內(nèi)容,然后返回mapper。
- //scaffold.js
- var routes_url = mass.adjustPath('config/routes.js'),
- action_url = "app/controllers/",
- view_url = "app/views/",
- mapper = new Router
- mass.require("routes("+routes_url+")",function(fn){//讀取routes.js配置文件
- fn(mapper)
- });
- //這里省掉,一會兒解說
- return mapper;
Router實(shí)例mapper在routes運(yùn)行完畢后,那么它的幾個(gè)屬性就會添加了N多成員與元素,我們再利用它來進(jìn)一步構(gòu)建我們的控制器,視圖與模型。
- //如 this.controllers = {};現(xiàn)在變?yōu)?
- { comments:
- { actions: [ 'index', 'create', 'new', 'edit', 'destroy', 'update', 'show' ],
- views: [ 'index', 'new', 'edit', 'show' ],
- namespace: 'tests' } }
- // this.GET = [];現(xiàn)在變?yōu)?
- [ { controller: 'comments',
- action: 'index',
- method: 'GET',
- namespace: '/tests/',
- url: '/tests/comments.:format?',
- helper: 'tests_comments',
- matcher: /^\/tests\/comments$/i },
- { controller: 'comments',
- action: 'new',
- method: 'GET',
- namespace: '/tests/',
- url: '/tests/comments/new.:format?',
- helper: 'new_tests_comments',
- matcher: /^\/tests\/comments\/new$/i },
- { controller: 'comments',
- action: 'edit',
- method: 'GET',
- namespace: '/tests/',
- url: '/tests/comments/:id/edit.:format?',
- helper: 'edit_tests_comment',
- matcher: /^\/tests\/comments\/\d+\/edit$/i },
- { controller: 'comments',
- action: 'show',
- method: 'GET',
- namespace: '/tests/',
- url: '/tests/comments/:id.:format?',
- helper: 'tests_comment',
- matcher: /^\/tests\/comments\/\d+$/i } ]
mapper有四個(gè)數(shù)組屬性,GET,POST,DELETE,PUT,我稱之為匹配棧,這些數(shù)組的元素都是一個(gè)個(gè)對象,對象都有一個(gè)matcher的正則屬性,就是用來匹配請求過來的URL的pathname屬性,當(dāng)然首先我們先取得其method,讓相應(yīng)的匹配棧去處理它。
現(xiàn)在手腳架scaffold.js還很簡鄙,以后它會結(jié)合熱部署功能,當(dāng)用戶修改routes.js或其他配置文件時(shí),它將會自動生成更多的視圖與控制器等等。
然后我們就啟動服務(wù)器了,由于req是EventEmitter的實(shí)例,因此我們可以隨意在上面綁定自定義事件,這里有兩個(gè)事件next_intercepter與err500。err500就不用說了,next_intercepter是用來啟動攔截器群集。這里我們只需要啟動***個(gè)。它在回調(diào)中會自動啟動下一個(gè)。這些攔截器是由intercepters.js 統(tǒng)一加載的。
- //intercepters.js
- mass.intercepter = function(fn){//攔截器的外殼
- return function(req, res, err){
- if(err ){
- req.emit("next_intercepter", req, res, err);
- }else if(fn(req,res) === true){
- req.emit("next_intercepter", req, res)
- }
- }
- }
- var deps = ["mime","postData","query","methodOverride","json","favicon","matcher","handle404"];//"more",
- mass.define("intercepters", deps.map(function(str){
- return "intercepters/"+str
- }).join(","), function(){
- console.log("取得一系列欄截器");
- return [].slice.call(arguments,0)
- });
每個(gè)攔截器都會對原始數(shù)據(jù)進(jìn)行處理,并決定是繼續(xù)啟用下一個(gè)攔截器。比如mime攔截器:
- mass.define("intercepters/mime",function(){
- console.log("本模塊用于取得MIME,并作為request.mime而存在");
- return mass.intercepter(function(req, res){
- console.log("進(jìn)入MIME回調(diào)");
- var str = req.headers['content-type'] || '';
- req.mime = str.split(';')[0];
- return true;
- })
- })
#p#
postData攔截器
- mass.define("intercepters/postData","querystring",function(qs){
- console.log("本模塊用于取得POST請求過來的數(shù)據(jù),并作為request.body而存在");
- return mass.intercepter(function(req,res){
- console.log("進(jìn)入postData回調(diào)");
- reqreq.body = req.body || {};
- if ( req._body || /GET|HEAD/.test(req.method) || 'application/x-www-form-urlencoded' !== req.mime ){
- return true;
- }
- var buf = '';
- req.setEncoding('utf8');
- function buildBuffer(chunk){
- buf += chunk
- }
- req.on('data', buildBuffer);
- req.once('end',function(){
- try {
- if(buf != ""){
- req.body = qs.parse(buf);
- req._body = true;
- }
- req.emit("next_intercepter",req,res)
- } catch (err){
- req.emit("next_intercepter",req,res,err)
- }finally{
- req.removeListener("data",buildBuffer)
- }
- })
- });
- });
query攔截器
- mass.define("intercepters/query","querystring,url",function(qs,URL){
- console.log("本模塊用于取得URL的參數(shù)并轉(zhuǎn)為一個(gè)對象,作為request.query而存在");
- return mass.intercepter(function(req, res){
- req.query = ~req.url.indexOf('?')
- ? qs.parse(URL.parse(req.url).query)
- : {};
- return true;
- })
- })
methodOverride攔截器
- mass.define("intercepters/methodOverride",function(){
- console.log("本模塊用于校正method屬性");
- var methods = {
- "PUT":"PUT",
- "DELETE":"DELETE"
- },
- method = mass.configs.method || "_method";
- return mass.intercepter(function(req, res){
- reqreq.originalMethod = req.method;
- var defaultMethod = req.method === "HEAD" ? "GET" : req.method;
- var _method = req.body ? req.body[method] : req.headers['x-http-method-override']
- _method = (_method || "").toUpperCase();
- req.method = methods[_method] || defaultMethod;
- if(req.body){
- delete req.body[method];
- }
- return true;
- })
- })
json攔截器
- mass.define("intercepters/json",function(){
- console.log("本模塊處理前端發(fā)過來的JSON數(shù)據(jù)");
- return mass.intercepter(function(req, res, err){
- reqreq.body = req.body || {};
- if (req._body || 'GET' == req.method || !~req.mime.indexOf("json")){
- console.log("進(jìn)入json回調(diào)")
- return true;
- }else{
- var buf = '';
- req.setEncoding('utf8');
- function buildBuffer(chunk){
- buf += chunk;
- }
- req.on('data', buildBuffer);
- req.once('end', function(){
- try {
- req.body = JSON.parse(buf);
- req._body = true;
- req.emit("next_intercepter",req,res);
- } catch (err){
- err.status = 400;
- req.emit("next_intercepter",req,res,err);
- }finally{
- req.removeListener("data",buildBuffer);
- }
- });
- }
- })
- })
而在這么多攔截器中,最重要的是matcher攔截器,它進(jìn)入框架MVC系統(tǒng)的入口。把原始請求的pathname取出來,然后通過正則匹配它,只要一個(gè)符合就停下來,然后加載對應(yīng)的控制器文件,調(diào)用相應(yīng)的action處理請求!
- mass.define("intercepters/matcher","url",function(URL){
- console.log("用于匹配請求過來的回調(diào)")
- return mass.intercepter(function(req,res){
- console.log("進(jìn)入matcher回調(diào)");
- var pathname = URL.parse(req.url).pathname, is404 = true,method = req.method, arr = mapper[method];
- for(var i =0, obj; obj = arr[i++];){
- if(obj.matcher.test(pathname)){
- is404 = false
- var url = mass.adjustPath("app/controllers/",obj.namespace, obj.controller+"_controller.js")
- mass.require(obj.controller+"_controller("+url +")",function(object){
- object[obj.action](req,res);//進(jìn)入控制器的action!!!
- console.log(obj.action)
- },function(){
- var err = new Error;
- err.statusCode = 404
- req.emit("next_intercepter",req,res,err);
- })
- break;
- }
- }
- if(is404){
- var err = new Error;
- err.statusCode = 404
- req.emit("next_intercepter",req,res,err);
- }
- })
- })
***殿后的是handle404攔截器:
- mass.define("intercepters/handle404","fs,path",function(fs){
- console.log("本模塊用于處理404錯(cuò)誤");
- return function(req, res, err){
- console.log("進(jìn)入handle404回調(diào)");
- var accept = req.headers.accept || '';
- if (~accept.indexOf('html')) {
- res.writeHead(404, {
- "Content-Type": "text/html"
- });
- var html = fs.readFileSync(mass.adjustPath("public/404.html"))
- res.write((html+"").replace("{{url}}",req.url));
- res.end();
- } else if (~accept.indexOf('json')) {//json
- var error = {
- message: err.message,
- stack: err.stack
- };
- for (var prop in err) error[prop] = err[prop];
- var json = JSON.stringify({
- error: error
- });
- res.setHeader('Content-Type', 'application/json');
- res.end(json);
- // plain text
- } else {
- res.writeHead(res.statusCode, {
- 'Content-Type': 'text/plain'
- });
- res.end(err.stack);
- }
- }
- })
再回過頭來看控制器部分,從模板中生成的controller非常簡單:
- mass.define("comments_controller",function(){
- return {
- "index":function(){},
- "create":function(){},
- "new":function(){},
- "edit":function(){},
- "destroy":function(){},
- "update":function(){},
- "show":function(){}
- }
- });
因此你需要動手改到其可用,如
- "show":function(req,res){
- res.writeHead(200, {
- "Content-Type": "text/html"
- });
- var html = fs.readFileSync(mass.adjustPath("app/views/tests/show.html"))
- res.write(html);
- res.end();
- }
以后會判定action的結(jié)果自動調(diào)用視圖。
當(dāng)然現(xiàn)在框架還很簡單,只用了半天時(shí)間而已。它必須支持ORM與靜態(tài)文件緩存才行。此外還有cookie,session等支持,這些做成一個(gè)攔截器就行了。
總結(jié)如下:
◆ 判定網(wǎng)站是否存在,沒有通過手腳架構(gòu)建一個(gè)
◆ 讀取routes等配置文件,生成MVC系統(tǒng)所需要的控制器,視圖與模型。
◆ 通過熱部署功能,監(jiān)視用戶對配置文件的修改,進(jìn)一步智能生成需要控制器,視圖與模型。
◆ 通過一系列攔截器處理請來,直到matcher攔截器里面進(jìn)入MVC系統(tǒng),這時(shí)通過模型操作數(shù)據(jù)庫,渲染頁面。攔截器群集的應(yīng)用大大提高應(yīng)用的伸縮性?,F(xiàn)在還沒有來得及得node.js的多線程,可能這里面能發(fā)掘出許多好東西呢。
原文:http://www.cnblogs.com/rubylouvre/archive/2011/12/13/2286280.html
【編輯推薦】