五分鐘輕仿一套express源碼

目錄

  • 概述
  • hello-world 實例
  • 運行原理
  • 多路由多回調以及中間件

概述

Express是一個基於 Node.js 平臺,快速、開放、極簡的 web 開發框架。主要有 <font color=Chocolate>路由</font>、<font color=Chocolate>中間件</font>、<font color=Chocolate>模板引擎</font>、 <font color=Chocolate>錯誤處理</font>等功能node

Hello world 實例

在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方法。

多路由多回調以及中間件

  1. 測試用例
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');
});
  1. 先對項目結構改造
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. 理清邏輯

測試代碼中 註冊添加了多個路由且能添加多個回調方法,將邏輯分爲三步。

(1)Application容器將請求方法和handler分發給router,在執行listen監聽函數時,執行self._router.handle(req, res, done),讓塞入Router中的邏輯運行。

image

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)中建立一個<font color=Chocolate>route</font>實例
image

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方法。
添加 <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();
}

image

(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實現
<font color=Chocolate>app.use</font>、
<font color=Chocolate>app.param</font>、
<font color=Chocolate>app.get</font>、
<font color=Chocolate>app.post</font>等路由方法方法的封裝

邏輯說明圖

image

源碼

倉庫地址:源碼連接點這裏~

相關文章
相關標籤/搜索