экспресс-чтение исходного кода

Node.js JavaScript Express

1. Введение

Основная цель этой статьи - проанализировать и понять исходный код экспресса. В Интернете есть бесчисленное множество анализов исходного кода. Эта статья собирается создать новый способ имитации колеса экспресса и пройти через тест- управляемый метод разработки, чтобы понять код экспресс.

Экспресс-исходники в этой статье относятся к последней версии официального сайта (v4.15.4), а общая идея статьи относится к более раннему творению.другая статья, это обновленная версия.

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

Ссылка на код:GitHub.com/Ван Зи и Супер…

2. Инициализация фреймворка

Прежде чем имитировать экспресс-фреймворк, необходимо сделать две вещи.

  • Подтвердить потребности.
  • Подтвердите структуру.

Сначала подтвердите запрос и начните с официального сайта экспресс. На сайте есть пример программы Hello world.Если вы хотите имитировать экспресс, программа должна пройти тест.Скопируйте и сохраните измененный код в тестовую директорию.test/index.js.

const express = require('express')
const app = express()

app.get('/', function (req, res) {
  res.send('Hello World!')
})

app.listen(3000, function () {
  console.log('Example app listening on port 3000!')
})

Затем подтвердите имя фреймворка и структуру каталогов. Название кадра называетсяexpross. Структура каталогов следующая:

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

позволятьexpross/index.jsзагрузка файлаlibв каталогеexpross.jsдокумент.

module.exports = require('./lib/expross');

пройти черезПервые две строки тестовой программыможно сделать выводlib/expross.jsРезультат экспорта должен бытьфункция, так что вexpross.jsДобавьте в файл следующий код:

function createApplication() {
  return {};
}

exports = module.exports = createApplication;

Тестовая программа содержит две функции, поэтому временноcreateApplicationТело функции реализовано следующим образом:

function createApplication() {
    return {
        get: function() {
            console.log('expross().get function');
        },

        listen: function() {
            console.log('expross().listen function');
        }
    }
}

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

На данный момент начальная структура была построена и изменена.test/index.jsПервые две строки файла:

const expross = require('../');
const app = expross();

бегатьnode test/index.jsПосмотреть Результаты.

2. Первая итерация

Этот раздел является первой итерацией expross. Основная цель — полностью реализовать текущую функцию тестового примера, которая разделена на две части:

  • Реализовать http-сервер.
  • Реализовать получение маршрутных запросов.

Реализовать http-сервер относительно просто, вы можете обратиться к реализации на официальном сайте nodejs.

const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World\n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

Ссылаясь на этот случай, осознайтеexprossизlistenфункция.

listen: function(port, cb) {
    var server = http.createServer(function(req, res) {
        console.log('http.createServer...');
    });

    return server.listen(port, cb);
}

ТекущийlistenФункция содержит два параметра, ноhttp.listenСуществует много перегруженных функций, чтобы иhttp.listenПоследовательно, функция может быть установлена ​​наhttp.listen«прокси», который хранитexpross().listenиhttp.listenпараметры унифицированы.

listen: function(port, cb) {
    var server = http.createServer(function(req, res) {
        console.log('http.createServer...');
    });

      //代理
    return server.listen.apply(server, arguments);
}

существуетlistenВ функции мы перехватываем все http запросы, и каждый http запрос будет печататьhttp.createServer ..., Причина перехвата http-запросов заключается в том, что expross необходимо анализировать каждый http-запрос и обрабатывать различную бизнес-логику в соответствии с разными http-запросами.

На нижнем слое:

HTTP-запрос в основном включает строку запроса, заголовок запроса и тело сообщения. Nodejs инкапсулирует часто используемые данные в класс http.IncomingMessage. В приведенном выше коде req является объектом этого класса.

Каждому http-запросу соответствует http-ответ. Ответ http в основном включает в себя строку состояния, заголовок ответа и тело сообщения. Nodejs инкапсулирует часто используемые данные в класс http.ServerResponse. В приведенном выше коде res является объектом этого класса.

Не только nodejs, но практически все фреймворки http-сервисов содержат два объекта, request и response, которые представляют собой http-запросы и ответы соответственно и отвечают за взаимодействие между сервером и браузером.

На верхнем уровне:

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

Для управления этими http запросами есть имя собственное - "управление маршрутом", каждый HTTP-запрос по умолчанию равен одномумаршрутизация, общие стратегии дифференциации маршрутизации включают в себя URL-адреса, существительные HTTP-запроса и т. д., но не ограничиваются только ими, все параметры в заголовке HTTP-запроса могут фактически оцениваться и различаться, например, использование поля агента пользователя для оценки мобильного терминала.

В разных фреймворках немного отличаются правила управления маршрутизацией, но в любом случае требуется набор функций для управления http-запросами и отображения бизнес-логики.getФункция — это функция в управлении маршрутизацией, которая в основном отвечает за добавление запросов на получение.

Теперь, когда вы знаете о важности управления маршрутами, создайте здесь массив маршрутизаторов, который отвечает за управление всеми сопоставлениями маршрутов. Ссылаясь на экспресс-фреймворк, абстрагируются основные свойства каждого маршрута:

  • path Путь запроса, например: /books, /books/1.
  • метод Метод запроса, например: GET, POST, PUT, DELETE.
  • handle Функция обработчика.
var router = [{
    path: '*',
    method: '*',
    handle: function(req, res) {
        res.writeHead(200, {'Content-Type': 'text/plain'});
        res.end('404');
    }
}];

Измените функцию прослушивания и измените логику перехвата HTTP-запросов, чтобы она соответствовала таблице маршрутизации маршрутизатора.Если совпадение успешно, выполните соответствующую функцию дескриптора, в противном случае выполните функцию router[0].handle.

listen: function(port, cb) {
    var server = http.createServer(function(req, res) {

        for(var i=1,len=router.length; i<len; i++) {
            if((req.url === router[i].path || router[i].path === '*') &&
                (req.method === router[i].method || router[i].method === '*')) {
                return router[i].handle && router[i].handle(req, res);
            }
        }

        return router[0].handle && router[0].handle(req, res);
    });

    return server.listen.apply(server, arguments);
}

Реализовать запрос на получение маршрута очень просто.Эта функция в основном предназначена для добавления маршрута запроса на получение, просто добавьте его информацию в массив маршрутизаторов.

get: function(path, fn) {
    router.push({
        path: path,
        method: 'GET',
        handle: fn
    });
}

Выполните тестовый пример и сообщите об ошибке, указывающей, что res.send не существует. Эта функция не является нативной функцией nodejs, здесь в res временно добавлена ​​функция send, которая отвечает за отправку ответа в браузер.

listen: function(port, cb) {
    var server = http.createServer(function(req, res) {
        if(!res.send) {
            res.send = function(body) {
                res.writeHead(200, {
                    'Content-Type': 'text/plain'
                });
                res.end(body);
            };
        }

        ......
    });

    return server.listen.apply(server, arguments);
}

