Пять минут легкой имитации исходного кода Express

Node.js внешний интерфейс JavaScript Express

содержание

  • Обзор
  • пример приветствия
  • Как это работает
  • Несколько маршрутов, несколько обратных вызовов и промежуточное ПО

Обзор

Express — это быстрая, открытая и минималистичная среда веб-разработки, основанная на платформе Node.js. В основном включает маршрутизацию, промежуточное ПО, механизм шаблонов, обработку ошибок и другие функции.

Привет, мир, экземпляр

Добавьте 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.helloworlds.js

node 1.helloworls.js

Приведенный выше код запустит веб-сайт на порту 3000 этой машины, и на веб-странице будет отображаться Hello World.

Как это работает

Теперь создаем новую папку lib и пишем свою экспресс-библиотеку, чтобы понять, как она работает

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;

Назначение: два метода app.get, app.listen в экземпляре Application.js.

Операция: создайте функцию приложения и добавьте методы получения и прослушивания в прототип.

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 построен на модуле http, встроенном в node.js.

Ключом к приведенному выше коду является метод createServer модуля http, что означает создание экземпляра HTTP-сервера. Этот метод принимает функцию обратного вызова, параметрами которой являются объект запроса и объект ответа, представляющие HTTP-запрос и HTTP-ответ соответственно.

Объект помещается в массив маршрутизатора, когда приходит запрос цикла, и когда метод запроса и путь совпадают с таковыми в объекте, выполняется метод обработчика обратного вызова.

Несколько маршрутов, несколько обратных вызовов и промежуточное ПО

  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       #框架入口
  • приложение изменено с буквального на класс приложения
  • Богатые методы HTTP-запроса
  • Инкапсулировать маршрутизатор
  • Маршруты с одинаковым путем объединяются в группу, и вводится понятие слоя.
  • Добавьте контроль маршрутизации, поддержите следующий метод и добавьте функцию захвата ошибок
  • Передайте параметр out при выполнении Router.handle
  1. четкая логика

В тестовом коде регистрируется и добавляется несколько маршрутов, а также можно добавлять несколько методов обратного вызова, что делит логику на три этапа.

(1) Контейнер приложения распределяет метод запроса и обработчик на маршрутизатор.Когда выполняется функция слушателя прослушивания, выполняется self._router.handle(req, res, done), чтобы запустить логику, вставленную в маршрутизатор.

Класс приложения

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) Запрос каждого метода в маршрутизаторе добавит слой в текущую систему маршрутизации и создаст экземпляр маршрута в слое (уровне).

Класс маршрутизатора

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;
    }
});

Если это промежуточное программное обеспечение, по умолчанию пути нет, поэтому маршрут слоя устанавливается в значение 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
}

Когда приложение начинает прослушивать порт, выполняется метод handle маршрутизатора. добавить следующий Функция в основном отвечает за передачу управления следующему промежуточному ПО.Если текущее промежуточное ПО не завершает запрос и не вызывается следующее, запрос будет приостановлен, и промежуточное ПО, определенное позже, не будет иметь возможности быть выполненным.

Когда путь в маршрутизаторе соответствует методу, перейдите на текущий слой и запустите layer.handle_request, чтобы выполнить метод, добавленный в маршрут.

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.prototype.handle_request = function (req, res, next) {
    this.handler(req, res, next);
}

Обратите внимание, что метод this.handler здесь — это route.dispatch.bind(route), добавленный при добавлении уровня. Отправка привязывается к Layer.handler в методе router.route при инициализации слоя и анализе кода отправки. :

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

  • Обработчик маршрута уровня маршрутизатора (route.dispatch) имеет специальное свойство маршрута.
  • Функция обработчика пути уровня маршрута (реальный бизнес-код) имеет специальный метод атрибута

Приложение занимается только магией упаковки и распределением маршрутизации, Реализация маршрутизатора приложение.использование, приложение.парам, приложение.получить, Инкапсуляция методов маршрутизации, таких как app.post

Логическая пояснительная схема

исходный код

Адрес склада:Ссылка на источник нажмите здесь ~