Да ладно, почти все знания о коа, которые вы хотите, здесь!

Node.js koa

Я написал много демонстраций с koa раньше и добавил реальное онлайн-приложение, но я никогда не видел его исходный код.

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

Это на самом деле только 4 файла:

  • application.js (главный файл)
  • context.js (создает объект контекста для сетевых запросов)
  • request.js (обертка объекта запроса koa)
  • response.js (обертка объекта ответа koa)

alt

пройти черезpackage.jsonфайл, мы можем ясно видеть:

alt

application.jsЭто входной файл, так что давайте зайдем и посмотрим.

alt

основной метод

  • listen
  • use

Основное использование

const Koa = require('koa');
const app = new Koa();
app.listen(3000);
app.use((ctx, next) => {
      ctx.body = "hello"
})

listen

alt

Это просто услуга.

вот одинdebugмодуль, который можно использовать для отладки. (Предполагается, что для вашей переменной среды установлено значение DEBUG, иначе вы не увидите вывод)

код функции обратного вызова:

alt

use

Метод использования, аннотация, данная исходным кодом:

Use the given middleware fn.

В Koa весь сервис строится через промежуточное ПО.

Реализация метода use очень проста:

alt

В приведенной выше функции обратного вызова есть код:

const fn = componse(this.middleware)

Он используется для объединения всего промежуточного программного обеспечения

промежуточное ПО

alt

Например, у нас есть этот фрагмент кода:

let fn1 = (ctx,next)=>{
    console.log(1);
    next();
    console.log(2);
}
let fn2 = (ctx,next)=>{
    console.log(3);
    next();
    console.log(4);
}
let fn3 = (ctx,next)=>{
    console.log(5);
    next();
    console.log(6);
}

Надеюсь получить: 1, 3, 5, 6, 4, 2 результата.

Этот код проще:

let fns = [fn1,fn2,fn3]; 
function dispatch(index){
    let middle = fns[index];
    // 判断一下临界点
    if(fns.length === index) return function(){}
    middle({},()=>dispatch(index+1));
}
dispatch(0);

После понимания способа написания синхронизации, когда промежуточное ПО написано как asyn await, его легко написать.

function dispatch(index){
    let middle = fns[index];
    if(fns.length === index) return Promise.resolve()
    return Promise.resolve(middle({},()=>dispatch(index+1)))
}

Давайте посмотрим на код компоновки:

alt

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

легкие ошибки

const Koa = require('koa');
const app = new Koa();
app.listen(3000);
function ajax(){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            resolve("123");
        },3000)
    })
}
app.use(async (ctx,next)=>{
    console.log(1);
    next();
    console.log(2);
});

app.use(async (ctx, next) => {
    ctx.body = await ajax();
})

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

ctx

alt

ПосмотримcreateContextРеализация исходного кода:

alt

request

request.js

alt

Это переупаковка предыдущего объекта req.

Здесь используется расширенный синтаксис: get/set, аналогичный Object.definePrototype, который в основном может выполнять некоторую логическую обработку во время установки.

response

response.js

Подобно тому, как это обрабатывает request.js. Здесь я выдернул абзац основного письма:

{
    get body() {
        return this._body;
    },
    set body(val) {
        const original = this._body;
        this._body = val;

        // no content
        if (null == val) {
            if (!statuses.empty[this.status]) this.status = 204;
            this.remove('Content-Type');
            this.remove('Content-Length');
            this.remove('Transfer-Encoding');
            return;
        }

        // set the status
        if (!this._explicitStatus) this.status = 200;

        // set the content-type only if not yet set
        const setType = !this.header['content-type'];

        // string
        if ('string' == typeof val) {
            if (setType) this.type = /^\s*</.test(val) ? 'html' : 'text';
            this.length = Buffer.byteLength(val);
            return;
        }

        // buffer
        if (Buffer.isBuffer(val)) {
            if (setType) this.type = 'bin';
            this.length = val.length;
            return;
        }

        // stream
        if ('function' == typeof val.pipe) {
            onFinish(this.res, destroy.bind(null, val));
            ensureErrorHandler(val, err => this.ctx.onerror(err));

            // overwriting
            if (null != original && original != val) this.remove('Content-Length');

            if (setType) this.type = 'bin';
            return;
        }

        // json
        this.remove('Content-Length');
        this.type = 'json';
    }
}

context

context.jsТо, что делает файл, более интересно.

alt

Он создает слой прокси и напрямую монтирует некоторые методы атрибутов под запросом и некоторые методы атрибутов под ответом на объект ctx.

Например, пройтиctx.request.urlчтобы получить путь запроса, теперь просто напишитеctx.urlВот и все.

delegateЭта библиотека, давайте кратко рассмотрим, в основном рассмотрим два метода:

alt

Можем еще немного упростить:

let proto = {

}
function delateGetter(property,name){
    proto.__defineGetter__(name,function(){
        return this[property][name];
    })
}
function delateSetter(property,name){
    proto.__defineSetter__(name,function(val){
        this[property][name] = val;
    })
}
delateGetter('request','query');
delateGetter('request','method')
delateGetter('response','body');
delateSetter('response','body');

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

Реализация некоторого промежуточного программного обеспечения

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

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

koa-static

koa-static — это простое статическое промежуточное ПО, исходный код которого находится вздесь, основная логика реализованаkoa-sendЗавершено, но я его перевернул и в нем нет обработки etag.

alt