Прежде чем закончить эту итерацию, давайте разберем каталог программы:

Application.js создает файлы для переноса кода функции createApplication в файл, файл expross.js оставляет только ссылку.

var app = require('./application');

function createApplication() {
    return app;
}

exports = module.exports = createApplication;

Вся структура каталогов выглядит следующим образом:

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

Наконец, запуститеnode test/index.js, откройте браузер для доступаhttp://127.0.0.1:3000/.

3. Вторая итерация

Этот раздел — вторая итерация expross, и основная цель — построить предварительную систему маршрутизации. Согласно изменениям в предыдущем разделе, текущий маршрут описывается и управляется массивом маршрутизаторов.Для маршрутизаторов существует две операции, а именно функция application.get и функция application.listen.Первая используется для добавления, а последний используется для обработки .

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

var Router = function() {
    this.stack = [{
        path: '*',
        method: '*',
        handle: function(req, res) {
            res.writeHead(200, {
                'Content-Type': 'text/plain'
            });
            res.end('404');
        }
    }];
};


Router.prototype.get = function(path, fn) {
    this.stack.push({
        path: path,
        method: 'GET',
        handle: fn
    });
};


Router.prototype.handle = function(req, res) {
    for(var i=1,len=this.stack.length; i<len; i++) {
        if((req.url === this.stack[i].path || this.stack[i].path === '*') &&
            (req.method === this.stack[i].method || this.stack[i].method === '*')) {
            return this.stack[i].handle && this.stack[i].handle(req, res);
        }
    }

    return this.stack[0].handle && this.stack[0].handle(req, res);
};

Измените содержимое исходного файла application.js.

var http = require('http');
var Router = require('./router');


exports = module.exports = {
    _router: new Router(),

    get: function(path, fn) {
        return this._router.get(path, fn);
    },

    listen: function(port, cb) {
        var self = this;

        var server = http.createServer(function(req, res) {
            if(!res.send) {
                res.send = function(body) {
                    res.writeHead(200, {
                        'Content-Type': 'text/plain'
                    });
                    res.end(body);
                };
            }

            return self._router.handle(req, res);
        });

        return server.listen.apply(server, arguments);
    }
};

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

Проблем с нормальной работой этой системы маршрутизации нет, но если количество маршрутов будет продолжать увеличиваться, массив this.stack будет продолжать увеличиваться, а эффективность сопоставления будет продолжать снижаться.Чтобы решить проблему эффективности , необходимо тщательно проанализировать компоненты маршрутизации.

В настоящее время в expross маршрут состоит из трех частей: пути, метода и обработчика. Связь между первыми двумя — это не связь «один к одному», а связь «один ко многим», например:

GET books/1
PUT books/1
DELETE books/1

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

Здесь каждый элемент массива this.stack в системе Router представляет слой. Каждый слой содержит три переменные внутри.

  • путь, указывающий путь маршрута.
  • handle, который представляет функцию обработчика маршрута.
  • route, который представляет реальный маршрут.

Общая структура показана на рисунке ниже:

------------------------------------------------
|     0     |     1     |     2     |     3     |      
------------------------------------------------
| Layer     | Layer     | Layer     | Layer     |
|  |- path  |  |- path  |  |- path  |  |- path  |
|  |- handle|  |- handle|  |- handle|  |- handle|
|  |- route |  |- route |  |- route |  |- route |
------------------------------------------------
                  router 内部

Сначала создайте класс Layer.

function Layer(path, fn) {
    this.handle = fn;
    this.name = fn.name || '<anonymous>';
    this.path = path;
}


//简单处理
Layer.prototype.handle_request = function (req, res) {
  var fn = this.handle;

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


//简单匹配
Layer.prototype.match = function (path) {
    if(path === this.path || path === '*') {
        return true;
    }

    return false;
};

Снова измените класс Router.

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


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

    for(var i=1,len=self.stack.length; i<len; i++) {
        if(self.stack[i].match(req.url)) {
            return self.stack[i].handle_request(req, res);
        }
    }

    return self.stack[0].handle_request(req, res);
};


Router.prototype.get = function(path, fn) {
    this.stack.push(new Layer(path, fn));
};

бегатьnode test/index.js,доступhttp://127.0.0.1:3000/Все выглядит нормально, но приведенный выше код игнорирует метод атрибута маршрута. Такой результат приведет к проблемам, если тестовый пример:

app.put('/', function(req, res) {
    res.send('put Hello World!');
});

app.get('/', function(req, res) {
    res.send('get Hello World!');
});

Программа не может отличить PUT от GET.

Следовательно, необходимо продолжать улучшать атрибут маршрута в классе Layer в системе маршрутизации.Этот атрибут является маршрутом, который фактически содержит атрибут метода.

Структура маршрута следующая:

------------------------------------------------
|     0     |     1     |     2     |     3     |      
------------------------------------------------
| item      | item      | item      | item      |
|  |- method|  |- method|  |- method|  |- method|
|  |- handle|  |- handle|  |- handle|  |- handle|
------------------------------------------------
                  route 内部

Создайте класс маршрута.

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<len; i++) {
        if(method === self.stack[i].method) {
            return self.stack[i].handle_request(req, res);
        }
    }
};

В приведенном выше коде объект элемента в предыдущей структурной диаграмме не определен, но вместо него используется объект Layer, в основном для удобства и скорости.С другой точки зрения, на самом деле у них много общего. Кроме того, для облегчения понимания в коде реализован только метод GET, а кодовая реализация остальных методов аналогична.

Теперь, когда есть класс Route, следующим шагом будет изменение исходного класса Router и интеграция в него маршрута.

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

    for(var i=0,len=self.stack.length; i<len; i++) {
        if(self.stack[i].match(req.url) && 
            self.stack[i].route && self.stack[i].route._handles_method(method)) {
            return self.stack[i].handle_request(req, res);
        }
    }

    return self.stack[0].handle_request(req, res);
};


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

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

    layer.route = route;

    this.stack.push(layer);

    return route;
};


Router.prototype.get = function(path, fn) {
    var route = this.route(path);
    route.get(fn);

    return this;
};

бегатьnode test/index.js, все выглядит так же, как и раньше.

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

Во-первых, структура каталогов текущей программы выглядит следующим образом:

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

Далее суммируйте работу каждой части текущего Expross.

Application представляет собой приложение, а expross — это фабричный класс, отвечающий за создание объектов приложения. Маршрутизатор представляет собой компонент маршрутизации и отвечает за всю систему маршрутизации приложения. Внутренняя часть компонента состоит из массива слоев. Каждый слой представляет собой набор информации о маршрутизации с одним и тем же путем. Конкретная информация хранится внутри маршрута. Каждый маршрут также является объектом уровня, но слой внутри маршрута и Слой внутри маршрутизатора существует в определенной степени.

  • Слой внутри маршрутизатора в основном включает в себя атрибуты пути и маршрута.
  • Слой внутри маршрута в основном содержит атрибуты метода и дескриптора.

