目錄
- 概述
- hello-world 例項
- 執行原理
- 多路由多回撥以及中介軟體
概述
Express是一個基於 Node.js 平臺,快速、開放、極簡的 web 開發框架。主要有 路由、中介軟體、模板引擎、 錯誤處理等功能
Hello world 例項
在test資料夾中新加1.helloworld.js
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.js
node 1.helloworls.js
複製程式碼
上面程式碼會在本機的3000埠啟動一個網站,網頁顯示Hello World。
執行原理
現在新建lib資料夾我們手寫一個自己的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中實現例項中app.get、app.listen兩個方法
操作:構造Appliaction函式,在原型上新增 get、listen方法
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 #框架入口
複製程式碼
- app從字面量變為Application類
- 豐富HTTP請求方法
- 封裝Router
- 路徑一樣的路由整合為一組,引入Layer的概念
- 增加路由控制,支援next方法,並增加錯誤捕獲功能
- 執行Router.handle的時候傳入out引數
- 理清邏輯
測試程式碼中 註冊新增了多個路由且能新增多個回撥方法,將邏輯分為三步。
(1)Application容器將請求方法和handler分發給router,在執行listen監聽函式時,執行self._router.handle(req, res, done),讓塞入Router中的邏輯執行。
Application類
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)中建立一個route例項
Router類
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方法。 新增 next 函式主要負責將控制權交給下一個中介軟體,如果當前中介軟體沒有終結請求,並且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
- Router Layer 路徑 處理函式(route.dispatch) 有一個特殊的route屬性
- Route layer 路徑 處理函式(真正的業務程式碼) 有一特殊的屬性method
Application只做包裝幻術及路由分發, Router實現 app.use、 app.param、 app.get、 app.post等路由方法方法的封裝
邏輯說明圖
原始碼
倉庫地址:原始碼連結點這裡~