Kooa 2中間部品の原理解析――見たらすぐ書きます.
16779 ワード
原文を読む
前言
KKAのタマネギの模型は紹介します.
今回はタマネギモデルの実現原理に対してあまりにも多くの解剖を行いません.主にAPIの使い方とタマネギモデルに基づいて中間部品を分析します.
koa-bodyparserミドルウェアシミュレーション
npm install koa koa-bodyparser
koa-bodyparserの具体的な使い方は以下の通りです.
まず、
koa-better-bodyミドルウェアシミュレーション
npm install koa koa-better-body koa-convert path uuid
koa-better-bodyの具体的な使い方は以下の通りです.
koa-view中間部品シミュレーション
Nodeテンプレートは、私たちがよく使うツールです.ビジネスの最中にページをレンダリングしてくれます.テンプレートの種類が多いので、
npm install koa koa-view ejs
以下はejsのテンプレートファイルです.
koa-static中間部品シミュレーション
以下は
npm install koa koa-static mime
koa-staticの具体的な使い方は以下の通りです.
koa-router中間部品シミュレーション
npm install koa koa-router
締め括りをつける
上記のいくつかの中間部品を分析してシミュレーションしましたが、実は
前言
Koa 2.x
バージョンは今最も流行っているNodeJSフレームワークで、Koa 2.0
のソースコードは特別に簡略化されています.Express
パッケージの機能ほど多くないです.ですから、ほとんどの機能はKoa
開発チーム(Express
と同じ製品です.)とコミュニティ貢献者がKoa
のNodeJSのパッケージ特性に対して実現した中間部品に提供しています.中間部品を導入し、対応する位置にKoa
のuse
方法を呼び出し、これによって内部動作ctx
を通じていくつかの機能を実現することができます.次に、一般的な中間部品の実現原理と自分と他の人のためにどのように開発すべきかを議論します.KKAのタマネギの模型は紹介します.
今回はタマネギモデルの実現原理に対してあまりにも多くの解剖を行いません.主にAPIの使い方とタマネギモデルに基づいて中間部品を分析します.
//
// Koa
const Koa = require("koa");
//
const app = new Koa();
app.use(async (ctx, next) => {
console.log(1);
await next();
console.log(2);
});
app.use(async (ctx, next) => {
console.log(3);
await next();
console.log(4);
});
app.use(async (ctx, next) => {
console.log(5);
await next();
console.log(6);
});
//
app.listen(3000);
// 1
// 3
// 5
// 6
// 4
// 2
Koa
のKoa
方法は非同期をサポートするものであることを知っています.したがって、通常の玉ねぎモデルの実行順序に従ってコードを実行するためには、use
を呼び出したときにコードを待つ必要があり、非同期の終了を待ってからまた下方に実行する必要があります.next
ではKoa
を使用することを提案しています.導入されたミドルウェアはいずれもasync/await
法で呼び出されており、use
の各々のミドルウェアはKoa
関数に戻るものであると分析できる.koa-bodyparserミドルウェアシミュレーション
async
の原理を分析するには、まず使い方と役割を知る必要があります.koa-bodyparser
のミドルウェアは私たちのkoa-bodyparser
要求とフォームに提出されたクエリ文字列をオブジェクトに変換し、post
に掛けます.他のミドルウエアやインターフェースで値を取って、使う前にインストールする必要があります.npm install koa koa-bodyparser
koa-bodyparserの具体的な使い方は以下の通りです.
// koa-bodyparser
const Koa = require("koa");
const bodyParser = require("koa-bodyparser");
const app = new Koa();
//
app.use(bodyParser());
app.use(async (ctx, next) => {
if (ctx.path === "/" && ctx.method === "POST") {
// ctx.request.body post
console.log(ctx.request.body);
}
});
app.listen(3000);
使用法によれば、ctx.request.body
の中間部品が導入されたのは実は関数であることが分かります.koa-bodyparser
において実行され、use
の特徴からKoa
の関数が実行されたと推測した後、koa-bodyparser
の関数を返してくれました.以下はシミュレーションで実現したコードです.// :my-koa-bodyparser.js
const querystring = require("querystring");
module.exports = function bodyParser() {
return async (ctx, next) => {
await new Promise((resolve, reject) => {
//
let dataArr = [];
//
ctx.req.on("data", data => dataArr.push(data));
// Promise
ctx.req.on("end", () => {
// json
let contentType = ctx.get("Content-Type");
// Buffer
let data = Buffer.concat(dataArr).toString();
if (contentType === "application/x-www-form-urlencoded") {
// , ctx.request.body
ctx.request.body = querystring.parse(data);
} else if (contentType === "applaction/json") {
// json, ctx.request.body
ctx.request.body = JSON.parse(data);
}
//
resolve();
});
});
//
await next();
};
};
上記のコードでは、async
の呼び出しと、なぜストリームを介してデータを受信し、処理データをnext
に接続し、Promiseで行われるべきかについて、いくつかの点で注意が必要です.まず、
ctx.request.body
の呼び出しです.next
のKoa
が実行することを知っています.つまり、次の中間部品の関数、つまり次のnext
のuse
のasync
関数を実行することです.後の非同期コードが実行された後、現在のコードを実行するためには、await
を使って待つ必要があります.第二に、ctx.request.body
を受信したデータがPromiseで実行されるのは、データを受信する動作が非同期であるため、データを処理する過程全体が非同期の完了を待ってからctx.request.body
にデータを掛ける必要があり、次のuse
のasync
関数でctx.request.body
にデータを取得できることを保証することができる.ですから、私たちはawait
を使って、一つのPromiseが成功したらnext
を実行します.koa-better-bodyミドルウェアシミュレーション
koa-bodyparser
は、処理フォーム提出時にはまだ弱いように見えます.ファイルアップロードはサポートされていないので、koa-better-body
はこの不足を補いましたが、koa-better-body
はKoa 1.x
バージョンのミドルウェアで、Koa 1.x
のミドルウェアはGenerator
関数を使用して実装されています.koa-convert
を使用してkoa-better-body
をKoa 2.x
のミドルウェアに変換したいです.npm install koa koa-better-body koa-convert path uuid
koa-better-bodyの具体的な使い方は以下の通りです.
// koa-better-body
const Koa = require("koa");
const betterBody = require("koa-better-body");
const convert = require("koa-convert"); // koa 1.0 koa 2.0
const path = require("path");
const fs = require("fs");
const uuid = require("uuid/v1"); //
const app = new Koa();
// koa-better-body koa 1.0 koa 2.0,
app.use(convert(betterBody({
uploadDir: path.resolve(__dirname, "upload")
})));
app.use(async (ctx, next) => {
if (ctx.path === "/" && ctx.method === "POST") {
// ctx.request.fields post
console.log(ctx.request.fields);
//
let imgPath = ctx.request.fields.avatar[0].path;
let newPath = path.resolve(__dirname, uuid());
fs.rename(imgPath, newPath);
}
});
app.listen(3000);
上記のコードのkoa-better-body
の主な機能は、フォームにアップロードされたファイルをローカルに指定されたフォルダに保存し、ctx.request.fields
属性にファイルフローのオブジェクトを掛けています.次に、アナログkoa-better-body
の機能について、Koa 2.x
に基づいてファイルアップロードのミドルウェアを実現します.// :my-koa-better-body.js
const fs = require("fs");
const uuid = require("uuid/v1");
const path = require("path");
// Buffer split
Buffer.prototype.split = function (sep) {
let len = Buffer.from(sep).length; //
let result = []; //
let start = 0; // Buffer
let offset = 0; //
//
while ((offset = this.indexOf(sep, start)) !== -1) {
//
result.push(this.slice(start, offset));
start = offset + len;
}
//
result.push(this.slice(start));
//
return result;
}
module.exports = function (options) {
return async (ctx, next) => {
await new Promise((resolve, reject) => {
let dataArr = []; //
//
ctx.req.on("data", data => dataArr.push(data));
ctx.req.on("end", () => {
//
let bondery = `--${ctx.get("content-Type").split("=")[1]}`;
//
let lineBreak = process.platform === "win32" ? "\r
" : "
";
//
let fields = {};
// buffer '' '--'
dataArr = dataArr.split(bondery).slice(1, -1);
// dataArr Buffer
dataArr.forEach(lines => {
// , + + +
// , filename + + +
let [head, tail] = lines.split(`${lineBreak}${lineBreak}`);
// , , fields
if (head.includes("filename")) {
// ,
let tail = lines.slice(head.length + 2 * lineBreak.length, -lineBreak.length);
// : + + ,
fs.createWriteStream(path.join(__dirname, options.uploadDir, uuid())).end(tail);
} else {
//
let key = head.match(/name="(\w+)"/)[1];
// key fields tail
fields[key] = tail.toString("utf8").slice(0, -lineBreak.length);
}
});
// fields ctx.request.fields , Promise
ctx.request.fields = fields;
resolve();
});
});
//
await next();
}
}
上記のコンテンツ論理は、コード注釈によって理解されてもよく、アナログkoa-better-body
の機能論理であり、我々の主な関心は、中間部品が実現されるように、上記の機能によって実現される非同期動作は依然としてデータの読み取りであり、データ処理が終了するのを待つためにまだPromiseで実行され、await
を使用して待機している.koa-view中間部品シミュレーション
Nodeテンプレートは、私たちがよく使うツールです.ビジネスの最中にページをレンダリングしてくれます.テンプレートの種類が多いので、
next
ミドルウェアが現れました.これらのテンプレートと互換して、先に依存モジュールをインストールしてください.npm install koa koa-view ejs
以下はejsのテンプレートファイルです.
ejs
panda
shen
{%>
koa-views 具体用法如下:
// koa-views
const Koa = require("koa");
const views = require("koa-views");
const path = require("path");
const app = new Koa();
//
app.use(views(path.resolve(__dirname, "views"), {
extension: "ejs"
}));
app.use(async (ctx, next) => {
await ctx.render("index", { name: "panda", age: 20, arr: [1, 2, 3] });
});
app.listen(3000);
koa-view
ミドルウェアを使用した後、koa-views
にctx
方法を追加してください.テンプレートに対するレンダリングと応答ページを実現するためには、render
が持参したejs
方法と同様に、用法からrender
方法が非同期であることが分かります.次に、簡単なrender
ミドルウェアを実現するためのシミュレーションを行います.// :my-koa-views.js
const fs = require("fs");
const path = require("path");
const { promisify } = require("util");
// Promise
const readFile = promisify(fs.radFile);
//
module.exports = function (dir, options) {
return async (ctx, next) => {
//
const view = require(options.extension);
ctx.render = async (filename, data) => {
//
let tmpl = await readFile(path.join(dir, `${filename}.${options.extension}`), "utf8");
//
let pageStr = view.render(tmpl, data);
//
ctx.set("Content-Type", "text/html;charset=utf8");
ctx.body = pageStr;
}
//
await next();
}
}
await
に掛けられたkoa-views
方法が非同期的に実行されたのは、内部読取テンプレートファイルが非同期的に実行されるため、待ち時間が必要であるため、ctx
方法はrender
関数であり、中間部品内部に私たちが使用するテンプレートを動的に導入している.render
内部で、対応するasync
方法を用いて代替データ後のページ文字列を取得し、ejs
のタイプで応答する.koa-static中間部品シミュレーション
以下は
ctx.render
ミドルウェアの使い方です.コード使用の依存性は以下の通りです.使用前にインストールする必要があります.npm install koa koa-static mime
koa-staticの具体的な使い方は以下の通りです.
// koa-static
const Koa = require("koa");
const static = require("koa-static");
const path = require("path");
const app = new Koa();
app.use(static(path.resolve(__dirname, "public")));
app.use(async (ctx, next) => {
ctx.body = "hello world";
});
app.listen(3000);
使用と分析を通じて、render
ミドルウェアの役割はサーバが要求を受けた時に、静的ファイルを処理してくれることが分かりました.ファイル名に直接アクセスすると、このファイルを探して直接応答します.もしこのファイルパスがなかったら、作文フォルダの下のhtml
を探します.もしあれば、直接応答します.存在しないなら他のミドルウェアに任せます.// :my-koa-static.js
const fs = require("fs");
const path = require("path");
const mime = require("mime");
const { promisify } = require("util");
// stat access Promise
const stat = promisify(fs.stat);
const access = promisify(fs.access)
module.exports = function (dir) {
return async (ctx, next) => {
// , join /
let realPath = path.join(dir, ctx.path);
try {
// stat
let statObj = await stat(realPath);
// , , index.html
if (statObj.isFile()) {
ctx.set("Content-Type", `${mime.getType()};charset=utf8`);
ctx.body = fs.createReadStream(realPath);
} else {
let filename = path.join(realPath, "index.html");
// catch next
await access(filename);
//
ctx.set("Content-Type", "text/html;charset=utf8");
ctx.body = fs.createReadStream(filename);
}
} catch (e) {
await next();
}
}
}
上記のロジックでは、経路の有無を検出する必要があります.私たちが導出した関数は全部koa-static
関数ですので、私たちはkoa-static
とindex.html
をPromiseに変換し、async
を用いて捕獲し、経路が不正な場合はstat
を呼び出して他のミドルウェア処理に渡します.koa-router中間部品シミュレーション
access
のフレームワークでは、ルーティングはフレーム内に内蔵されており、try...catch
に内蔵されていない.next
のミドルウェアを使用して実現され、使用前にインストールが必要である.npm install koa koa-router
Express
は機能が非常に強いです.以下は簡単に使うだけで、使用する機能によってシミュレーションします.// koa-router
const Koa = require("Koa");
const Router = require("koa-router");
const app = new Koa();
const router = new Router();
router.get("/panda", (ctx, next) => {
ctx.body = "panda";
});
router.get("/panda", (ctx, next) => {
ctx.body = "pandashen";
});
router.get("/shen", (ctx, next) => {
ctx.body = "shen";
})
//
app.use(router.routes());
app.listen(3000);
上記のようにKoa
から導出されたのはクラスであり、使用時には一例を作成する必要があり、この方法を呼び出したkoa-router
の関数を接続するが、ルーティングに適合すると、ルーティングkoa-router
の方法における経路に従ってマッチングし、シリアルで内部のコールバック関数を実行する.すべてのコールバック関数が実行されるとkoa-router
シリアルのroutes
全体が実行されます.原理は他のミドルウェアと同じです.私は以下に上で使用した機能について簡単に実現します.// :my-koa-router.js
//
class Layer {
constructor(path, cb) {
this.path = path;
this.cb = cb;
}
match(path) {
// true, false
return path === this.path;
}
}
//
class Router {
constructor() {
// ,{ path: /xxx, fn: cb }
this.layers = [];
}
get(path, cb) {
//
this.layers.push(new Layer(path, cb));
}
compose(ctx, next, handlers) {
//
function dispatch(index) {
// index , Koa next
if(index >= handlers.length) return next();
// , , dispatch(index + 1)
//
handlers[index].cb(ctx, () => dispatch(index + 1));
}
//
dispatch(0);
}
routes() {
return async (ctx, next) { // next Koa next, Koa
//
let handlers = this.layers.filter(layer => layer.match(ctx.path));
this.compose(ctx, next, handlers);
}
}
}
上記のasync
クラスを作成しました.get
方法を定義しました.もちろんKoa
などもあります.next
の意味だけを実現します.Router
内はget
メソッドを呼び出すパラメータ関数とルート文字列を一緒に構成してオブジェクトを構築して配列post
に預けました.したがって、ルーティングオブジェクトを専門に構成するクラスget
を作成し、ルーティングマッチング時にget
に従ってルーティング文字列を取得し、ルーティングフィルタリング配列中のルーティングと一致しないルーティングオブジェクトを介してget
方法を呼び出してフィルタリングされた配列をパラメータlayers
として入力することができる.シリアルは、ルートオブジェクトのコールバック関数を実行します.Layer
この方法の実現思想は非常に重要で、ctx.path
のソースコードの中で直列に接続するために使用され、compose
のソースコードの中でhandlers
のcompose
、Koa
とReact
などのモジュールに使用されています.すべての整合されたルーティングのコールバック関数が実行されるまで、配列内の次の中間redux
が実行される.promise
は、配列内のコールバック関数のパラメータthunk
とは異なるlogger
であり、配列内のルーティング対象コールバック関数のdispatch
は、次のマッチングルートのコールバックを表す.締め括りをつける
上記のいくつかの中間部品を分析してシミュレーションしましたが、実は
Koa
とnext
との比較の利点はそんなに重くないことを理解しています.開発と使用が便利で、必要な機能はすべて対応の中間部品で実現できます.中間部品を使うと、私達が処理したデータと新しい方法をnext
にマウントするなど、良いところを持ってきます.後のnext
から入ってきたコールバック関数で使用してもいいし、いくつかの共通論理を処理してくれてもいいです.next
の各コールバックで処理するほどではなく、冗長コードが大幅に減少しました.これにより、Koa
にミドルウェアを使用する過程は、典型の「装飾器」モードになります.上記の分析を通して、Express
の「タマネギモデル」と非同期の特徴も分かりました.どうやって自分のミドルウェアを開発するべきか分かりました.