Если приходит запрос, то он будет сканировать каждый слой внутри маршрутизатора от начала до конца, и при обработке каждого слоя сначала будет сравниваться URI, если одинаковые, то будет сканироваться каждый элемент маршрута, и если совпадение будет успешным , будет возвращена конкретная информация, и совпадений нет. возвращает не найдено.

Наконец, структура всей системы маршрутизации выглядит следующим образом:

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

3. Третья итерация

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

  • Расширенный интерфейс в настоящее время поддерживает только интерфейс получения.
  • Увеличьте контроль потока в системе маршрутизации.

Текущая структура expross поддерживает только интерфейс get. Конкретный интерфейс предоставляется expross. Интерфейс router.get вызывается внутри, а внутренний интерфейс является инкапсуляцией Route.get.

HTTP, очевидно, не только метод GET, но и включает в себя многие, такие как: PUT, POST, DELETE и т. д. Писать отдельную функцию обработки для каждого метода явно избыточно, потому что содержание функции связано не только с имя функции, все остальное высечено в камне. В соответствии с характеристиками языка сценариев JavaScript содержимое функции может динамически генерироваться с помощью списка методов HTTP.

Чтобы сгенерировать функцию динамически, сначала необходимо определить имя функции. Имя функции — это имя метода, поддерживаемого HTTP-сервером в nodejs, которое можно получить из официальной документации Конкретные параметрыhttp.METHODS. Это свойство было добавлено в версии 0.11.8. Если версия nodejs ниже этой, вам необходимо вручную создать список методов. Подробнее см. в коде nodejs.

Получение имени метода HTTP экспресс-фреймворка инкапсулировано в другой пакет, называемыйmethodsВнутри приводится список совместимых глаголов в младшей версии.

function getBasicNodeMethods() {
  return [
    'get',
    'post',
    'put',
    'head',
    'delete',
    'options',
    'trace',
    'copy',
    'lock',
    'mkcol',
    'move',
    'purge',
    'propfind',
    'proppatch',
    'unlock',
    'report',
    'mkactivity',
    'checkout',
    'merge',
    'm-search',
    'notify',
    'subscribe',
    'unsubscribe',
    'patch',
    'search',
    'connect'
  ];
}

Зная список имен поддерживаемых методов, все, что осталось, — это цикл for для генерации всех функций.

Ядро всех обработчиков глаголов находится в Route.

http.METHODS.forEach(function(method) {
    method = method.toLowerCase();
    Route.prototype[method] = function(fn) {
        var layer = new Layer('/', fn);
        layer.method = method;

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

        return this;
    };
});

Затем измените Router.

http.METHODS.forEach(function(method) {
    method = method.toLowerCase();
    Router.prototype[method] = function(path, fn) {
        var route = this.route(path);
        route[method].call(route, fn);

        return this;
    };
});

Наконец, измените содержимое application.js. Изменения здесь больше: переопределение класса Application вместо использования литералов для их непосредственного создания.

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


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

    var server = http.createServer(function(req, res) {
        self.handle(req, res);
    });

    return server.listen.apply(server, arguments);
};


Application.prototype.handle = function(req, res) {
    if(!res.send) {
        res.send = function(body) {
            res.writeHead(200, {
                'Content-Type': 'text/plain'
            });
            res.end(body);
        };
    }

    var router = this._router;
    router.handle(req, res);
};

Затем добавьте функцию метода HTTP.

http.METHODS.forEach(function(method) {
    method = method.toLowerCase();
    Application.prototype[method] = function(path, fn) {
        this._router[method].apply(this._router, arguments);
        return this;
    };
});

Поскольку экспорт является классом приложения, измените файл expross.js.

var Application = require('./application');

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

бегатьnode test/index.js,погнали.

Если вы внимательно изучите исходный код системы маршрутизации, то обнаружите, что определение самого маршрута не похоже на текстовое описание. Например:

Добавьте два маршрута с одинаковым путем:

app.put('/', function(req, res) {
    res.send('put Hello World!');
});

app.get('/', function(req, res) {
    res.send('get Hello World!');
});

Результатом является не ожидаемая структура, подобная следующей:

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

Вместо этого структура выглядит следующим образом:

 ----- -----------        -------------
|     | Layer     | ----> | Layer     |
|  0  |   |-path  |       |  |- method|   route
|     |   |-route |       |  |- handle|
|-----|-----------|       -------------
|     | Layer     |       -------------      
|  1  |   |-path  | ----> | Layer     |
|     |   |-route |       |  |- method|   route     
|-----|-----------|       |  |- handle|        
|     | Layer     |       -------------
|  2  |   |-path  |       -------------  
|     |   |-route | ----> | Layer     |
|-----|-----------|       |  |- method|   route
| ... |   ...     |       |  |- handle| 
 ----- -----------        -------------
    router

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

Поскольку Express Framework, сохраненная маршрута, сохранена информация о маршрутизации, может использоваться в качестве отдельных членов для использования, если вы хотите реальные результаты, описанные в предыдущем описании, вам необходимо создать свой код:

var router = express.Router();

router.route('/users/:user_id')
.get(function(req, res, next) {
  res.json(req.user);
})
.put(function(req, res, next) {
  // just an example of maybe updating the user
  req.user.name = req.params.name;
  // save user ... etc
  res.json(req.user);
})
.post(function(req, res, next) {
  next(new Error('not implemented'));
})
.delete(function(req, res, next) {
  next(new Error('not implemented'));
});

вместо этого:

var app = expross();

app.get('/users/:user_id', function(req, res) {

})

.put('/users/:user_id', function(req, res) {

})

.post('/users/:user_id', function(req, res) {

})

.delete('/users/:user_id', function(req, res) {

});

После понимания использования Route следующим шагом будет обсуждение только что упомянутой проблемы порядка. В системе маршрутизации очень важен порядок обработки маршрутов, т. к. маршруты хранятся в виде массивов.Если вы встретите два одинаковых маршрута, одно и то же имя метода и разные функции обработки, порядок объявлений в это время напрямую изменится. влияют на результат (по этой же причине промежуточное ПО Express зависит от порядка), как в следующем примере:

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

Если приведенный выше код выполняется, он всегда будет возвращатьfirst, но иногда будет динамически определять, выполнять ли следующий маршрут по параметрам, присланным с ресепшн, как его пропустить?firstВходитьsecond?这就涉及到路由系统的流程控制问题。

Управление процессом делится на активный и пассивный режимы.

