成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

express源碼閱讀

liangdas / 1814人閱讀

摘要:函數(shù)表示增加一個(gè)路由,函數(shù)表示監(jiān)聽服務(wù)器。內(nèi)部創(chuàng)建一個(gè)叫做的類,并為該類添加兩個(gè)方法,和。建立類,并將原來(lái)內(nèi)的代碼移動(dòng)到類中。內(nèi)部在寫代碼之前,先梳理一下上面所有的概念之間的關(guān)系和。代表一個(gè)應(yīng)用程序。修改原有的函數(shù)。

express源碼閱讀

簡(jiǎn)介:這篇文章的主要目的是分析express的源碼,但是網(wǎng)絡(luò)上express的源碼評(píng)析已經(jīng)數(shù)不勝數(shù),所以本文章另辟蹊徑,準(zhǔn)備仿制一個(gè)express的輪子,當(dāng)然輪子的主體思路是閱讀express源碼所得。

源碼地址:expross

1. 搭建結(jié)構(gòu)

有了想法,下一步就是搭建一個(gè)山寨的框架,萬(wàn)事開頭難,就從建立一個(gè)文件夾開始吧!

首先建立一個(gè)文件夾,叫做expross(你沒(méi)有看錯(cuò),山寨從名稱開始)。

expross
 |
 |-- application.js

接著創(chuàng)建application.js文件,文件的內(nèi)容就是官網(wǎng)的例子。

var http = require("http");

http.createServer(function(req, res) {
  res.writeHead(200, {"Content-Type": "text/plain"});
  res.end("Hello World");
}).listen(3000);

一個(gè)簡(jiǎn)單的http服務(wù)就創(chuàng)建完成了,你可以在命令行中啟動(dòng)它,而expross框架的搭建就從這個(gè)文件出發(fā)。

1.1 第一劃 Application

在實(shí)際開發(fā)過(guò)程中,web后臺(tái)框架的兩個(gè)核心點(diǎn)就是路由和模板。路由說(shuō)白了就是一組URL的管理,根據(jù)前端訪問(wèn)的URL執(zhí)行對(duì)應(yīng)的處理函數(shù)。怎樣管理一組URL和其對(duì)應(yīng)的執(zhí)行函數(shù)呢?首先想到的就是數(shù)組(其實(shí)我想到的是對(duì)象)。

創(chuàng)建一個(gè)名稱叫做router的數(shù)組對(duì)象。

var http = require("http");

//路由
var router = [];
router.push({path: "*", fn: function(req, res) {
    res.writeHead(200, {"Content-Type": "text/plain"});
    res.end("404");
}}, {path: "/", fn: function(req, res) {
    res.writeHead(200, {"Content-Type": "text/plain"});
    res.end("Hello World");
}});


