node.js 后端框架設計構想

webphp 12年前發布 | 2K 次閱讀

我打算把我的后端的框架定位為建站框架,本文是我的一些思路與初步實踐。如果園子里有做過后端框架的高手(不限語言),也請指教一下。以下是大概的流程。

后端的核心文件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]+" ")
                }
                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")
    });

只要運行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")
    }
});

上面就是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;

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 } ]

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)
});

每個攔截器都會對原始數據進行處理,并決定是繼續啟用下一個攔截器。比如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;
    })
})

postData攔截器

query攔截器

methodOverride攔截器

json攔截器

而在這么多攔截器中,最重要的是matcher攔截器,它進入框架MVC系統的入口。把原始請求的pathname取出來,然后通過正則匹配它,只要一個符合就停下來,然后加載對應的控制器文件,調用相應的action處理請求!

最后殿后的是handle404攔截器:

再回過頭來看控制器部分,從模板中生成的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的結果自動調用視圖。

當然現在框架還很簡單,只用了半天時間而已。它必須支持ORM與靜態文件緩存才行。此外還有cookie,session等支持,這些做成一個攔截器就行了。

總結如下:

  • 判定網站是否存在,沒有通過手腳架構建一個
  • 讀取routes等配置文件,生成MVC系統所需要的控制器,視圖與模型。
  • 通過熱部署功能,監視用戶對配置文件的修改,進一步智能生成需要控制器,視圖與模型。
  • 通過一系列攔截器處理請來,直到matcher攔截器里面進入MVC系統,這時通過模型操作數據庫,渲染頁面。攔截器群集的應用大大提高應用的伸縮性。現在還沒有來得及得node.js的多線程,可能這里面能發掘出許多好東西呢。

相關代碼我稍晚會上傳到github中。。。

基本就是這樣,希望大家踴躍參與討論。

 本文由用戶 webphp 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!