Статья об анализе исходного кода koa

Node.js задняя часть API koa
Статья об анализе исходного кода koa

предисловие

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

основной механизм

Теперь давайте начнем с нуля и посмотрим, что именно происходит внутри коа?

// usage
const app = new Koa()
// source code
constructor() {
    super();

    this.proxy = false;
    this.middleware = [];
    this.subdomainOffset = 2;
    this.env = process.env.NODE_ENV || 'development';
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);
}

Это источник всего,KoaЭкземпляры класса рождаются из этого, наследуя отEvents, вы можете знать, что его подклассы могут обрабатывать асинхронные события, ноKoaКак с этим бороться пока неизвестно, поставим сначала знак вопроса. Однако в процессе создания экземпляра может быть известно, что в качестве атрибутов экземпляра инициализируются три объекта, а именноcontext request response, и знакомый массив всего глобального ПО промежуточного слояmiddleware

значок класса коа

koa.png
// usage 
app.use(async (ctx, next) => {
    // 中间件函数
});

При вызове метода использования, после подтверждения того, что онasyncВ случае функции, пройдитеpushоперация, эта функция будет добавлена ​​кmiddlewareв массиве

use(fn) {
    // 类型检查...
    this.middleware.push(fn);
    return this;
}

На данный момент у нас уже есть операции для обработки, ноkoaна самом деле не бежал

app.listen(3000);

так сказать при включенииhttpКогда серверkoaчтобы действительно начать иметь дело с нашимиhttpзапрос, так что же именно скрывается за таким аккуратным вызовом?

const server = http.createServer(this.callback());
return server.listen(...args);

koaиспользовалnodeроднойhttpпакет для созданияhttp服务, все секреты скрыты вcallback()в этом методе

// source code
const fn = compose(this.middleware);
...
return fn(ctx).then(handleResponse).catch(onerror);

koaсамо зависит отkoa-composeмодуль, изkoaдляfnС точки зрения использования,middlewareдолжны быть заключены вfnобъект, передавcontextобъект для возвратаPromise

Теперь иди вглубьkoa-composemodule, чтобы увидеть, что он делает с нашим массивом промежуточного программного обеспечения.

// source code
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)    // 递归调用dispatch
      }))
    } catch (err) {
      return Promise.reject(err)
    }
  }
}

Закрытие внутри выглядит знакомо, не так ли? Сравните еще раз

// usage 
app.use(async (ctx, next) => {
    // 中间件函数
});

здесьPromise.resolve(fn(..))Функции промежуточного программного обеспечения, которые помогают нам выполнять асинхронно, здесьnextФункция объясняет, почемуKoaВызов промежуточного программного обеспечения выполняется рекурсивно, который рекурсивно вызываетdispatchфункция для обхода массива, в то же время все промежуточные функции используют один и тот жеctx, еще раз рассматривая внешний

const handleRequest = (req, res) => {
  res.statusCode = 404;
  const ctx = this.createContext(req, res);
  const onerror = err => ctx.onerror(err);
  const handleResponse = () => respond(ctx);
  onFinished(res, onerror);
  return fn(ctx).then(handleResponse).catch(onerror);
};

return handleRequest;

contextИспользовать родной узелhttpв функции обратного вызова слушателяreq resдля дальнейшей инкапсуляции, что означает, что для каждогоhttpпросить,koaсоздастcontextИ совместно используется для всего глобального промежуточного программного обеспечения, когда все промежуточное программное обеспечение выполняется, все данные, которые должны быть возвращены, будут возвращены в унифицированныйresВозврат, поэтому мы можем получить только из промежуточного программного обеспеченияctxполучить то, что вам нужноreqДанные в обрабатываются, и, наконец,ctxвозвращатьbodyдля родногоresсделать возврат

Каждый запрос имеет уникальныйcontextОбъект, в него помещаются все вещи о запросе и возврате

createContextметод будетreq resДальнейшая инкапсуляция

// source code
const context = Object.create(this.context); // 创建一个对象,使之拥有context的原型方法,后面以此类推
const request = context.request = Object.create(this.request);
const response = context.response = Object.create(this.response);

context.app = request.app = response.app = this;
context.req = request.req = response.req = req;
context.res = request.res = response.res = res;
request.ctx = response.ctx = context;
request.response = response;
response.request = request;

по запросуcontextправила,contextдолжен существовать как временный объект, все должно быть заключено в объект, поэтомуapp req resРодились три атрибута, но я не знаю, если у вас есть вопрос, почемуapp req resтакже заперт вrequestа такжеresponseвнутри ?