http.createServer(function(req, res) {
    //自動(dòng)匹配
    for(var i=1,len=router.length; i

router數(shù)組用來(lái)管理所有的路由,數(shù)組的每個(gè)對(duì)象有兩個(gè)屬性組成,path表示路徑,fn表示路徑對(duì)應(yīng)的執(zhí)行函數(shù)。一切看起來(lái)都很不錯(cuò),但是這并不是一個(gè)框架,為了組成一個(gè)框架,并且貼近express,這里繼續(xù)對(duì)上面的代碼進(jìn)一步封裝。

首先定義一個(gè)類:Application

var Application = function() {}

在這個(gè)類上定義二個(gè)函數(shù):

Application.prototype.use = function(path, cb) {};

Application.prototype.listen = function(port) {};

把上面的實(shí)現(xiàn),封裝到這個(gè)類中。use 函數(shù)表示增加一個(gè)路由,listen 函數(shù)表示監(jiān)聽http服務(wù)器。

var http = require("http");

var Application = function() {
    this.router = [{
        path: "*", 
        fn: function(req, res) {
            res.writeHead(200, {"Content-Type": "text/plain"});
            res.end("Cannot " + req.method + " " + req.url);
        }
    }];
};

Application.prototype.use = function(path, cb) {
    this.router.push({
        path: path,
        fn: cb
    });
};

Application.prototype.listen = function(port) {
    var self = this;
    http.createServer(function(req, res) {
        for(var i=1,len=self.router.length; i

可以像下面這樣啟動(dòng)它:

var app = new Application();
app.use("/", function(req, res) {
    res.writeHead(200, {"Content-Type": "text/plain"});
    res.end("Hello World");
});
app.listen(3000);

看樣子已經(jīng)和express的外觀很像了,為了更像,這里創(chuàng)建一個(gè)expross的文件,該文件用來(lái)實(shí)例化Application。代碼如下:

var Application = require("./application");
exports = module.exports = createApplication;

function createApplication() {
    var app = new Application();
    return app;
}

為了更專業(yè),調(diào)整目錄結(jié)構(gòu)如下:

-----expross
 | |
 | |-- index.js
 | |
 | |-- lib
 |      |
 |      |-- application.js
 |      |-- expross.js
 |
 |---- test.js

運(yùn)行node test.js,走起……

1.2 第二劃 Layer

為了進(jìn)一步優(yōu)化代碼,這里抽象出一個(gè)概念:Layer。代表層的含義,每一層就是上面代碼中的router數(shù)組的一個(gè)項(xiàng)。

Layer含有兩個(gè)成員變量,分別是path和handle,path代表路由的路徑,handle代表路由的處理函數(shù)fn。

------------------------------------------------
|     0     |     1     |     2     |     3     |      
------------------------------------------------
| Layer     | Layer     | Layer     | Layer     |
|  |- path  |  |- path  |  |- path  |  |- path  |
|  |- handle|  |- handle|  |- handle|  |- handle|
------------------------------------------------
                  router 內(nèi)部

創(chuàng)建一個(gè)叫做layer的類,并為該類添加兩個(gè)方法,handle_requestmatchmatch用來(lái)匹配請(qǐng)求路徑是否符合該層,handle_request用來(lái)執(zhí)行路徑對(duì)應(yīng)的處理函數(shù)。

function Layer(path, fn) {
    this.handle = fn;
    this.name = fn.name || "";
    this.path = path;
}
//簡(jiǎn)單處理
Layer.prototype.handle_request = function (req, res) {
  var fn = this.handle;

  if(fn) {
      fn(req, res);
  }
}
//簡(jiǎn)單匹配
Layer.prototype.match = function (path) {
    if(path === this.path) {
        return true;
    }
    
    return false;
}

因?yàn)閞outer數(shù)組中存放的將是Layer對(duì)象,所以修改Application.prototype.use代碼如下:

Application.prototype.use = function(path, cb) {
    this.router.push(new Layer(path, cb));
};

當(dāng)然也不要忘記Application構(gòu)造函數(shù)的修改。

var Application = function() {
    this.router = [new Layer("*", function(req, res) {
        res.writeHead(200, {"Content-Type": "text/plain"});
        res.end("Cannot " + req.method + " " + req.url);
    })];
};

接著改變listen函數(shù),將其主要的處理邏輯抽取成handle函數(shù),用來(lái)匹配處理請(qǐng)求信息。這樣可以讓函數(shù)本身的語(yǔ)意更明確,并且遵守單一原則。

Application.prototype.handle = function(req, res) {
    var self = this;

    for(var i=0,len=self.router.length; i

listen函數(shù)變得簡(jiǎn)單明了。

Application.prototype.listen = function(port) {
    var self = this;

    http.createServer(function(req, res) {
        self.handle(req, res);
    }).listen(port);
};

運(yùn)行node test.js,走起……

1.3 第三劃 router

Application類中,成員變量router負(fù)責(zé)存儲(chǔ)應(yīng)用程序的所有路由和其處理函數(shù),既然存在這樣一個(gè)對(duì)象,為何不將其封裝成一個(gè)Router類,這個(gè)類負(fù)責(zé)管理所有的路由,這樣職責(zé)更加清晰,語(yǔ)意更利于理解。

so,這里抽象出另一個(gè)概念:Router,代表一個(gè)路由組件,包含若干層的信息。

建立Router類,并將原來(lái)Application內(nèi)的代碼移動(dòng)到Router類中。

var Router = function() {
    this.stack = [new Layer("*", function(req, res) {
        res.writeHead(200, {"Content-Type": "text/plain"});
        res.end("Cannot " + req.method + " " + req.url);
    })];
};

Router.prototype.handle = function(req, res) {
    var self = this;

    for(var i=0,len=self.stack.length; i

為了利于管理,現(xiàn)將路由相關(guān)的文件放到一個(gè)目錄中,命名為router。將Router類文件命名為index.js保存到router文件夾內(nèi),并將原來(lái)的layer.js移動(dòng)到該文件夾?,F(xiàn)目錄結(jié)構(gòu)如下:

-----expross
 | |
 | |-- index.js
 | |
 | |-- lib
 |      |
 |      |-- router
 |      |     |
 |      |     |-- index.js
 |      |     |-- layer.js
 |      |     
 |      |
 |      |-- application.js
 |      |-- expross.js
 |
 |---- test.js

修改原有application.js文件,將代碼原有router的數(shù)組移除,新增加_router對(duì)象,該對(duì)象是Router類的一個(gè)實(shí)例。

var Application = function() {
    this._router = new Router();
};

Application.prototype.use = function(path, fn) {
    var router = this._router;
    return router.use(path, fn);
};

Application.prototype.handle = function(req, res) {
    var router = this._router;
    router.handle(req, res);
};

到現(xiàn)在為止,整體的框架思路已經(jīng)非常的明確,一個(gè)應(yīng)用對(duì)象包括一個(gè)路由組件,一個(gè)路由組件包括n個(gè)層,每個(gè)層包含路徑和處理函數(shù)。每次請(qǐng)求就遍歷應(yīng)用程序指向的路由組件,通過(guò)層的成員函數(shù)match來(lái)進(jìn)行匹配識(shí)別URL訪問(wèn)的路徑,如果成功則調(diào)用層的成員函數(shù)handle_request進(jìn)行處理。

運(yùn)行node test.js,走起……

1.4 第四劃 route

如果研究過(guò)路由相關(guān)的知識(shí)就會(huì)發(fā)現(xiàn),路由其實(shí)是由三個(gè)參數(shù)構(gòu)成的:請(qǐng)求的URI、HTTP請(qǐng)求方法和路由處理函數(shù)。之前的代碼只處理了其中兩種,對(duì)于HTTP請(qǐng)求方法這個(gè)參數(shù)卻刻意忽略,現(xiàn)在是時(shí)候把它加進(jìn)來(lái)了。

按照上面的結(jié)構(gòu),如果加入請(qǐng)求方法參數(shù),肯定會(huì)加入到Layer里面。但是再加入之前,需要仔細(xì)分析一下路由的常見方式:

GET        /pages
GET     /pages/1
POST    /page
PUT     /pages/1
DELETE     /pages/1

HTTP的請(qǐng)求方法有很多,上面的路由列表是一組常見的路由樣式,遵循REST原則。分析一下會(huì)發(fā)現(xiàn)大部分的請(qǐng)求路徑其實(shí)是相似或者是一致的,如果將每個(gè)路由都建立一個(gè)Layer添加到Router里面,從效率或者語(yǔ)意上都稍微有些不符,因?yàn)樗麄兪且唤MURL,負(fù)責(zé)管理page相關(guān)信息的URL,能否把這樣類似訪問(wèn)路徑相同而請(qǐng)求方法不同的路由劃分到一個(gè)組里面呢?

答案是可以行的,這就需要再次引入一個(gè)概念:route,專門來(lái)管理具體的路由信息。

------------------------------------------------
|     0     |     1     |     2     |     3     |      
------------------------------------------------
| item      | item      | item      | item      |
|  |- method|  |- method|  |- method|  |- method|
|  |- handle|  |- handle|  |- handle|  |- handle|
------------------------------------------------
                  route 內(nèi)部

在寫代碼之前,先梳理一下上面所有的概念之間的關(guān)系:application、expross、router、route和layer。

 --------------
| Application  |                                 ---------------------------------------------------------
|     |           |        ----- -----------        |     0     |     1     |     2     |     3     |  ...  |
|      |-router | ----> |     | Layer     |       ---------------------------------------------------------
 --------------        |  0  |   |-path  |       | item      | item      | item      | item      |       |
  application           |     |   |-route | ----> |  |- method|  |- method|  |- method|  |- method|  ...  |
                       |-----|-----------|       |  |- handle|  |- handle|  |- handle|  |- handle|       |
                       |     | Layer     |       ---------------------------------------------------------
                       |  1  |   |-path  |                                  route
                       |     |   |-route |       
                       |-----|-----------|       
                       |     | Layer     |
                       |  2  |   |-path  |
                       |     |   |-route |
                       |-----|-----------|
                       | ... |   ...     |
                        ----- ----------- 
                             router

application代表一個(gè)應(yīng)用程序。expross是一個(gè)工廠類負(fù)責(zé)創(chuàng)建application對(duì)象。router是一個(gè)路由組件,負(fù)責(zé)整個(gè)應(yīng)用程序的路由系統(tǒng)。route是路由組件內(nèi)部的一部分,負(fù)責(zé)存儲(chǔ)真正的路由信息,內(nèi)部的每一項(xiàng)都代表一個(gè)路由處理函數(shù)。router內(nèi)部的每一項(xiàng)都是一個(gè)layer對(duì)象,layer內(nèi)部保存一個(gè)route和其代表的URI。

如果一個(gè)請(qǐng)求來(lái)臨,會(huì)現(xiàn)從頭至尾的掃描router內(nèi)部的每一層,而處理每層的時(shí)候會(huì)先對(duì)比URI,匹配掃描route的每一項(xiàng),匹配成功則返回具體的信息,沒(méi)有任何匹配則返回未找到。

創(chuàng)建Route類,定義三個(gè)成員變量和三個(gè)方法。path代表該route所對(duì)應(yīng)的URI,stack代表上圖中route內(nèi)部item所在的數(shù)組,methods用來(lái)快速判斷該route中是是否存在某種HTTP請(qǐng)求方法。

var Route = function(path) {
    this.path = path;
    this.stack = [];

    this.methods = {};
};

Route.prototype._handles_method = function(method) {
    var name = method.toLowerCase();
    return Boolean(this.methods[name]);
};

Route.prototype.get = function(fn) {
    var layer = new Layer("/", fn);
    layer.method = "get";

    this.methods["get"] = true;
    this.stack.push(layer);

    return this;
};

Route.prototype.dispatch = function(req, res) {
    var self = this,
        method = req.method.toLowerCase();

    for(var i=0,len=self.stack.length; i

在上面的代碼中,并沒(méi)有定義前面結(jié)構(gòu)圖中的item對(duì)象,而是使用了Layer對(duì)象進(jìn)行替代,主要是為了方便快捷,從另一種角度看,其實(shí)二者是存在很多共同點(diǎn)的。另外,為了利于理解,代碼中只實(shí)現(xiàn)了GET方法,其他方法的代碼實(shí)現(xiàn)是類似的。

既然有了Route類,接下來(lái)就改修改原有的Router類,將route集成其中。

Router.prototype.handle = function(req, res) {
    var self = this,
        method = req.method;

    for(var i=0,len=self.stack.length; i

代碼中,暫時(shí)去除use方法,創(chuàng)建get方法用來(lái)添加請(qǐng)求處理函數(shù),route方法是為了返回一個(gè)新的Route對(duì)象,并將改層加入到router內(nèi)部。

最后修改Application類中的函數(shù),去除use方法,加入get方法進(jìn)行測(cè)試。

Application.prototype.get = function(path, fn) {
    var router = this._router;
    return router.get(path, fn);
};

Application.prototype.route = function (path) {
  return this._router.route(path);
};

測(cè)試代碼如下:

var expross = require("./expross");
var app = expross();

app.get("/", function(req, res) {
    res.writeHead(200, {"Content-Type": "text/plain"});
    res.end("Hello World");
});

app.route("/book")
.get(function(req, res) {
    res.writeHead(200, {"Content-Type": "text/plain"});
    res.end("Get a random book");
});

app.listen(3000);

運(yùn)行node test.js,走起……

1.5 第五劃 next

next 主要負(fù)責(zé)流程控制。在實(shí)際的代碼中,有很多種情況都需要進(jìn)行權(quán)限控制,例如:

var expross = require("./expross");
var app = expross();

app.get("/", function(req, res) {
    res.writeHead(200, {"Content-Type": "text/plain"});
    res.end("first");
});

app.get("/", function(req, res) {
    res.writeHead(200, {"Content-Type": "text/plain"});
    res.end("second");
});

app.listen(3000);

上面的代碼如果執(zhí)行會(huì)發(fā)現(xiàn)永遠(yuǎn)都返回first,但是有的時(shí)候會(huì)根據(jù)前臺(tái)傳來(lái)的參數(shù)動(dòng)態(tài)判斷是否執(zhí)行接下來(lái)的路由,怎樣才能跳過(guò)first進(jìn)入secondexpress引入了next的概念。

跳轉(zhuǎn)到任意layer,成本是比較高的,大多數(shù)的情況下并不需要。在express中,next跳轉(zhuǎn)函數(shù),有兩種類型:

跳轉(zhuǎn)到下一個(gè)處理函數(shù)。執(zhí)行 next()

跳轉(zhuǎn)到下一組route。執(zhí)行 next("route")

要想使用next的功能,需要在代碼書寫的時(shí)候加入該參數(shù):

var expross = require("./expross");
var app = expross();

app.get("/", function(req, res, next) {
    console.log("first");
    next();
});

app.get("/", function(req, res) {
    res.writeHead(200, {"Content-Type": "text/plain"});
    res.end("second");
});

app.listen(3000);

而該功能的實(shí)現(xiàn)也非常簡(jiǎn)單,主要是在調(diào)用處理函數(shù)的時(shí)候,除了需要傳入req、res之外,再傳一個(gè)流程控制函數(shù)next。

Router.prototype.handle = function(req, res) {
    var self = this,
        method = req.method,
        i = 1, len = self.stack.length,
        stack;

    function next() {
        if(i >= len) {        
            return self.stack[0].handle_request(req, res);
        }

        stack = self.stack[i++];

        if(stack.match(req.url) && stack.route 
            && stack.route._handles_method(method)) {
            return stack.handle_request(req, res, next);
        } else {
            next();
        }
    }

    next();
};

修改原有Router的handle函數(shù)。因?yàn)橐刂屏鞒?,所以for循環(huán)并不是很合適,可以更換為while循環(huán),或者干脆使用類似遞歸的手法。

代碼中定義一個(gè)next函數(shù),然后執(zhí)行next函數(shù)進(jìn)行自啟動(dòng)。next內(nèi)部和之前的操作類似,主要是執(zhí)行handle_request函數(shù)進(jìn)行處理,不同之處是調(diào)用該函數(shù)的時(shí)候,將next本身當(dāng)做參數(shù)傳入,這樣可以在內(nèi)部執(zhí)行該函數(shù)進(jìn)行下一個(gè)處理,類似給handle_request賦予for循環(huán)中++的能力。

按照相同的方式,修改Route的dispatch函數(shù)。

Route.prototype.dispatch = function(req, res, done) {
    var self = this,
        method = req.method.toLowerCase(),
        i = 0, len = self.stack.length, stack;

    function next(gt) {
        if(gt === "route") {
            return done();
        }

        if(i >= len) {
            return done();
        }

        stack = self.stack[i++];

        if(method === stack.method) {
            return stack.handle_request(req, res, next);
        } else {
            next();
        }        
    }

    next();
};

代碼思路基本和上面的相同,唯一的差別就是增加route判斷,提供跳過(guò)當(dāng)前整組處理函數(shù)的能力。

Layer.prototype.handle_request = function (req, res, next) {
  var fn = this.handle;

  if(fn) {
      fn(req, res, next);
  }
}

Router.prototype.route = function route(path) {
    var route = new Route(path);

    var layer = new Layer(path, function(req, res, next) {
        route.dispatch(req, res, next)
    });

    layer.route = route;

    this.stack.push(layer);
    return route;
};

最后不要忘記修改Layer的handle_request函數(shù)和Router的route函數(shù)。

1.6 后記

該小結(jié)基本結(jié)束,當(dāng)然如果要繼續(xù)還可以寫很多內(nèi)容,包括錯(cuò)誤處理、函數(shù)重載、高階函數(shù)(生成各種HTTP函數(shù)),以及各種神奇的用法,如繼承、緩存、復(fù)用等等。

但是我覺(jué)得搭建結(jié)構(gòu)這一結(jié)已經(jīng)將express的基本結(jié)構(gòu)捋清了,如果重頭到尾的走下來(lái),再去讀框架的源碼應(yīng)該是沒(méi)有問(wèn)題的。

接下來(lái)繼續(xù)山寨express 的其他部分。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/79822.html

相關(guān)文章

  • vue仿追書神器,vue小說(shuō)項(xiàng)目源碼

    摘要:一點(diǎn)閱讀器源自追書神器,免費(fèi)使用目前已初步開發(fā)完成項(xiàng)目地址歡迎,,推薦一個(gè)之前用文章類閱讀寫的一點(diǎn)閱讀微信小程序一點(diǎn)文章已上線,可以再微信搜索一點(diǎn)文章體驗(yàn)在線體驗(yàn)地址點(diǎn)擊這里體驗(yàn)服務(wù)器太,渲染慢部分效果截圖一點(diǎn)閱讀器優(yōu)勢(shì)一點(diǎn)閱讀器追書神 vue-reader 一點(diǎn)閱讀器!API源自追書神器,免費(fèi)使用!目前已初步開發(fā)完成! Github項(xiàng)目地址:https://github.com/An...

    justCoding 評(píng)論0 收藏0
  • vue仿追書神器,vue小說(shuō)項(xiàng)目源碼

    摘要:一點(diǎn)閱讀器源自追書神器,免費(fèi)使用目前已初步開發(fā)完成項(xiàng)目地址歡迎,,推薦一個(gè)之前用文章類閱讀寫的一點(diǎn)閱讀微信小程序一點(diǎn)文章已上線,可以再微信搜索一點(diǎn)文章體驗(yàn)在線體驗(yàn)地址點(diǎn)擊這里體驗(yàn)服務(wù)器太,渲染慢部分效果截圖一點(diǎn)閱讀器優(yōu)勢(shì)一點(diǎn)閱讀器追書神 vue-reader 一點(diǎn)閱讀器!API源自追書神器,免費(fèi)使用!目前已初步開發(fā)完成! Github項(xiàng)目地址:https://github.com/An...

    happyhuangjinjin 評(píng)論0 收藏0
  • vue仿追書神器,vue小說(shuō)項(xiàng)目源碼

    摘要:一點(diǎn)閱讀器源自追書神器,免費(fèi)使用目前已初步開發(fā)完成項(xiàng)目地址歡迎,,推薦一個(gè)之前用文章類閱讀寫的一點(diǎn)閱讀微信小程序一點(diǎn)文章已上線,可以再微信搜索一點(diǎn)文章體驗(yàn)在線體驗(yàn)地址點(diǎn)擊這里體驗(yàn)服務(wù)器太,渲染慢部分效果截圖一點(diǎn)閱讀器優(yōu)勢(shì)一點(diǎn)閱讀器追書神 vue-reader 一點(diǎn)閱讀器!API源自追書神器,免費(fèi)使用!目前已初步開發(fā)完成! Github項(xiàng)目地址:https://github.com/An...

    Pocher 評(píng)論0 收藏0
  • 學(xué)習(xí)express.js源代碼的方法

    摘要:學(xué)習(xí)的源代碼的好處自然不少。閱讀源代碼可以幫你實(shí)現(xiàn)你的好奇心。本文會(huì)推薦一些的源代碼分析文章,可以幫助更快的,更加全方位的理解研讀之。 盡管有Hapi,Koa等有力的競(jìng)爭(zhēng)者,express.js依然是非常流行的nodejs web服務(wù)器框架,畢竟它早于2007年就已經(jīng)在開發(fā)了。 學(xué)習(xí)expressjs的源代碼的好處自然不少。 它可以幫你深刻理解HTTP協(xié)議,這個(gè)協(xié)議是做前端后端都必然需...

    huaixiaoz 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<