Для платформы expross логика обработки привязки маршрута и параметры пути, установленные пользователем, ненадежны, и во время работы могут возникнуть исключения.Пассивное управление потоком заключается в том, что при возникновении этих исключений платформа expross должна быть ответственной для работы по отлову этих исключений, потому что, если местонахождение исключения не ясно, код js не сможет продолжать работать, и об ошибке нельзя будет точно сообщить.

Активное управление потоком должно обрабатывать логику работы внутри функции и переходить к логике выполнения внутри маршрута путем активного вызова.

В настоящее время экспресс решает проблему управления технологическим процессом, вводя следующий параметр. next — это параметр функции-обработчика, которая сама по себе является функцией, которую можно использовать несколькими способами:

  • Выполнить следующую функцию обработчика. воплощать в жизньnext().
  • Сообщить об исключениях. воплощать в жизньnext(err).
  • Пропустить текущий Маршрут и выполнить следующий пункт Маршрутизатора. воплощать в жизньnext('route').
  • Пропустить весь маршрутизатор. воплощать в жизньnext('router').

Далее мы пытаемся реализовать следующие требования.

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

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

  try {
    fn(req, res, next);
  } catch (err) {
    next(err);
  }
};

Затем измените функцию Route.dispath. Поскольку он включает в себя логические переходы внутри, менее подходит для цикла шаг за шагом, как используется здесь, аналогичный рекурсивно.

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

    function next(err) {
        //跳过route
        if(err && err === 'route') {
            return done();
        }

        //跳过整个路由系统
        if(err && err === 'router') {
            return done(err);
        }

        //越界
        if(idx >= stack.length) {
            return done(err);
        }

        //不等枚举下一个
        var layer = stack[idx++];
        if(method !== layer.method) {
            return next(err);
        }

        if(err) {
            //主动报错
            return done(err);
        } else {
            layer.handle_request(req, res, next);
        }
    }

    next();
};

Весь процесс обработки по сути представляет собой цикл for, и единственное отличие состоит в том, что в функции обработки пользователь активно вызывает логику обработки следующей функции.

Если пользователь возвращает ошибку через следующую функцию,routeиrouterЭти три случая в настоящее время передаются маршрутизатору для обработки.

Так как функция диспетчеризации модифицируется, то и функция Router.route, которая вызывает эту функцию, также должна быть изменена, на этот раз она модифицируется напрямую, и в дальнейшем нет необходимости настраивать ее по количеству параметров.

Router.prototype.route = function route(path) {
    ...

    //使用bind方式
    var layer = new Layer(path, route.dispatch.bind(route));

    ...
};

Затем измените код Router.handle, логика аналогична Route.dispatch.

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

    function next(err) {
        var layerError = (err === 'route' ? null : err);

        //跳过路由系统
        if(layerError === 'router') {
            return done(null);
        }

        if(idx >= stack.length || layerError) {
            return done(layerError);
        }

        var layer = stack[idx++];
        //匹配,执行
        if(layer.match(req.url) && layer.route &&
            layer.route._handles_method(method)) {
            return layer.handle_request(req, res, next);
        } else {
            next(layerError);
        }
    }

    next();
};

Функции для обработки модифицированного и оригинала аналогичны, но с одним предупреждением, когда происходит исключение, результат будет возвращен на более высокий уровень, а не выполнять оригиналthis.stackЛогика кода для уровня 0.

Наконец, добавьте логику обработки исключений экспресс-фреймворка.

Раньше код инициализации оригинального this.stack можно было удалить, а

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

изменить на

var Router = function() {
    this.stack = [];
};

Затем измените функцию Application.Handle.

Application.prototype.handle = function(req, res) {

    ...

    var done = function finalhandler(err) {
        res.writeHead(404, {
            'Content-Type': 'text/plain'
        });

        if(err) {
            res.end('404: ' + err);    
        } else {
            var msg = 'Cannot ' + req.method + ' ' + req.url;
            res.end(msg);    
        }
    };

    var router = this._router;
    router.handle(req, res, done);
};

Здесь функция done просто обрабатывается для возврата страницы 404. На самом деле в экспресс-фреймворке используется отдельный пакет npm, который называетсяfinalhandler.

Просто измените тестовый пример, чтобы подтвердить результаты.

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

app.get('/', function(req, res, next) {
    next();
})

.get('/', function(req, res, next) {
    next(new Error('error'));
})

.get('/', function(req, res) {
    res.send('third');
});

app.listen(3000, function() {
    console.log('Example app listening on port 3000!');
});

бегатьnode test/index.js,доступhttp://127.0.0.1:3000/, Результаты показывают, что:

404: Error: error

Вроде пока все идет хорошо, но есть еще одно требование, которое в настоящее время упускается из виду. Все исключения текущей функции обработки захватываются фреймворком, а возвращаемая информация может быть только фиксированной страницей 404, что, очевидно, очень неудобно для пользователей фреймворка.Браузеры, поэтому expross нужен интерфейс, возвращающий ошибки пользователям верхнего уровня. .

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

function process_fun(req, res, next) {

}

Если вы добавите функцию обработки ошибок, в соответствии с правилами Nodejs, первый параметр - это сообщение об ошибке, и определение должно быть следующим:

function process_err(err, req, res, next) {

}

Поскольку информация о первом параметре двух объявлений различается, если входящая функция обработки является функцией, которая обрабатывает ошибки, или функцией, которая обрабатывает нормальные значения, это ключевая проблема, которую должна решить экспресс-инфраструктура.

В javascript свойство Function.length может получить количество параметров, указанных входящей функцией, которые можно использовать в качестве ключевой информации для их различения.

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

//错误处理
Layer.prototype.handle_error = function (error, req, res, next) {
  var fn = this.handle;

  //如果函数参数不是标准的4个参数,返回错误信息
  if(fn.length !== 4) {
    return next(error);
  }

  try {
    fn(error, req, res, next);
  } catch (err) {
    next(err);
  }
};

Затем измените функцию Route.dispatch.

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

    function next(err) {

        ...

        if(err) {
            //主动报错
            layer.handle_error(err, req, res, next);
        } else {
            layer.handle_request(req, res, next);
        }
    }

    next();
};

Когда возникает ошибка, Route всегда будет искать функцию обработки ошибок, если она найдена, возвращает, в противном случае выполняетdone(err), скиньте ошибку на роутер.

Для модификации маршрутизатора. Handle, из-за концепции некоторого промежуточного программного обеспечения, полная обработка ошибок будет удалена в следующий раздел.

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

4. Четвертая итерация

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

В экспрессе промежуточное ПО фактически является компонентом, который вызывается в середине всей системы процессов после поступления веб-запроса и до вызова функции обработки. Суть его в том, что это функция, которая может получать доступ к объектам запроса и ответа и изменять их внутри, а также корректировать следующий поток обработки.

Официальное объяснение, данное экспрессом, выглядит следующим образом:

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

ПО промежуточного слояэто функция, которая имеет доступ к объекту запроса (request object (req)), объект ответа (response object (res)) и промежуточное ПО в процессе цикла запрос-ответ в веб-приложениях, обычно называемоеnextПеременные.