сделать их обоих одинаковымиapp req resа такжеctx, чтобы передать обязанности по обработке, доступ клиентов извне, им нужен толькоctxполучить всеkoaпредоставленные данные и методы, в то время какkoaбудет продолжать дальнейшее разделение этих обязанностей, таких какrequestиспользуется для дальнейшей инкапсуляцииreqиз,responseиспользуется для дальнейшей инкапсуляцииres, так что обязанности рассредоточены, связанность уменьшена, а все ресурсы разделены, чтобы всяcontextОбладает свойством высокой сплоченности, и внутренние элементы могут иметь доступ друг к другу.

// source code
context.state = {};

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

// source code
const ctx = this.createContext(req, res);
const onerror = err => ctx.onerror(err);

этот огромныйctxПри его создании прослушиватель ошибок монтируется сразу в начале, но вcreateContextне нашел этого вonerrorметод, который должен принадлежать методу-прототипу одного из модулей, после недолгих поисков я обнаружил, что он находится вcontext.jsВ этом файле этот файл определяет все значения по умолчаниюcontextобъект имеет методы-прототипы

// application.js
// handleRequest()
const onerror = err => ctx.onerror(err);
onFinished(res, onerror);
return fn(ctx).then(handleResponse).catch(onerror);

Если во время выполнения асинхронной функции возникает исключение, которое не перехватывается внутри,koaбудет использоваться единообразноPromiseизcatchоператор для обработки ошибок, если он представлен диаграммой, это

блок-схема обработки запроса koa

koa-request-process.png

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

глобальный обработчик ошибок

// source code
// application.js

onerror(err) {
  assert(err instanceof Error, `non-error thrown: ${err}`);

  if (404 == err.status || err.expose) return;
  if (this.silent) return;

  const msg = err.stack || err.toString();
  console.error();
  console.error(msg.replace(/^/gm, '  '));
  console.error();
}

KoaС процессом обработки внутреннего ядра разобрались, теперь войдем во внутренний модуль, чтобы узнать

модуль запроса

Многоgetter setterполностью извлеченnode httpПредоставляет полезные свойства, связанные с запросом, и некоторые необходимыеhelperФункции здесь не повторяются, почти все функции в документации по APIctx.reqНиже приведены свойства под ним, стоит упомянуть, чтоthis,Головная больthis, этот фрагмент кода использует многоthis.reqспособ получить доступ к информации HTTP-запроса, предоставленной узлом изначально (потому что мы помещаем узелreqназначенныйcontext), когда вkoaПри наружном использовании мы используемctx.req.propertyNameв виде… да, я думаю, вы тоже разобрались, нам нужно связать нашthisуказать наctx.req, так что вcontext.jsВ модуле используется прокси дляthisПри правильной привязке упомянутый выше код присваивания, который позволяет этим модулям обращаться друг к другу, фактически в определенной степени сокращается.thisбеда

// 详见 delegates 包
// https://github.com/tj/node-delegates

// Koa context.js
delegate(proto, 'request')
  .method('acceptsLanguages')
  .method('acceptsEncodings')
  .access('query')
  .access('path')
  .access('url')
  .getter('origin')

// delegates 的内部核心实现,通过apply来重新绑定this
proto[name] = function(){
  return this[target][name].apply(this[target], arguments);
};

модуль ответа

response.jsто же самое внутриrequest.jsпохожи, стоит отметить, чтоwritableЭтот геттер, если на запрос был дан ответ, возвращаетtrue, где считанное значение равноthis.res.finished, во многих ошибках мы часто сталкиваемся с попыткой повторно написать заголовок ответаheaderошибка, источник этой ошибки дажеon-finishedЭтот пакет может выполнять обратный вызов, чтобы сохранить состояние запроса после завершения ответа.res

вспомогательная функция ответа

koa имеет унифицированную функцию ответа, расположенную вapplication.jsв концеctx.body ctx.statusзапросить ответ

// responses
if (Buffer.isBuffer(body)) return res.end(body);
if ('string' == typeof body) return res.end(body);
if (body instanceof Stream) return body.pipe(res);

// body: json
body = JSON.stringify(body);
if (!res.headersSent) {
  ctx.length = Buffer.byteLength(body);
}
res.end(body);

Стоит отметить, что возвращаемый корпус поддерживаетBuffer stringпоток и наиболее распространенныйjson

полный текст

Для коммерческих перепечаток, пожалуйста, свяжитесь с автором для авторизации Для некоммерческих перепечаток, пожалуйста, укажите источник Спасибо за сотрудничество!

Контактное лицо: tecker_yuknigh@163.com