node.js 後端架構設計構想

來源:互聯網
上載者:User

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

後端的核心檔案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("=========================",true)        if(path.existsSync(dir)){            mass.log("此網站已存在",true);        }else{            fs.mkdir(dir,0755)            mass.log("開始利用內部模板建立您的網站……",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("
  • "+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, viewsmass.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.jsmass.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攔截器

    mass.define("intercepters/postData","querystring",function(qs){    console.log("本模組用於取得POST請求過來的資料,並作為request.body而存在");    return mass.intercepter(function(req,res){        console.log("進入postData回調");        req.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的參數並轉為一個對象,作為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){        req.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("本模組處理前端發過來的JSON資料");    return mass.intercepter(function(req, res, err){        req.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);                }            });        }    })})

    而在這麼多攔截器中,最重要的是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);        }    })})

    最後殿後的是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);        }    }})

    再回過頭來看控制器部分,從模板中產生的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中。。。

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

    聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.