Функции промежуточного программного обеспечения включают в себя:

  • Выполнить любой код.
  • Объекты запроса и ответа на изменение.
  • Запрос окончания - цикл ответа.
  • Вызовите следующее промежуточное ПО в стеке.

Должен вызываться, если текущее промежуточное ПО не завершает цикл запрос-ответ.next()Метод передает управление следующему промежуточному ПО, иначе запрос зависает.

Экспресс-приложения могут использовать следующее промежуточное ПО:

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

Официальное определение на самом деле достаточно ясное Стиль промежуточного программного обеспечения на самом деле представляет собой функцию обработки, упомянутую в предыдущем разделе, но она не имеет официального названия. Таким образом, для кода каждый дескриптор внутри свойства this.stack в классе Router является промежуточным программным обеспечением, которое отличается в зависимости от используемого интерфейса.Промежуточное ПО уровня приложенияиПромежуточное ПО маршрутизации, а четырехпараметрическая функция обработкипромежуточное ПО для обработки ошибок.

Затем добавьте функциональность промежуточного программного обеспечения в структуру Expross.

Первый — промежуточное ПО уровня приложения, которое используется в классе Application двумя способами: Application.use и Application.METHOD (различные методы HTTP-запроса), последний фактически был реализован в предыдущем разделе, а первый необходимо быть добавлено.

Код в предыдущем разделе показал, что Application.METHOD на самом деле является прокси-сервером для Router.METHOD, и Application.use — то же самое.

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

    router.use(path, fn);

    return this;
};

Поскольку Application.use поддерживает дополнительные пути, вам нужно добавить перегруженный код, который обрабатывает пути.

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

    //路径挂载
    if(typeof fn !== 'function') {
        path = fn;
        fn = arguments[1];
    }

    router.use(path, fn);

    return this;
};

На самом деле параметры поддерживаемые экспресс фреймворком это не только эти два, но некоторые мелкие детали убраны для удобства понимания, что удобно для понимания фреймворка.

Далее реализуем функцию Router.use.

Router.prototype.use = function(fn) {
    var path = '/';

    //路径挂载
    if(typeof fn !== 'function') {
        path = fn;
        fn = arguments[1];
    }

    var layer = new Layer(path, fn);
    layer.route = undefined;

    this.stack.push(layer);

    return this;
};

Внутренний код аналогичен Application.use, за исключением того, что в конце вместо вызова Router.use объект Layer создается напрямую и помещается в массив this.stack.

В этом фрагменте кода вы можете увидеть разницу между обычной маршрутизацией и промежуточным ПО. Обычный маршрут помещается в Route, а свойство Router.route указывает на объект Route, а свойство Router.handle указывает на функцию Route.dispatch, свойство Router.route промежуточного ПО не определено, а Router. handle указывает на функцию обработки промежуточного программного обеспечения, которая размещается в маршрутизаторе в массиве стека.

Для ПО промежуточного слоя на уровне маршрута сначала экспортируйте класс Router, если это необходимо для простоты использования.

exports.Router = Router;

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

var app = express();
var router = express.Router();

router.use(function (req, res, next) {
  console.log('Time:', Date.now());
});

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

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

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

var router = express.Router();

// 将路由挂载至应用
app.use('/', router);

Это действительно хороший метод, теперь давайте изменим expross, чтобы он выглядел похоже.

Сначала создайте объект-прототип, на который можно перенести существующие методы маршрутизатора.

var proto = {};

proto.handle = function(req, res, done) {...};
proto.route = function route(path) {...};
proto.use = function(fn) { ... };

http.METHODS.forEach(function(method) {
    method = method.toLowerCase();
    proto[method] = function(path, fn) {
        var route = this.route(path);
        route[method].call(route, fn);

        return this;
    };
});

Затем создайте промежуточную функцию, задайте ее прототип с помощью функции Object.setPrototypeOf и, наконец, экспортируйте функцию, которая генерирует эти процедуры.

module.exports = function() {
    function router(req, res, next) {
        router.handle(req, res, next);
    }

    Object.setPrototypeOf(router, proto);

    router.stack = [];
    return router;
};

Измените тестовый пример, чтобы проверить эффект.

app.use(function(req, res, next) {
    console.log('Time:', Date.now());
    next();
});

app.get('/', function(req, res, next) {
    res.send('first');
});


router.use(function(req, res, next) {
    console.log('Time: ', Date.now());
    next();
});

router.use('/', function(req, res, next) {
    res.send('second');
});

app.use('/user', router);

app.listen(3000, function() {
    console.log('Example app listening on port 3000!');
});

Результат не идеален, исходное приложение еще нужно доработать в двух местах.

Во-первых, это проблема логической обработки. Исходная функция Router.handle не имеет отношения к промежуточному программному обеспечению и нуждается в дальнейшей модификации.

proto.handle = function(req, res, done) {

    ...

    function next(err) {

        ...

        //注意这里,layer.route属性
        if(layer.match(req.url) && layer.route &&
            layer.route._handles_method(method)) {
            layer.handle_request(req, res, next);
        } else {
            layer.handle_error(layerError, req, res, next);
        }
    }

    next();
};

Измените его на:

proto.handle = function(req, res, done) {

    ...

    function next(err) {

        ...

        //匹配,执行
        if(layer.match(path)) {
            if(!layer.route) {
                //处理中间件
                layer.handle_request(req, res, next);    
            } else if(layer.route._handles_method(method)) {
                //处理路由
                layer.handle_request(req, res, next);
            }    
        } else {
            layer.handle_error(layerError, req, res, next);
        }
    }

    next();
};

Во-вторых, проблема сопоставления путей. Исходный единственный путь разбивается на различные комбинации промежуточных путей.Оценка здесь требует нескольких шагов, потому что каждое промежуточное ПО сопоставляется только с тем, проходит ли его собственный путь, но, условно говоря, сопоставление, используемое в настоящее время, является конгруэнтным сопоставлением.Это очень просто без участия регулярное сопоставление в экспресс-фреймворке.

Чтобы реализовать логику сопоставления, необходимо четко знать, какой путь соответствует какой функции обработки.Здесь определены три переменные:

  • req.originalUrl Исходный путь запроса.
  • req.url Текущий путь.
  • req.baseUrl родительский путь.

В основном измените функцию Router.handle, которая в основном отвечает за извлечение текущего сегмента пути, что удобно для сопоставления с заранее переданным путем.

