Express是一個基於 Node.js 平臺,快速、開放、極簡的 web 開發框架。主要有 <font color=Chocolate>路由</font>、<font color=Chocolate>中間件</font>、<font color=Chocolate>模板引擎</font>、 <font color=Chocolate>錯誤處理</font>等功能node
在test文件夾中新加1.helloworld.jsgit
var express = require('express'); var app = express(); app.get('/', function (req, res) { res.end('Hello World!'); }); var server = app.listen(3000, function () { console.log('Example app listening at 3000'); });
運行 1.helloworls.jsgithub
node 1.helloworls.js
上面代碼會在本機的3000端口啓動一個網站,網頁顯示Hello World。web
如今新建lib文件夾咱們手寫一個本身的express庫 瞭解其運行原理express
YUAN-EXPRESS | | | - lib | | - application.js #包裹app層 | | - express.js #框架入口 | | - test | | - 1.helloworld.js |
express.js數組
const Application = require('./application'); function createApplicaton() { return new Application(); } module.exports = createApplicaton;
目的:在application.js中實現實例中<font color=Chocolate>app.get</font>、<font color=Chocolate>app.listen</font>兩個方法服務器
操做:構造Appliaction函數,在原型上添加 <font color=Chocolate>get</font>、<font color=Chocolate>listen</font>方法app
application.js框架
const http = require('http') const url = require('url') let router = [{ path:"*", method:"*", handler(req,res){ res.end(`Cannot ${req.method}_${req.url}`) } }] function Application() { } Application.prototype.get = function (path,handler) {//在Application原型上添加get方法 router.push({ path, method: 'get', handler }) } Application.prototype.listen = function () {//在Application原型上添加listen方法匹配路徑,執行對應的handler方法 let self = this const server = http.createServer(function (req,res) { let { pathname } = url.parse(req.url,true) for(var i = 1;i<router.length;i++){ let { path,method,handler } = router[i] if (pathname == path && req.method.toLocaleLowerCase() == method){ return handler(req,res) } } router[0].handler(req,res) }) server.listen(...arguments) } module.exports = Application
Express框架創建在node.js內置的http模塊上。函數
上面代碼的關鍵是http模塊的createServer方法,表示生成一個HTTP服務器實例。該方法接受一個回調函數,該回調函數的參數,分別爲表明HTTP請求和HTTP迴應的request對象和response對象。
循環請求過來時放入router數組的對象,當請求方法和路徑與對象中的一致時,執行回調handler方法。
const express = require('../lib/express'); const app = express(); /** * 1.get是指定多個處理函數 * 2.中間件錯誤處理 * 3. 子路徑系統 單首創建一個子路徑系統,而且把它掛載到主路徑 系統上 * */ /** * app.use * express.Router(); */ app.use(function (req, res, next) { console.log('Ware1:', Date.now()); next(); }); //路由是完整匹配的。/ != /user 因此進不來 app.get('/', function (req, res, next) { res.end('1'); }); //建立一個新的路由容器,或者說路由系統 const user = express.Router();// router user.use(function (req, res, next) { console.log('Ware2', Date.now()); next(); }); //在子路徑裏的路徑是相對於父路徑 user.get('/2', function (req, res, next) { res.end('2'); }); //use表示使用中間件,只須要匹配前綴就能夠了 app.use('/user', user);//user第二個參數是處理函數 (req,res,next) // req.url = /user/3 //app.use('/user', artcile); app.use(function (err, req, res, next) { res.end('catch ' + err); }); app.listen(3000, function () { console.log('server started at port 3000'); });
iExpress/ | | | - application.js #包裹app層 | | - route/ | | - index.js #Router類 | | - route.js #Route類 | | - layer.js #Layer類 | | - middle/ | | - init.js #內置中間件 | | - test/ | | - 測試用例文件1 | | - ... | ·- express.js #框架入口
測試代碼中 註冊添加了多個路由且能添加多個回調方法,將邏輯分爲三步。
(1)Application容器將請求方法和handler分發給router,在執行listen監聽函數時,執行self._router.handle(req, res, done),讓塞入Router中的邏輯運行。
const Router = require('./router'); Application.prototype.lazyrouter = function () { if (!this._router) { this._router = new Router(); } } methods.forEach(function (method) { Application.prototype[method] = function () { this.lazyrouter(); //這樣寫能夠支持多個處理函數 this._router[method].apply(this._router, slice.call(arguments)); return this; } }); Application.prototype.listen = function () { let self = this; let server = http.createServer(function (req, res) { function done() {//若是沒有任何路由規則匹配的話會走此函數 res.end(`Cannot ${req.method} ${req.url}`); } //若是路由系統沒法處理,也就是沒有一條路由規則跟請求匹配,是會把請求交給done self._router.handle(req, res, done); }); server.listen(...arguments); }
(2) 在Router中每個方法的請求都會往當前的路由系統中添加一個層,在層(layer)中建立一個<font color=Chocolate>route</font>實例
proto.route = function (path) { let route = new Route(path); let layer = new Layer(path, route.dispatch.bind(route)); layer.route = route; this.stack.push(layer);//在Router中新增一層layer return route; } methods.forEach(function (method) { proto[method] = function (path) {//請求過來 let route = this.route(path);//往Router裏添一層 route[method].apply(route, slice.call(arguments, 1));// return this; } });
若是是中間件,默認沒有path 因此layer的route設爲undefined
proto.use = function (path, handler) { if (typeof handler != 'function') { handler = path; path = '/'; } let layer = new Layer(path, handler); layer.route = undefined;//咱們正是經過layer有沒有route來判斷是一箇中間件函數仍是一個路由 this.stack.push(layer); return this }
Application開始監聽端口時,執行Router的handle方法。
添加 <font color=Chocolate>next</font>
函數主要負責將控制權交給下一個中間件,若是當前中間件沒有終結請求,而且next沒有被調用,那麼請求將被掛起,後邊定義的中間件將得不到被執行的機會。
當Router中的路徑和方法匹配時,走到當前layer中,運行layer.handle_request 執行route中添加的方法。
proto.handle = function (req, res, out) { //slashAdded是否添加過/ removed指的是被移除的字符串 let idx = 0, self = this, slashAdded = false, removed = ''; // /user/2 let { pathname } = url.parse(req.url, true); function next(err) { if (slashAdded) { req.url = ''; slashAdded = false; } if (removed.length > 0) { req.url = removed + req.url; removed = ''; } if (idx >= self.stack.length) { return out(err); } let layer = self.stack[idx++]; //在此匹配路徑 params 正則+url= req.params if (layer.match(pathname)) {// layer.params if (!layer.route) { //這一層是中間件層// /user/2 removed = layer.path;// /user req.url = req.url.slice(removed.length);// /2 if (err) { layer.handle_error(err, req, res, next); } else { if (req.url == '') { req.url = '/'; slashAdded = true; } layer.handle_request(req, res, next); } } else { if (layer.route && layer.route.handle_method(req.method)) { //把layer的parmas屬性拷貝給req.params req.params = layer.params; self.process_params(layer, req, res, () => { layer.handle_request(req, res, next); }); } else { next(err); } } } else { next(err); } } next(); }
(3)進入到當前layer,按照順序執行添加的每個route
Layer類
Layer.prototype.handle_request = function (req, res, next) { this.handler(req, res, next); }
注意 這裏的this.handler方法,是添加layer時加入的route.dispatch.bind(route),dispatch是在router.route方法中,初始化layer的時候綁定到Layer.handler上的,解析下dispatch代碼:
Route.prototype.dispatch = function (req, res, out) { let idx = 0, self = this; function next(err) { if (err) {//若是一旦在路由函數中出錯了,則會跳過當前路由 return out(err); } if (idx >= self.stack.length) { return out();//route.dispath裏的out恰好是Router的next } let layer = self.stack[idx++]; if (layer.method == req.method.toLowerCase()) { layer.handle_request(req, res, next); } else { next(); } } next(); }
文字結構圖以下
Application | | Router | | - stack | | - Layer | | - path router | | - method handler
Router
Layer
Application只作包裝幻術及路由分發,
Router實現
<font color=Chocolate>app.use</font>、
<font color=Chocolate>app.param</font>、
<font color=Chocolate>app.get</font>、
<font color=Chocolate>app.post</font>等路由方法方法的封裝
倉庫地址:源碼連接點這裏~