Node.js后端框架设计构想

开发 前端
我打算把我的后端的框架定位为建站框架,本文是我的一些思路与初步实践。如果园子里有做过后端框架的高手(不限语言),也请指教一下。以下是大概的流程。

后端的核心文件mass.js包含批量创建与删除文件夹,MD5加密,类型识别与模块加载等功能。现在网站名与网站的路径也还是混淆在里面,以后会独立到一个配置文件中。只要运行node mass.js这命令就立即从模板文件中构建一个样板网站出来。下面就是它建站的最主要代码:

//--------开始创建网站---------  
 //你想建立的网站的名字(请修正这里)  
 mass.appname = "jslouvre";  
 //在哪个目录下建立网站(请修正这里)  
 mass.approot = process.cwd();  
 //用于修正路径的方法,可以传N个参数  
 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">此网站已存在</code>",true);  
     }else{  
         fs.mkdir(dir,0755)  
         mass.log("<code style="color:green">开始利用内部模板建立您的网站……</code>",true);  
     }  
     global.mapper = scaffold(dir);//取得路由系统  
     http.createServer(function(req, res) {  
         var arr = intercepters.concat();  
         //有关HTTP状态的解释 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")  
 }); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.

只要运行mass.js,它会根据appname与approot判定目标路径是否存在此网站,没有就创建相应文件夹 fs.mkdir(dir,0755)。但更多的文件夹与文件是由scaffold.js完成的。scaffold里面个文件夹列表,用于让程序从templates把相应的文件夹拷贝到网站的路径下,并建立505.html, 404.html, favicon.ico, routes.js等文件。其中最重头的是routes,它是用来定义路由规则。

//routes.js  
//最重要的部分,根据它生成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")  
    }  
}); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

上面就是routes.js的所有内容。允许建立五种路由:根路由,资源路由,方法路由(get,delete,put,post),命名空间路由,嵌套路由。其实它们统统都会归化为资源路由,每个URL都对应一个控制器与其下的action。它会调用router.js,让里面的Router实例mapper调用router.js里面的内容,然后返回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; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

Router实例mapper在routes运行完毕后,那么它的几个属性就会添加了N多成员与元素,我们再利用它来进一步构建我们的控制器,视图与模型。

//如 this.controllers = {};现在变为  
{ comments:  
   { actions: [ 'index', 'create', 'new', 'edit', 'destroy', 'update', 'show' ],  
   
     views: [ 'index', 'new', 'edit', 'show' ],  
     namespace: 'tests' } }  
   
//   this.GET = [];现在变为  
[ { 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 } ] 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.

mapper有四个数组属性,GET,POST,DELETE,PUT,我称之为匹配栈,这些数组的元素都是一个个对象,对象都有一个matcher的正则属性,就是用来匹配请求过来的URL的pathname属性,当然首先我们先取得其method,让相应的匹配栈去处理它。

现在手脚架scaffold.js还很简鄙,以后它会结合热部署功能,当用户修改routes.js或其他配置文件时,它将会自动生成更多的视图与控制器等等。

然后我们就启动服务器了,由于req是EventEmitter的实例,因此我们可以随意在上面绑定自定义事件,这里有两个事件next_intercepter与err500。err500就不用说了,next_intercepter是用来启动拦截器群集。这里我们只需要启动***个。它在回调中会自动启动下一个。这些拦截器是由intercepters.js 统一加载的。

//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)  
}); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

每个拦截器都会对原始数据进行处理,并决定是继续启用下一个拦截器。比如mime拦截器:

mass.define("intercepters/mime",function(){  
    console.log("本模块用于取得MIME,并作为request.mime而存在");  
    return mass.intercepter(function(req, res){  
        console.log("进入MIME回调");  
        var str = req.headers['content-type'] || '';  
        req.mime = str.split(';')[0];  
        return true;  
    })  
}) 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

#p#

postData拦截器

mass.define("intercepters/postData","querystring",function(qs){  
    console.log("本模块用于取得POST请求过来的数据,并作为request.body而存在");  
    return mass.intercepter(function(req,res){  
        console.log("进入postData回调");  
        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)  
            }  
        })  
    });  
}); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

query拦截器