proto.handle = function(req, res, done) {
    var self = this,
        method = req.method,
        idx = 0, stack = self.stack,
        removed = '', slashAdded = false;


    //获取当前父路径
    var parentUrl = req.baseUrl || '';
    //保存父路径
    req.baseUrl = parentUrl;
    //保存原始路径
    req.orginalUrl = req.orginalUrl || req.url;


    function next(err) {
        var layerError = (err === 'route' ? null : err);

        //如果有移除,复原原有路径
        if(slashAdded) {
            req.url = '';
            slashAdded = false;
        }


        //如果有移除,复原原有路径信息
        if(removed.length !== 0) {
            req.baseUrl = parentUrl;
            req.url = removed + req.url;
            removed = '';
        }


        //跳过路由系统
        if(layerError === 'router') {
            return done(null);
        }


        if(idx >= stack.length || layerError) {
            return done(layerError);
        }

        //获取当前路径
        var path = require('url').parse(req.url).pathname;

        var layer = stack[idx++];
        //匹配,执行
        if(layer.match(path)) {

            //处理中间件
            if(!layer.route) {
                //要移除的部分路径
                removed = layer.path;

                //设置当前路径
                req.url = req.url.substr(removed.length);
                if(req.url === '') {
                    req.url = '/' + req.url;
                    slashAdded = true;
                }

                //设置当前路径的父路径
                req.baseUrl = parentUrl + removed;

                //调用处理函数
                layer.handle_request(req, res, next);    
            } else if(layer.route._handles_method(method)) {
                //处理路由
                layer.handle_request(req, res, next);
            }    
        } else {
            layer.handle_error(layerError, req, res, next);
        }
    }

    next();
};

Этот код в основном обрабатывает два случая:

Во-первых, это промежуточное программное обеспечение маршрутизации. как:

router.use('/1', function(req, res, next) {
    res.send('first user');
});

router.use('/2', function(req, res, next) {
    res.send('second user');
});

app.use('/users', router);

В этом случае порядок сопоставления Router.handle в середине времени будет выполнять рекурсивные вызовы Router.handle, поэтому необходимо сохранить моментальный снимок текущего пути, информацию о конкретном пути в req.url, req.originalUrl и три параметра req.baseUrl.

Второй случай — промежуточное ПО без маршрутизации. как:

app.get('/', function(req, res, next) {
    res.send('home');
});

app.get('/books', function(req, res, next) {
    res.send('books');
});

В этом случае Router.handle в основном соответствует путям в порядке следования в стеке.

После изменения функции обработки вам также необходимо изменить функцию сопоставления Layer.match. В настоящее время могут быть три ситуации для создания слоя:

  • Промежуточное ПО без пути. Свойство пути по умолчанию равно/.
  • Промежуточное ПО с путями.
  • Нормальный маршрут. Если свойство пути*, который представляет собой произвольный путь.

Измените исходный слой, чтобы он был конструктором, и добавьте тег fast_star, чтобы определить, является ли путь *.

function Layer(path, fn) {
  this.handle = fn;
  this.name = fn.name || '<anonymous>';
  this.path = undefined;

  //是否为*
  this.fast_star = (path === '*' ? true : false);
  if(!this.fast_star) {
    this.path = path;
  }
}

Затем измените функцию сопоставления Layer.match.

Layer.prototype.match = function(path) {

  //如果为*,匹配
  if(this.fast_star) {
    this.path = '';
    return true;
  }

  //如果是普通路由,从后匹配
  if(this.route && this.path === path.slice(-this.path.length)) {
    return true;
  }

  if (!this.route) {
    //不带路径的中间件
    if (this.path === '/') {
      this.path = '';
      return true;
    }

    //带路径中间件
    if(this.path === path.slice(0, this.path.length)) {
      return true;
    }
  }

  return false;
};

Всего в коде оценивается четыре случая, согласно this.route различают промежуточное ПО и обычные маршруты, а затем судят отдельно.

В дополнение к обычному промежуточному программному обеспечению для экспресса также требуется промежуточное программное обеспечение ошибок, которое специально используется для обработки сообщений об ошибках. Объявление этого промежуточного программного обеспечения такое же, как у функции обработки ошибок, представленной в конце предыдущего раздела, и те же четыре параметра: err, req, res и next.

В настоящее время в Router.handle при обнаружении сообщения об ошибке он напрямую возвращает сообщение об ошибке через функцию обратного вызова и отображает страницу ошибки.

if(idx >= stack.length || layerError) {
    return done(layerError);
}

Здесь нам нужно изменить политику, изменить ее, чтобы она продолжала вызывать следующее промежуточное ПО, промежуточное ПО, пока оно не столкнется с неправильной датой.

//没有找到
if(idx >= stack.length) {
    return done(layerError);
}

Исходный код этого блока сохраняет только суждение о том, завершено ли перечисление, и переносит суждение об ошибке в позицию, где функция обработки окончательно выполняется. Причина этого в том, что как выполнение функции-обработчика, так и выполнение обработки ошибок требуют операций сопоставления путей и анализа путей, поэтому целесообразно разместить их позже.

//处理中间件
if(!layer.route) {

    ...

    //调用处理函数
    if(layerError)
        layer.handle_error(layerError, req, res, next);
    else
        layer.handle_request(req, res, next);

} else if(layer.route._handles_method(method)) {
    //处理路由
    layer.handle_request(req, res, next);
}

На данный момент часть expross, связанная с обработкой ошибок, в основном завершена.

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

Основное содержание следующего раздела состоит в том, чтобы представить два важных элемента интерфейсных и внутренних взаимодействий: запрос и ответ. Express сильно расширен на основе nodejs, поэтому необходимо его имитировать.

5. Пятая итерация

Этот раздел является пятой итерацией экспресса. Основная цель — инкапсулировать два объекта, запрос и ответ, для простоты использования.

Фактически, nodejs предоставил нам эти два объекта по умолчанию.Причина инкапсуляции состоит в том, чтобы обогатить интерфейсы двух для облегчения пользователей фреймворка.В настоящее время фреймворк уже имеет интерфейс для объекта ответа:

if(!res.send) {
    res.send = function(body) {
        res.writeHead(200, {
            'Content-Type': 'text/plain'
        });
        res.end(body);
    };
}

Если вам нужно продолжить инкапсуляцию, добавление аналогичной структуры в код, очевидно, создаст у людей ощущение беспорядка, потому что исходные версии запроса и ответа предоставляются фреймворку с помощью nodejs, а фреймворк получает два объекта, а не Class, там Есть много способов предоставить еще один набор интерфейсов поверх двух. В конечном счете новый интерфейс добавляется к объекту или к цепочке прототипов объекта. Текущий код выбирает прежний, код экспресса выбирает первый последний.

Сначала создайте два файла: request.js и response.js, которые экспортируют объекты req и res соответственно.

//request.js
var http = require('http');

var req = Object.create(http.IncomingMessage.prototype);

module.exports = req;


//response.js
var http = require('http');

var res = Object.create(http.ServerResponse.prototype);

module.exports = res;

Код двух файлов предназначен для создания объекта, который указывает на прототипы объектов запроса и ответа, предоставленных nodejs соответственно.В будущем пользовательский интерфейс экспресса может быть смонтирован на этих двух объектах единообразно.