Мы также можем сами написать простейшее статическое промежуточное ПО:

const path = require('path');
const util = require('util');
const fs = require('fs');
const stat = util.promisify(fs.stat);
function static(p){
    return async (ctx,next)=>{
        try{
            p = path.join(p,'.'+ctx.path);
            let stateObj = await stat(p);
            console.log(p);
            if(stateObj.isDirectory()){
                
            }else{
                ctx.body = fs.createReadStream(p);
            }
        }catch(e){
            console.log(e)
            await next();
        }
    }
}    

body-parser

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

function bodyParser(){
    return async (ctx,next)=>{
        await new Promise((resolve,reject)=>{
            let buffers = [];
            ctx.req.on('data',function(data){
                buffers.push(data);
            });
            ctx.req.on('end',function(){
                ctx.request.body = Buffer.concat(buffers)
                resolve();
            });
        });
        await next();
    }
}
module.exports = bodyParser;

не более, чемBuffer.concat(buffers)Необходимо будет обработать несколько ситуаций, таких как форма, json, файл и т. д.

существуетkoa-bodyparser, оно используетco-bodyОдин слой упаковки.

Обработка формы и json относительно проста:

querystring.parse(buff.toString()); // form的处理

JSON.parse(buff.toString()); // json的处理

Здесь нужно сказать, как обрабатывается файл:

alt

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

Buffer.prototype.split = function(sep){
    let pos = 0;
    let len = Buffer.from(sep).length;
    let index = -1;
    let arr = [];
    while(-1!=(index = this.indexOf(sep,pos))){
        arr.push(this.slice(pos,index));
        pos = index+len;
    }
    arr.push(this.slice(pos));
    return arr;
}
// 核心实现
let type = ctx.get('content-type');
let buff = Buffer.concat(buffers);
let fields = {}
if(type.includes('multipart/form-data')){
    let sep = '--'+type.split('=')[1];
    let lines = buff.split(sep).slice(1,-1);
    lines.forEach(line=>{
        let [head,content] = line.split('\r\n\r\n');
        head = head.slice(2).toString();
        content = content.slice(0,-2);
        let [,name] = head.match(/name="([^;]*)"/);
        if(head.includes('filename')){
            // 取除了head的部分
            let c = line.slice(head.length+6);
            let p = path.join(uploadDir,Math.random().toString());
            require('fs').writeFileSync(p,c)
            fields[name] = [{path:p}];
        } else {
           fields[name] = content.toString();
        }
    })
}
ctx.request.fields = fields;

конечно нравитсяkoa-better-bodyИспользуемая в нем обработка файлов не использует разделение. он использовалformidable

alt

alt

Операция по перехвату идетmultipart_parser.jsобрабатывается в файле.

koa-router

Основное использование

var Koa = require('koa');
var Router = require('koa-router');

var app = new Koa();
var router = new Router();

router.get('/', (ctx, next) => {
  // ctx.router available
});

app
  .use(router.routes())
  .use(router.allowedMethods());

принцип

Есть статья о Наггетс:Интерпретация и реализация простого koa-маршрутизатора

alt

Я также проанализирую его в соответствии с идеей просмотра исходного кода.

router.routesзаключается в возврате промежуточного программного обеспечения:

Router.prototype.routes = Router.prototype.middleware = function () {
  var router = this;

  var dispatch = function dispatch(ctx, next) {
    debug('%s %s', ctx.method, ctx.path);

    var path = router.opts.routerPath || ctx.routerPath || ctx.path;
    var matched = router.match(path, ctx.method);
    var layerChain, layer, i;

    if (ctx.matched) {
      ctx.matched.push.apply(ctx.matched, matched.path);
    } else {
      ctx.matched = matched.path;
    }

    ctx.router = router;

    if (!matched.route) return next();

    var matchedLayers = matched.pathAndMethod
    var mostSpecificLayer = matchedLayers[matchedLayers.length - 1]
    ctx._matchedRoute = mostSpecificLayer.path;
    if (mostSpecificLayer.name) {
      ctx._matchedRouteName = mostSpecificLayer.name;
    }

    layerChain = matchedLayers.reduce(function(memo, layer) {
      memo.push(function(ctx, next) {
        ctx.captures = layer.captures(path, ctx.captures);
        ctx.params = layer.params(path, ctx.captures, ctx.params);
        ctx.routerName = layer.name;
        return next();
      });
      return memo.concat(layer.stack);
    }, []);

    return compose(layerChain)(ctx, next);
  };

  dispatch.router = this;

  return dispatch;
};

Что он делает, так это то, что входящий запрос будет проходить через router.match, а затем помещать функцию выполнения совпадающего маршрута в массив, объединять его обратно и выполнять через функцию compose(koa-compose).

как мы пишемrouter.get/post, все, что он делает, это регистрирует маршрут, а затем вставляет экземпляр слоя в this.stack:

alt

alt

Также как сопоставление специальных символов, таких как:/:id/:name, он используетpath-to-regexpделать обработку.

Поняв вышеизложенное в сочетании с анализом исходного кода Nuggets, вы в принципе можете это сделать.

Суммировать

Вещи Koa кажутся относительно простыми, но есть еще много вещей, которые не были проанализированы, например, прокси в исходном коде.

Однако, согласно правилу 28, нам в основном нужно освоить только 80% реализации исходного кода.

последний из последних

Чтобы рекламировать мой блог, пожалуйста, посетите:передняя часть винглета