mass.define("intercepters/query","querystring,url",function(qs,URL){  
    console.log("本模块用于取得URL的参数并转为一个对象,作为request.query而存在");  
    return mass.intercepter(function(req, res){  
        req.query = ~req.url.indexOf('?')  
        ? qs.parse(URL.parse(req.url).query)  
        : {};  
        return true;  
    })  
}) 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

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;  
    })  
}) 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

json拦截器

mass.define("intercepters/json",function(){  
    console.log("本模块处理前端发过来的JSON数据");  
    return mass.intercepter(function(req, res, err){  
        reqreq.body = req.body || {};  
        if (req._body  || 'GET' == req.method || !~req.mime.indexOf("json")){  
            console.log("进入json回调")  
            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);  
                }  
            });  
        }  
    })  
}) 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

而在这么多拦截器中,最重要的是matcher拦截器,它进入框架MVC系统的入口。把原始请求的pathname取出来,然后通过正则匹配它,只要一个符合就停下来,然后加载对应的控制器文件,调用相应的action处理请求!

mass.define("intercepters/matcher","url",function(URL){  
    console.log("用于匹配请求过来的回调")  
    return mass.intercepter(function(req,res){  
        console.log("进入matcher回调");  
        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);//进入控制器的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);  
        }  
    })  
}) 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

***殿后的是handle404拦截器:

mass.define("intercepters/handle404","fs,path",function(fs){  
    console.log("本模块用于处理404错误");  
    return function(req, res, err){  
        console.log("进入handle404回调");  
        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);  
        }  
    }  
}) 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.

再回过头来看控制器部分,从模板中生成的controller非常简单:

mass.define("comments_controller",function(){  
    return {  
        "index":function(){},  
        "create":function(){},  
        "new":function(){},  
        "edit":function(){},  
        "destroy":function(){},  
        "update":function(){},  
        "show":function(){}  
    }  
 }); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

因此你需要动手改到其可用,如

"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();  
               

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

以后会判定action的结果自动调用视图。

当然现在框架还很简单,只用了半天时间而已。它必须支持ORM与静态文件缓存才行。此外还有cookie,session等支持,这些做成一个拦截器就行了。

总结如下:

◆ 判定网站是否存在,没有通过手脚架构建一个

◆ 读取routes等配置文件,生成MVC系统所需要的控制器,视图与模型。

◆ 通过热部署功能,监视用户对配置文件的修改,进一步智能生成需要控制器,视图与模型。

◆ 通过一系列拦截器处理请来,直到matcher拦截器里面进入MVC系统,这时通过模型操作数据库,渲染页面。拦截器群集的应用大大提高应用的伸缩性。现在还没有来得及得node.js的多线程,可能这里面能发掘出许多好东西呢。

原文:http://www.cnblogs.com/rubylouvre/archive/2011/12/13/2286280.html

【编辑推荐】

  1. 用Web socket和Node.js实现HTML 5画布的实时绘图
  2. 走近Node.js的异步代码设计
  3. 使用Node.js开发多人玩的HTML 5游戏
  4. Node.js提速指南
  5. Node.js初体验
责任编辑:陈贻新 来源: 司徒正美的博客
相关推荐

2020-04-20 16:00:05

Node.js框架JavaScript

2020-05-29 15:33:28

Node.js框架JavaScript

2019-08-29 10:58:02

Web 开发框架

2020-12-28 19:13:11

Node.js后端框架

2024-03-15 09:26:59

2012-03-07 14:32:41

Node.js

2014-10-30 10:28:55

Node.js

2013-11-01 09:34:56

Node.js技术

2015-03-10 10:59:18

Node.js开发指南基础介绍

2011-12-23 13:58:57

node.js

2020-07-15 08:06:04

Node.js框架开发

2022-10-18 18:43:40

Node.js低代码

2022-07-14 08:16:24

Node.js后端自动化测试

2021-01-11 09:00:00

开发Node.js后端

2011-09-08 13:46:14

node.js

2011-11-01 10:30:36

Node.js

2011-09-09 14:23:13

Node.js

2011-09-02 14:47:48

Node

2017-06-28 08:31:11

Node.jsMVC微服务

2012-10-24 14:56:30

IBMdw
点赞
收藏

51CTO技术栈公众号