Собственный HTTP-сервер
Друзья, изучившие Nodejs, должны быть хорошо знакомы со следующим кодом:
const http = require('http');
let server = http.createServer((req, res) => {
// ....回调函数,输出hello world
res.end('hello world!')
})
server.listen(3000)
С помощью всего нескольких строк кода создается простой сервер, который обрабатывает HTTP-запросы в виде функций обратного вызова. Существует более четкая эквивалентная форма приведенного выше кода, код выглядит следующим образом:
let server = new http.Server();
server.on("request", function(req, res){
// ....回调函数,输出hello world
res.end('hello world!')
});
server.listen(3000);
Сначала создается экземпляр HttpServer, событие запроса отслеживается на экземпляре, а сервер отслеживается на порту 3000. HttpServer наследуется от net.Server. Он использует http_parser для разбора подключенного объекта сокета. После разбора http-заголовка будет запущено событие запроса, а данные тела будут продолжать храниться в потоке до тех пор, пока событие данных не будет использовано для получения данные.
req — это экземпляр http.IncomingMessage (который также реализует интерфейс Readable Stream).Подробности см. в документации.
res — это экземпляр http.ServerResponse (который также реализует интерфейс Writable Stream).Подробности см. в документации.
Koa пишет HTTP-сервер
Приложение Koa — это объект, который содержит набор функций промежуточного программного обеспечения, организованных и выполняемых в виде стека.
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
То, как Koa пишет http-сервер, сильно отличается от того, как мы пишем его напрямую через http-модуль node. Первый анализ показывает, что создание http-сервера узла происходит с помощью таких методов, как http.createServer.Как Koa инкапсулирует сервер в форме koa из собственного метода? Понимание этого принципа также позволяет понять концепцию дизайна фреймворка Koa.
Анализ исходного кода Koa
Лучший способ понять это — взглянуть непосредственно на исходный код Koa. Код Koa написан очень лаконично, около 1700 строк, он не слишком сложен и достоин внимания. В качестве примера для проведения анализа возьмем приведенную выше демку.Выполнение koa я разделяю на два этапа.Первый этап – этап инициализации.Основная работа заключается в инициализации используемого промежуточного ПО (форма async/await) и указании порта Прослушивание, второй этап: этап обработки запроса, запрос приходит, запрос обрабатывается.
фаза инициализации
На первом этапе используются две основные функции: app.use и app.listen. Эти две функции существуют в application.js. Основная функция app.use помещает промежуточное ПО в список под названием промежуточное ПО.
use(fn) {
...
this.middleware.push(fn);
return this;
}
Основная функция listen — создать http-сервер так, как мы делали это в первой части, и прослушивать указанный порт. Функция слушателя события запроса — это this.callback(), которая возвращает функцию типа (req, res) => {}.
listen(...args) {
const server = http.createServer(this.callback());
return server.listen(...args);
}
Проанализируйте функцию обратного вызова, код выглядит следующим образом:
/**
* Return a request handler callback
* for node's native http server.
*
* @return {Function}
* @api public
*/
callback() {
const fn = compose(this.middleware); // 将中间件函数合成一个函数fn
// ...
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res); // 使用req和res创建一个上下文环境ctx
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
На этом первый этап завершен.Благодаря анализу исходного кода мы можем узнать, что фактическое содержимое выполнения примерно такое же, как и то, что выполнялось модулем node http в нашей первой части. Тут возникает вопрос, как реализована функция compose? Возвращаемая форма функции async/await — Promise, как обеспечить ее последовательное выполнение? В начале моей догадкой было поместить следующий мидлвар в тогдашний метод результата выполнения предыдущего мидлвара.Общая идея такова:
compose(middleware) {
return () => {
let composePromise = Promise.resolve();
middleware.forEach(task => { composePromise = composePromise.then(()=>{return task&&task()}) })
return composePromise;
}
}
Конечный эффект: f1().then(f2).then(f3).. Koa использует другой подход в koa-compose:
function compose (middleware) {
// ...
return function (context, next) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1)
}))
} catch (err) {
return Promise.reject(err)
}
}
}
}
Он начинается с первого промежуточного программного обеспечения, и когда он сталкивается со следующим, он прерывает выполнение кода этого промежуточного программного обеспечения и переходит к коду в течение периода выполнения соответствующего следующего промежуточного программного обеспечения... до последнего промежуточного программного обеспечения, а затем возвращается к обратному отсчету. в обратном порядке. Выполнение кода части под вторым промежуточным ПО next будет продолжаться после завершения. Оно будет продолжать отступать до тех пор, пока не завершится выполнение кода части под первым промежуточным ПО next и не завершится выполнение всего промежуточного ПО. Таким образом реализуется то, что мы называем моделью луковичных колец.
этап обработки запроса
Когда запрос наступает, он будет ввести функцию обратного вызова из события запроса, в KOA в Handlewarequest:
handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
// koa默认的错误处理函数,它处理的是错误导致的异常结束
const onerror = err => ctx.onerror(err);
// respond函数里面主要是一些收尾工作,例如判断http code为空如何输出,http method是head如何输出,body返回是流或json时如何输出
const handleResponse = () => respond(ctx);
// 第三方函数,用于监听 http response 的结束事件,执行回调
// 如果response有错误,会执行ctx.onerror中的逻辑,设置response类型,状态码和错误信息等
onFinished(res, onerror);
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
Когда запрос поступит, сначала выполните функцию компоновки, инкапсулированную на первом этапе, а затем введите handleResponse, чтобы выполнить некоторую завершающую работу. На этом весь этап обработки запроса завершен.
Суммировать
Koa — это веб-фреймворк с очень компактным дизайном, сам исходный код не содержит промежуточного программного обеспечения, что позволяет нам комбинировать некоторые промежуточное программное обеспечение в соответствии с нашими потребностями. Он реализует шаблон лука в сочетании с async/await.