iT邦幫忙

DAY 11
5

node.js伺服器實戰系列 第 11

node.js伺服器實戰(11) - 加入router機制

即使可以與檔案系統對應,對於一般使用上還是非常不足的。除了靜態網頁,還需要一個backend來處理資料,才是一個比較完整的伺服器。
進一步開發的需求

node.js的request事件處理函數,可以接收到ServerRequest物件,裡面包含了完整的http request資訊。利用這些資訊,其實可以制定出簡單的規則,讓伺服器知道什麼時候要回應靜態檔案,什麼時候需要用程式處理資訊。一個典型的http request大概會長的像這樣:

 GET /index.html HTTP/1.1
 Host: www.w3c.org
 ......

在node.js的http.ServerRequest物件可以找到這些資訊(利用之前寫的probe程式來取得API資訊):

上圖中可以利用的資訊:

  1. 利用host,可以知道使用者要access是哪個host,利用這個資訊,可以建立虛擬伺服器
  2. 利用url,可以知道要access的資源路徑
  3. 利用method,可以知道使用的http method

簡單的router機制

有了這些資訊,就可以制定出一套簡單的規則:

  1. 用一個host handler來處理不同的host
  2. 用url可以知道要routing到哪個路徑
  3. 用method,可以進一步區分出handler要處理的http method
  4. 利用上述資訊,註冊handler函數,接收http.ServerRequest與http.ServerResponse物件作為參數
  5. 所有資訊,可以利用一個物件來存放,收到這些資訊時,用上面的資訊來查詢,如果查不到可以使用的handler,再轉向檔案系統對應的handler。

為了使用方便,可以考慮使用fluent interface的方式來設計:

  1. 使用host([host name])來起始規則,所有要加到規則中的其他方法必須在之後呼叫。如果有傳host name參數,則之後的規則只應用在這個host中處理
  2. 使用map([server path], [fs path])來對應檔案系統到伺服器路徑
  3. 使用get([server path], function(request, response){...})來處理使用GET method,對應到某伺服器路徑的handler
  4. 其他http method依此類推,callback的介面也一樣

實作router

程式比較複雜,需要整合router與之前的檔案系統對應機制,先寫好一個獨立的router機制,用來依照條件存放各個route的處理函數,以及不同host所需對應到不同路徑的檔案系統目錄。

router.js

 var path = require('path'),
 cache = require('./cache');
 
 function addhost(host) {
     host = host || 'localhost';
     cache.regist(host);
     cache.put(host, 'fs', {});
     cache.put(host, 'route', {});
 }
 
 function addfs(host, path, fs) {
     if(path.indexOf('/')!==0) path = '/' + path;
     var a = cache.get(host, 'fs');
     if(a) {
         a[path] = fs;
     }
 }
 
 function addroute(host, path, method, fn) {
     if(path.indexOf('/')!==0) path = '/' + path;
     var a = cache.get(host, 'route');
     if(a) {
         if(!a[path]) a[path] = {};
         a[path][method] = fn;
     }
 }
 
 function query(host, dir, method) {
     if(dir.indexOf('/')!==0) dir = '/' + dir;
     var a = cache.get(host, 'route');
     if(a && a[dir] && a[dir][method]) {
         return {
             type: 'route',
             result: a[dir][method]
         };
     }
     var a = cache.get(host, 'fs');
     if(a) {
         for(var i in a) {
             if(dir.indexOf(i)===0) {
                 return {
                     type: 'fs',
                     result: path.join(a[i], dir.substr(i.length, dir.length))
                 };
             }
         }
     }
 }
 
 module.exports = {
     addhost: addhost,
     addfs: addfs,
     addroute: addroute,
     query: query
 };

接下來,還需要調整evolve.js:

(程式碼太長,請到github看囉XD
https://github.com/fillano/evolve/blob/eba643263f2c2b306c38deab7a2b5b082ca97845/lib/evolve.js

在evolve.js中,加入了一個dispatch函數,利用router.query()來查詢request url的route,如果是定義好的route handler,就交給它處理,否則交給getRes處理。(為了測試記憶體使用量,所以稍微調整了輸出資訊,用ab -n20000 http://localhost:8443/來測試可以看到v8做gc使得記憶體使用量上上下下變化)

使用router

實做時又調整了一下使用規則,傳入一個callback來處理要輸出的結果,這樣跟昨天寫好的程式就可以搭配:

 var evolve = require('../lib/evolve');
 var path = require('path');
 var server = new evolve({
     dirindex: ['index.html', 'index.htm', 'default.htm']
 });
 server.host('localhost:8443')
       .map('/', path.join(__dirname, '../www'))
       .get('/hello', function(request, response, cb) {
           cb(false, '/hello', {
               type: 'text/html',
               data: 'hello'
           });
       });
 server.listen(8443, 'localhost');

使用起來應該還不會太複雜,而且使用ab測試了一下,跟昨天的效能差不多。

不過問題是,伺服器程式本身越來越複雜,也越來越難測試。為了能進一步開發,必須有比較好的方法,才能一步一步前進...這個就留到明天來想一想。

相關文章


上一篇
node.js伺服器實戰(10) - 加入cache機制
下一篇
node.js伺服器實戰(12) - 開發效率與品質問題
系列文
node.js伺服器實戰33

尚未有邦友留言

立即登入留言