Затем измените функцию Application.handle, потому что в этой функции есть только что выпущенный запрос и ответ. Идея очень проста: указать прототипы двух на наши самодельные req и res. Поскольку прототип объектов req и res совпадает с прототипом запроса и ответа, он не влияет на исходный интерфейс nodejs.

var request = require('./request');
var response = require('./response');

...

Application.prototype.handle = function(req, res) {

    Object.setPrototypeOf(req, request);
    Object.setPrototypeOf(res, response);


    ...
};

Здесь оригинальный res.send переносится в файл response.js.

res.send = function(body) {
    this.writeHead(200, {
        'Content-Type': 'text/plain'
    });
    this.end(body);
};

Обратите внимание, что функция не res.writeHead и res.end, а this.writeHead и this.end.

Во всей системе маршрутизации Router.stack каждый элемент на самом деле является промежуточным программным обеспечением, каждое промежуточное программное обеспечение, вероятно, будет использовать эти два объекта req и res, поэтому объекты запроса кода и ответа nodejs обеспечивают модификацию собственного кода Application.handle, это не проблема, но есть лучший способ, expross framework этой части кода инкапсулируется во внутреннее промежуточное ПО.

Чтобы убедиться, что каждое промежуточное ПО в фреймворке получает правильность этих двух параметров, внутреннее промежуточное ПО необходимо поместить в первый элемент Router.stack. Здесь код в конструкторе исходного приложения удален, вместо того, чтобы напрямую создавать систему маршрутизации Router(), но создается динамически в методе ленивой загрузки.

Удалите код исходного конструктора приложения.

function Application() {}

Добавьте функцию ленивой инициализации.

Application.prototype.lazyrouter = function() {
    if(!this._router) {
        this._router = new Router();

        this._router.use(middleware.init());
    }
};

Поскольку это ленивая инициализация, поэтому использованиеthis._routerПеред объектом обязательно вызовите эту функцию, чтобы создать его динамическиthis._routerобъект.类似如下代码:

//获取router
this.lazyrouter();
router = this._router;

Затем создайте папку с именем промежуточного ПО, предназначенную для внутренних файлов промежуточного ПО, а затем создайте файл init.js, чтобы поместить код, используемый для инициализации res и req, в Application.handle.

var request = require('../request');
var response = require('../response');

exports.init = function expressInit(req, res, next) {
    //request文件可能用到res对象
    req.res = res;

    //response文件可能用到req对象
    res.req = req;

    //修改原始req和res原型
    Object.setPrototypeOf(req, request);
    Object.setPrototypeOf(res, response);

    //继续
    next();
};

Измените исходную функцию Application.handle.

Application.prototype.handle = function(req, res) {

    ...

    // 这里无需调用lazyrouter,因为listen前一定调用了.use或者.METHODS方法。
    // 如果二者都没有调用,没有必要创建路由系统。this._router为undefined。
    var router = this._router;
    if(router) {
        router.handle(req, res, done);
    } else {
        done();
    }
};

бегатьnode test/index.jsпогнали……

В экспресс-фреймворке объекты запроса и ответа имеют много очень полезных функций, но большинство из них не имеют ничего общего со структурой фреймворка, и эти функции слишком специализированы в деталях, и они не помогают пониманию самого фреймворка. Тем не менее, есть один аспект, который необходимо тщательно изучить, то есть передача параметров внешнего и внутреннего интерфейса, а также то, как экспресс получает и классифицирует эти параметры, что все еще требует небольшого понимания.

По умолчанию существует три способа получения параметров.

  • req.query представляет строку запроса.
  • req.params представляет переменные пути.
  • Req. Те представляет собой переменную представления формы.

req.cery - самый распространенный способ, например:

// GET /search?q=tobi+ferret
req.query.q
// => "tobi ferret"

// GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse
req.query.order
// => "desc"

req.query.shoe.color
// => "blue"

req.query.shoe.type
// => "converse"

Самый простой способ получить эти параметры в фоновом режиме — проанализировать URL-адрес с помощью модуля querystring, который поставляется с nodejs. экспресс использует другой пакет npm,qs. И инкапсулировать его как еще одно внутреннее промежуточное ПО, специально отвечающее за синтаксический анализ строки запроса, загружаемой по умолчанию.

req.params — это еще один способ получить параметры из URL, например:

//路由规则  /user/:name
// GET /user/tj
req.params.name
// => "tj"

Это метод получения параметров, указанный экспресс-структурой, который очень удобен для логики пакетной обработки. В expross не реализовано, т.к проблема сопоставления путей слишком детализирована, если вам это интересно, то можете изучитьpath-to-regexpМодуль экспресс также является пакетом на нем.

req.body — это способ получить данные формы, но фреймворк не будет анализировать эти данные по умолчанию и будет возвращать значение undefined только при прямом использовании. Если вы хотите поддерживать, вам нужно добавить другое промежуточное программное обеспечение, напримерbody-parserилиmulter.

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

6. Шестая итерация

Этот раздел является шестой итерацией, и его основная цель — показать, как экспрессы интегрируются с существующими механизмами рендеринга. Вещи, связанные с движком рендеринга, включают следующие аспекты:

  • Как разработать или связать движок рендеринга.
  • Как зарегистрировать движок рендеринга.
  • Как указать путь шаблона.
  • Как отрендерить шаблонизатор.

экспресс черезapp.engine(ext, callback)метод для создания собственного механизма шаблонов. в,extотносится к расширению файла,callbackЯвляется основной функцией шаблонизатора, принимающей в качестве параметров путь к файлу, объект параметра и функцию обратного вызова.

//下面的代码演示的是一个非常简单的能够渲染 “.ntl” 文件的模板引擎。

var fs = require('fs'); // 此模板引擎依赖 fs 模块
app.engine('ntl', function (filePath, options, callback) { // 定义模板引擎
  fs.readFile(filePath, function (err, content) {
    if (err) return callback(new Error(err));
    // 这是一个功能极其简单的模板引擎
    var rendered = content.toString().replace('#title#', '<title>'+ options.title +'</title>')
    .replace('#message#', '<h1>'+ options.message +'</h1>');
    return callback(null, rendered);
  })
});

Чтобы приложение отображало файлы шаблонов, необходимы следующие настройки:

//views, 放模板文件的目录
app.set('views', './views')
//view engine, 模板引擎
app.set('view engine', 'jade')

однаждыview engineНастраивая, вам не нужно явно указывать движок или загружать модуль движка шаблона в приложение, Express уже загружен внутри. Вот как отобразить страницу:

app.get('/', function (req, res) {
  res.render('index', { title: 'Hey', message: 'Hello there!'});
});

Для реализации вышеупомянутых функций сначала определите две переменные в классе приложений, один для хранения значений, хранящихся методами app.set и app.get, а другой, чтобы сохранить соответствующую взаимосвязь между именем расширения и функцией рендеринга. в шаблонном двигателе.

