Я написал много демонстраций с koa раньше и добавил реальное онлайн-приложение, но я никогда не видел его исходный код.
На этот раз я нашел время, чтобы посмотреть исходный код.
Это на самом деле только 4 файла:
- application.js (главный файл)
- context.js (создает объект контекста для сетевых запросов)
- request.js (обертка объекта запроса koa)
- response.js (обертка объекта ответа koa)
пройти черезpackage.json
файл, мы можем ясно видеть:
application.js
Это входной файл, так что давайте зайдем и посмотрим.
основной метод
- listen
- use
Основное использование
const Koa = require('koa');
const app = new Koa();
app.listen(3000);
app.use((ctx, next) => {
ctx.body = "hello"
})
listen
Это просто услуга.
вот одинdebugмодуль, который можно использовать для отладки. (Предполагается, что для вашей переменной среды установлено значение DEBUG, иначе вы не увидите вывод)
код функции обратного вызова:
use
Метод использования, аннотация, данная исходным кодом:
Use the given middleware
fn
.
В Koa весь сервис строится через промежуточное ПО.
Реализация метода use очень проста:
В приведенной выше функции обратного вызова есть код:
const fn = componse(this.middleware)
Он используется для объединения всего промежуточного программного обеспечения
промежуточное ПО
Например, у нас есть этот фрагмент кода:
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)))
}
Давайте посмотрим на код компоновки:
Базовая логика аналогична приведенному выше коду, но более строга с точки зрения логики.
легкие ошибки
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
ПосмотримcreateContext
Реализация исходного кода:
request
Это переупаковка предыдущего объекта req.
Здесь используется расширенный синтаксис: get/set, аналогичный Object.definePrototype, который в основном может выполнять некоторую логическую обработку во время установки.
response
Подобно тому, как это обрабатывает 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То, что делает файл, более интересно.
Он создает слой прокси и напрямую монтирует некоторые методы атрибутов под запросом и некоторые методы атрибутов под ответом на объект ctx.
Например, пройтиctx.request.url
чтобы получить путь запроса, теперь просто напишитеctx.url
Вот и все.
delegate
Эта библиотека, давайте кратко рассмотрим, в основном рассмотрим два метода:
Можем еще немного упростить:
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.
Мы также можем сами написать простейшее статическое промежуточное ПО:
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的处理
Здесь нужно сказать, как обрабатывается файл:
Это должен быть пакет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
Операция по перехвату идет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-маршрутизатора
Я также проанализирую его в соответствии с идеей просмотра исходного кода.
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:
Также как сопоставление специальных символов, таких как:/:id/:name
, он используетpath-to-regexpделать обработку.
Поняв вышеизложенное в сочетании с анализом исходного кода Nuggets, вы в принципе можете это сделать.
Суммировать
Вещи Koa кажутся относительно простыми, но есть еще много вещей, которые не были проанализированы, например, прокси в исходном коде.
Однако, согласно правилу 28, нам в основном нужно освоить только 80% реализации исходного кода.
последний из последних
Чтобы рекламировать мой блог, пожалуйста, посетите:передняя часть винглета