function Application() {
    this.settings = {};
    this.engines = {};
}

Затем нужно добиться функции app.set.

Application.prototype.set = function(setting, val) {
      if (arguments.length === 1) {
      // app.get(setting)
      return this.settings[setting];
    }

    this.settings[setting] = val;
    return this;
};

В коде реализована не только настройка, но и передается только один параметр, который эквивалентен функции get.

Затем реализуйте функцию app.get. Поскольку для установки маршрута уже существует метод app.get, его необходимо перегрузить для этого метода.

http.METHODS.forEach(function(method) {
    method = method.toLowerCase();
    Application.prototype[method] = function(path, fn) {
        if(method === 'get' && arguments.length === 1) {
            // app.get(setting)
            return this.set(path);
        }

        ...
    };
});

Наконец, реализуйте app.engine для сопоставления расширений и функций движка.

Application.prototype.engine = function(ext, fn) {
    // get file extension
    var extension = ext[0] !== '.'
      ? '.' + ext
      : ext;

    // store engine
    this.engines[extension] = fn;

    return this;
};

Расширенный ключ поведения, единое добавление ".".

На этом функция настройки информации, связанной с механизмом шаблонов, завершена, и следующим шагом является реализация наиболее важной функции механизма рендеринга.

res.render = function(view, options, callback) {
      var app = this.req.app;
    var done = callback;
    var opts = options || {};
    var self = this;

    //如果定义回调,则返回,否则渲染
    done = done || function(err, str) {
        if(err) {
            return req.next(err);
        }

        self.send(str);
    };

    //渲染
    app.render(view, opts, done);
};

Функция рендеринга имеет три параметра: представление представляет имя шаблона, параметры — это переменная для рендеринга шаблона, а обратный вызов — это функция обратного вызова после успешного рендеринга.

Функция рендеринга вызывается непосредственно внутри функции для рендеринга, а обратный вызов done вызывается после завершения рендеринга.

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

function View(name, options) {
    var opts = options || {};

    this.defaultEngine = opts.defaultEngine;
    this.root = opts.root;

    this.ext = path.extname(name);
    this.name = name;


    var fileName = name;
    if (!this.ext) {
      // get extension from default engine name
      this.ext = this.defaultEngine[0] !== '.'
        ? '.' + this.defaultEngine
        : this.defaultEngine;

      fileName += this.ext;
    }


    // store loaded engine
    this.engine = opts.engines[this.ext];

    // lookup path
    this.path = this.lookup(fileName);
}

Есть много свойств внутри класса View, в основном, включая двигатели, корневой каталог, расширения, имена файлов и многое другое, подготовиться к дальнейшему рендерингу.

View.prototype.render = function render(options, callback) {
    this.engine(this.path, options, callback);
};

Функция рендеринга View — это функция рендеринга движка, которая вызывает начало регистрации.

Разберитесь с определением представления, а затем реализуйте функцию рендеринга шаблона app.render.

Application.prototype.render = function(name, options, callback) {
    var done = callback;
    var engines = this.engines;
    var opts = options;

    view = new View(name, {
      defaultEngine: this.get('view engine'),
      root: this.get('views'),
      engines: engines
    });


    if (!view.path) {
      var err = new Error('Failed to lookup view "' + name + '"');
      return done(err);
    }


    try {
      view.render(options, callback);
    } catch (e) {
      callback(e);
    }
};

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

Вроде все сделано, дорабатываем тест-кейс и пробуем.

var fs = require('fs'); // 此模板引擎依赖 fs 模块
app.engine('ntl', function (filePath, options, callback) { // 定义模板引擎
  fs.readFile(filePath, function (err, content) {
    if (err) return callback(new Error(err));
    // 这是一个功能极其简单的模板引擎
    var rendered = content.toString().replace('#title#', '<title>'+ options.title +'</title>')
    .replace('#message#', '<h1>'+ options.message +'</h1>');
    return callback(null, rendered);
  });
});

app.set('views', './test/views'); // 指定视图所在的位置
app.set('view engine', 'ntl'); // 注册模板引擎


app.get('/', function(req, res, next) {
    res.render('index', { title: 'Hey', message: 'Hello there!'});
});

бегатьnode test/index.jsчтобы увидеть эффект.

Приведенный выше код представляет собой движок, зарегистрированный сам по себе. Если вы хотите совместить с существующим движком шаблонов, вам нужно обратиться к методу рендеринга самого шаблона в функции обратного вызова. Конечно, для удобства экспресс-фреймворк предоставляет дефолтный Если обработчик шаблонов экспортирует этот метод, это означает, что обработчик шаблонов поддерживает экспресс-фреймворк и нет необходимости использовать app.engine для его повторной инкапсуляции.

Метод объявлен следующим образом:

 __express(filePath, options, callback)

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

//该行代码在lib/ejs.js文件的355行左右
exports.__express = exports.renderFile;

Как экспресс-фреймворк реализует эту функцию загрузки по умолчанию? Это очень просто, просто добавьте суждение в конструктор представления.

if (!opts.engines[this.ext]) {
  // load engine
  var mod = this.ext.substr(1);
  opts.engines[this.ext] = require(mod).__express;
}

Код очень простой, если функция рендеринга, соответствующая движку, не найдена, попробуйте загрузить функцию __express.

постскриптум

На данный момент, это конец этой статьи. На самом деле, есть еще много контента, который можно написать, но писать его немного раздражает, а длина слишком велика, около 13 000 слов, и кажется, что с этим немного поработают позже.

Кратко скажем, где нет вступления.

  1. О приложении.

Любой, кто читал expross-код, обнаружит, что Application — это не класс, как я написал, а промежуточное программное обеспечение, объект, который использует метод множественного наследования метода mixin, createApplication в файле express.js. Функции — это точка входа. всего каркаса.

  1. О Router.handle.

Эта функция можно сказать, чтобы выразить ядро ​​рамки, если вы понимаете функцию, всю каркас в основном освоил. Я отдаю много деталей при подражании, где внутренние функции не сказали, что есть две ключевые моменты. Во-первых, URL в виде параметров процесса этот процесс включает в себя параметры параметров. Существует функция восстановления использует функции высшего порядка способ к кешированию, тщательно понимать очень интересно. Два, Setimmediate возвращается асинхронно, причина использования асинхронной обработки, поскольку необходимость выполнения следующего кода, включая ассоциированные параметры пути, параметры в следующем обработке могут использоваться.

  1. о других функциях.

Функций слишком много, поэтому я не буду перечислять их одну за другой. Как упоминалось выше, слишком много деталей. Регулярные выражения, уровень протокола http и использование собственных функций nodejs не очень помогают для понимания. всю структуру, поэтому все они отбрасываются. Но большинство функций автономны и просты для понимания.