expressとkoaの比較
51251 ワード
使用体験
koa
express
注意:この文書はすべてes 6構文で作成されています.環境がサポートされていない場合は、nodeをアップグレードするか、babelを使用してトランスコードしてください.
きどうモード koaは
見えるkoa@2es 6の構文実装を採用し、
expressは複数のミドルウェアを処理する
このように書くと、端末はfirstしか印刷されず、後悔せず、フロントエンドリクエストはタイムアウトまで待機します.この問題の原因は、expressが
koaは複数のミドルウェアを処理する
expressと同様にkoaミドルウェアのパラメータも2つあり,後者がnextである.nextの機能はexpressと同様であり,ここでは後述しない.
上でkoaの
両方の使用ロジックが異なることを上述したが、koaはパラメータにcontextを与え、この構造体には要求を返すすべての情報が含まれているため、次のコードを書くことができる.
このような論理はexpressとよく似ていて、原理も同じです.このように書くと、フロントエンドの要求が得られた結果は
expressルーティング処理
expressのコードは一般的に以下の通りです.
これはよく理解できますが、リクエストパスによって異なる結果を返します.koaは?
koa分路処理
koa自体がルーティング対応をサポートしていないため、必要に応じてサードパーティ製パッケージを導入することで実現できます.koajsには簡単なrouterパッケージがあります.具体的な書き方は以下の通りです.
具体的なアプリケーションでは他のルーティングパケットを用いて行うこともでき,githubでは多くのことが検索できる.また,実装の理由から,印刷要求処理に時間がかかるという興味深い現象を紹介する.
koaバージョン
koaはpromise方式でミドルウェアを処理しているため、
これにより、入口にミドルウェアを1つ置くだけで時間のかかる記録が完了します.
expressバージョン
expressはpromiseではなくコールバック方式でミドルウェアを処理しているため,上記のような便利な方式では時間のかかる取得はできない.next()をカプセル化しても、後続のnext()がすべてカプセル化されてこそ正しい結果が得られることを保証しなければならないため、何の役にも立たない.次に、リファレンスインプリメンテーションを示します.
まとめ
koaとexpressの違いはやはり比較的に大きくて、koaの内容は少なくて、nodejs自身のcreateServer関数に対して簡単なパッケージをして、多くの延長をしていません;expressは主にkoaよりrouterが多い.両者のコードの考え方はやはり違いますが、実際の使用には大きな障害はありません.
ソース分析
koa
koaのソースコードには主に4つのファイルがあります:application.js,context.js,request.js,response.js
context.js
contextには実際の機能コードはありません.いくつかの基礎関数と変数だけで、次はコードクリップです.
request.js
このファイルには主にset関数とget関数が山積みされており、主に要求構造体の特定のフィールドを取得したり、次のipを取得する関数などの特定のフィールドを変更したりするために使用されています.コードはよく理解されています.
response.js
responseはrequestに対応し、主にresを処理するツールクラスであり、以下はresのcontent-lengthを設定および取得するためのコードクリップである.
このうち
application.js
上記で使用したappは、このファイルで定義され、koaの核心でもあります.ここでは、いくつかのメンバー関数を選択して分析します(ファイルコード全体が250行未満で、自分で読む圧力も大きくありません).
次にuse,callback,respondの3つの関数に重点を置き,実際にkoaの情報ストリームを理解してこの3つの関数のソースコードを見るだけで十分ではない.use:内容は多くありません.そのうち、1つ目の
実際には,ここでは閉パケットを用いてミドルウェア配列の遍歴を実現していることがわかる.具体的には,i+1個目のミドルウェアを
これまでkoaソース分析は終わり,koaソースコードは少なく,余分なものはなく,ルーティングさえ他のパケットを導入する必要があった.
express
expressのソースコードはkoaより多くのものがあり、ここではコア部分だけを比較し、他の部分の内容を無視します.
ここでのexpressは私たちが上述したexpressオブジェクトであり、実際にこの関数はいくつかの一般的な機能と変数をappオブジェクトにバインドしており、私たちがコードで使用している
呼び出し
ここには、最初のパラメータがパスであれば関数であれば、パラメータを判断する論理がありますが、普段はあまり書かれていません.
実際にexpressはrouterを呼び出したuseメソッドがミドルウェアを処理していることがわかります.router.useは/router/index.jsで定義され、ソースコードは以下の通りです.
その中の前の大半は主にいくつかの準備作業です(この書き方はexpressでよく見られるようです).後でkoaが直接ミドルウェアを配列にpushするのとは異なり、expressはミドルウェアをLayerにカプセル化し、これもミドルウェアの実行をよりよく制御するためである.Layerのコードは/router/layer.jsにあります.(ここでは分析しない)
次にexpressがリクエストにどのように応答するかを分析します.上のlisten部分のコードから、
実際にappはhandleメソッドを呼び出し、このメソッドはアプリケーションオブジェクトから継承され、アプリケーション.jsを表示すると次のコードが見つかります.
実際にここで呼び出されたのはrouter.handleで、routerのソースコードを見てみましょう.
この関数は長いが,ほとんどの内容がマッチングルーティング,タイプ検出などの操作であり,実際の操作は
bingo,ここでは
ミドルウェアを処理する論理的にexpressは、1つのミドルウェアが実行されるたびに、次のミドルウェアを起動するために「センター」に自発的に通知することを理解することができる.koaはチェーンプロセスとして理解でき、各ミドルウェアは後のミドルウェアを起動する.
ここまで、koaとexpressの比較分析を基本的に完成し、両者にはそれぞれ独自の特徴があり、使用中にニーズに応じて最適なソリューションを選択することができます.
koa
const Koa = require('koa');
const app = new Koa();
app.use(ctx => {
ctx.body = 'Hello Koa';
});
app.listen(3000);
express
const app = require("express")();
app.use((req,res,next)=>{
res.status(200).send("headers ..."
);
});
app.listen(3001);
注意:この文書はすべてes 6構文で作成されています.環境がサポートされていない場合は、nodeをアップグレードするか、babelを使用してトランスコードしてください.
きどうモード
new Koa()
の方式を採用し、expressは伝統的な関数形式を採用し、ソースコードを比較すると以下の通りである://koa
const Emitter = require('events');
module.exports = class Application extends Emitter {
...
}
//express
exports = module.exports = createApplication;
function createApplication() {
...
}
見えるkoa@2es 6の構文実装を採用し、
Emitter
クラスを継承しており、具体的な情報はEmitterの説明を参照することができる.これはkoa@2es 6以上の環境でのみ実行でき、低バージョンでは使用を考慮できます[email protected].一方expressは伝統的でfunctionの形式でエクスポートされています.2.中間部品の形式は両者が異なり、これは両者が中間部品を処理する論理の違いによるもので、実際にはこれも両者の最も根本的な違いであり、具体的な分析は後に残して比較し、ここでは主に両者の使用上の違いを比較し、以下に示す.expressは複数のミドルウェアを処理する
const app = require("express")();
app.use((req,res,next)=>{
console.log("first");
//next();
});
app.use((req,res,next)=>{
console.log("second");
//next();
});
app.use((req,res,next)=>{
console.log("third");
res.status(200).send("headers ..."
);
});
app.listen(3001);
このように書くと、端末はfirstしか印刷されず、後悔せず、フロントエンドリクエストはタイムアウトまで待機します.この問題の原因は、expressが
next()
をアクティブに呼び出して中間価格を継続させ、注釈を放す必要があるためです.これにより、リクエストにどのように応答するかを自主的に制御することができます.koaは複数のミドルウェアを処理する
const Koa = require('koa');
const app = new Koa();
app.use((ctx,next) => {
ctx.body = 'Hello Koa-1';
next();
});
app.use((ctx,next) => {
ctx.body = 'Hello Koa-2';
next();
});
app.use((ctx,next) => {
ctx.body = 'Hello Koa-3';
next();
});
app.listen(3000);
expressと同様にkoaミドルウェアのパラメータも2つあり,後者がnextである.nextの機能はexpressと同様であり,ここでは後述しない.
上でkoaの
next()
の機能を紹介しました.ここのnext()
は同期呼び出しが必要です.決して非同期呼び出しを採用しないでください.次の形式に書かないでください.これは未呼び出しnext()
に相当します.具体的な原因は、後のソースコード部分で分析されます. app.use((ctx,next) => {
ctx.body = 'Hello Koa-2';
setTimeout(()=>next(),3000);
//next();
});
両方の使用ロジックが異なることを上述したが、koaはパラメータにcontextを与え、この構造体には要求を返すすべての情報が含まれているため、次のコードを書くことができる.
const Koa = require('koa');
const app = new Koa();
app.use((ctx)=>{
const res = ctx.res;
res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8','Accept-Language':'zh-CN,zh;q=0.8,en;q=0.6'});
res.end(' '
);
});
// response
app.use(ctx => {
ctx.body = 'Hello Koa';
});
app.listen(3000);
このような論理はexpressとよく似ていて、原理も同じです.このように書くと、フロントエンドの要求が得られた結果は
であり、後続のapp.use
は実際に実行されなかった.expressルーティング処理
expressのコードは一般的に以下の通りです.
const app = require("express")();
app.use("/first",(req,res,next)=>{
console.log("first");
res.status(200).send("headers-first ..."
);
});
app.use("/second",(req,res,next)=>{
console.log("second");
res.status(200).send("headers-second ..."
);
});
app.use("/third",(req,res,next)=>{
console.log("third");
res.status(200).send("headers-third ..."
);
});
app.listen(3001);
これはよく理解できますが、リクエストパスによって異なる結果を返します.koaは?
koa分路処理
const Koa = require('koa');
const app = new Koa();
app.use("/",ctx => {
ctx.body = 'Hello Koa';
});
app.listen(3000);
koa自体がルーティング対応をサポートしていないため、必要に応じてサードパーティ製パッケージを導入することで実現できます.koajsには簡単なrouterパッケージがあります.具体的な書き方は以下の通りです.
// Koa Trie Router
const Koa = require('koa')
const Router = require('koa-trie-router')
let app = new Koa()
let router = new Router()
router
.use(function(ctx, next) {
console.log('* requests')
next()
})
.get(function(ctx, next) {
console.log('GET requests')
next()
})
.put('/foo', function (ctx) {
ctx.body = 'PUT /foo requests'
})
.post('/bar', function (ctx) {
ctx.body = 'POST /bar requests'
})
app.use(router.middleware())
app.listen(3000)
具体的なアプリケーションでは他のルーティングパケットを用いて行うこともでき,githubでは多くのことが検索できる.また,実装の理由から,印刷要求処理に時間がかかるという興味深い現象を紹介する.
koaバージョン
const Koa = require('koa');
const app = new Koa();
app.use((ctx,next) => {
ctx.body = 'Hello Koa-1';
let start = new Date();
next().then(()=>{
console.log("time cost:",new Date()-start);
});
});
app.use((ctx,next) => {
ctx.body = 'Hello Koa-2';
next();
});
app.use((ctx,next) => {
ctx.body = 'Hello Koa-3';
next();
});
app.listen(3000);
koaはpromise方式でミドルウェアを処理しているため、
next()
は実際にpromiseオブジェクトを返しているので、上記の簡単な方式で処理時間を記録することができる.es 7では、より簡単な書き方ができます.const Koa = require('koa');
const app = new Koa();
app.use(async (ctx,next) => {
ctx.body = 'Hello Koa-1';
let start = new Date();
await next();
console.log("time cost:",new Date()-start);
});
app.use(async (ctx,next) => {
ctx.body = 'Hello Koa-2';
//
await new Promise((resolve,reject)=>setTimeout(()=>{next();resolve();},3000));
});
app.use((ctx,next) => {
ctx.body = 'Hello Koa-3';
next();
});
app.listen(3000);
これにより、入口にミドルウェアを1つ置くだけで時間のかかる記録が完了します.
expressバージョン
expressはpromiseではなくコールバック方式でミドルウェアを処理しているため,上記のような便利な方式では時間のかかる取得はできない.next()をカプセル化しても、後続のnext()がすべてカプセル化されてこそ正しい結果が得られることを保証しなければならないため、何の役にも立たない.次に、リファレンスインプリメンテーションを示します.
let time = null;
.use('/', (req, res, next) => {
time = Date.now();
next()
})
.use('/eg', bidRequest)
.use('/', (req, res, next) => {
console.log(`<= time cost[${req.baseUrl}] : `, Date.now() - time, 'ms');
})
まとめ
koaとexpressの違いはやはり比較的に大きくて、koaの内容は少なくて、nodejs自身のcreateServer関数に対して簡単なパッケージをして、多くの延長をしていません;expressは主にkoaよりrouterが多い.両者のコードの考え方はやはり違いますが、実際の使用には大きな障害はありません.
ソース分析
koa
koaのソースコードには主に4つのファイルがあります:application.js,context.js,request.js,response.js
context.js
contextには実際の機能コードはありません.いくつかの基礎関数と変数だけで、次はコードクリップです.
inspect() {
return this.toJSON();
},
toJSON() {
return {
request: this.request.toJSON(),
response: this.response.toJSON(),
app: this.app.toJSON(),
originalUrl: this.originalUrl,
req: '' ,
res: '' ,
socket: ''
};
},
request.js
このファイルには主にset関数とget関数が山積みされており、主に要求構造体の特定のフィールドを取得したり、次のipを取得する関数などの特定のフィールドを変更したりするために使用されています.コードはよく理解されています.
get ips() {
const proxy = this.app.proxy;
const val = this.get('X-Forwarded-For');
return proxy && val
? val.split(/\s*,\s*/)
: [];
},
response.js
responseはrequestに対応し、主にresを処理するツールクラスであり、以下はresのcontent-lengthを設定および取得するためのコードクリップである.
set length(n) {
this.set('Content-Length', n);
},
get length() {
const len = this.header['content-length'];
const body = this.body;
if (null == len) {
if (!body) return;
if ('string' == typeof body) return Buffer.byteLength(body);
if (Buffer.isBuffer(body)) return body.length;
if (isJSON(body)) return Buffer.byteLength(JSON.stringify(body));
return;
}
return ~~len;
},
このうち
~~len
が使われていますが、これはちょっと面白いです.2回反転して、出力が数字であることを保証することができます.lenが文字列であれば0を返します.(初めて見た…)application.js
上記で使用したappは、このファイルで定義され、koaの核心でもあります.ここでは、いくつかのメンバー関数を選択して分析します(ファイルコード全体が250行未満で、自分で読む圧力も大きくありません).
module.exports = class Application extends Emitter {
/*
: req,res,env context, context , req res 。
*/
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);
}
/*
nodejs createServer, 。
*/
listen() {
debug('listen');
const server = http.createServer(this.callback());
return server.listen.apply(server, arguments);
}
//
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
if (isGeneratorFunction(fn)) {
deprecate('Support for generators will be removed in v3. ' +
'See the documentation for examples of how to convert old middleware ' +
'https://github.com/koajs/koa/blob/master/docs/migration.md');
fn = convert(fn);
}
debug('use %s', fn._name || fn.name || '-');
this.middleware.push(fn);
return this;
}
//
callback() {
const fn = compose(this.middleware);
if (!this.listeners('error').length) this.on('error', this.onerror);
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
createContext(req, res) {
const context = Object.create(this.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.originalUrl = request.originalUrl = req.url;
context.cookies = new Cookies(req, res, {
keys: this.keys,
secure: request.secure
});
request.ip = request.ips[0] || req.socket.remoteAddress || '';
context.accept = request.accept = accepts(req);
context.state = {};
return context;
}
//
function respond(ctx) {
// allow bypassing koa
if (false === ctx.respond) return;
const res = ctx.res;
if (!ctx.writable) return;
let body = ctx.body;
const code = ctx.status;
// ignore body
if (statuses.empty[code]) {
// strip headers
ctx.body = null;
return res.end();
}
if ('HEAD' == ctx.method) {
if (!res.headersSent && isJSON(body)) {
ctx.length = Buffer.byteLength(JSON.stringify(body));
}
return res.end();
}
// status body
if (null == body) {
body = ctx.message || String(code);
if (!res.headersSent) {
ctx.type = 'text';
ctx.length = Buffer.byteLength(body);
}
return res.end(body);
}
// 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);
}
次にuse,callback,respondの3つの関数に重点を置き,実際にkoaの情報ストリームを理解してこの3つの関数のソースコードを見るだけで十分ではない.use:内容は多くありません.そのうち、1つ目の
if
はセキュリティチェックに使用され、2つ目のif
はgenerator関数の互換性を実現するために使用されます.具体的な実現過程はis-generator-function
というパッケージの中にあります.興味があれば見てもいいですが、やはりテクニックがあります.参考にしてください.useは最終的に中間部品push
をthis.middleware
配列に入れただけで、実質的な論理操作はありません.respond:この関数はリクエストに応答する場所です.これも、リクエストにアクティブに応答しなくてもいい理由です.関数には多くの判断がなされており,主に二次応答や特殊な特定の応答の要求を防止する.callback:callbackは、createServer
関数のコールバック、すなわちhandleRequest
関数を生成するために使用される.handleRequest
の戻り値はpromiseオブジェクトです.ここでは、中間部品配列を関数に変換して、使いやすいようにするcompose
メソッドが呼び出されていることに注意してください.具体的な実装はkoa-compose
というパッケージにあり、ここではその一部を抜粋して分析する.// compose(...)
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)
}
}
}
実際には,ここでは閉パケットを用いてミドルウェア配列の遍歴を実現していることがわかる.具体的には,i+1個目のミドルウェアを
next
としてi個目のミドルウェアに伝達することが考えられるが,これは,next
をアクティブに呼び出さなければならない理由であり,next
をアクティブに呼び出さなければループが早期に終了し,後続のミドルウェアが実行されないと考えられる.これまでkoaソース分析は終わり,koaソースコードは少なく,余分なものはなく,ルーティングさえ他のパケットを導入する必要があった.
express
expressのソースコードはkoaより多くのものがあり、ここではコア部分だけを比較し、他の部分の内容を無視します.
//express.js
function createApplication() {
var app = function(req, res, next) {
app.handle(req, res, next);
};
mixin(app, EventEmitter.prototype, false);
mixin(app, proto, false);
// expose the prototype that will get set on requests
app.request = Object.create(req, {
app: { configurable: true, enumerable: true, writable: true, value: app }
})
// expose the prototype that will get set on responses
app.response = Object.create(res, {
app: { configurable: true, enumerable: true, writable: true, value: app }
})
app.init();
return app;
}
ここでのexpressは私たちが上述したexpressオブジェクトであり、実際にこの関数はいくつかの一般的な機能と変数をappオブジェクトにバインドしており、私たちがコードで使用している
app.eg_funcs
のような方法はここから継承されていることがわかります.実際には、オブジェクトは、app.listen()
を使用してサービスを開始することに限定されず、以下はlisten
関数のコードである.//application.js
app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
呼び出し
app.listen
は、サーバを起動することができます.実際には、この2つのコードを直接手動で書いてサービスを起動することもできます.socket.io
およびhttps
サービスでは、このプロセスを自分で完了する必要があります.次はapp.use
のソースです.app.use = function use(fn) {
var offset = 0;
var path = '/';
// default path to '/'
// disambiguate app.use([fn])
if (typeof fn !== 'function') {
var arg = fn;
while (Array.isArray(arg) && arg.length !== 0) {
arg = arg[0];
}
// first arg is the path
if (typeof arg !== 'function') {
offset = 1;
path = fn;
}
}
var fns = flatten(slice.call(arguments, offset));
if (fns.length === 0) {
throw new TypeError('app.use() requires middleware functions');
}
// setup router
this.lazyrouter();
var router = this._router;
fns.forEach(function (fn) {
// non-express app
if (!fn || !fn.handle || !fn.set) {
return router.use(path, fn);
}
debug('.use app under %s', path);
fn.mountpath = path;
fn.parent = this;
// restore .app property on req and res
router.use(path, function mounted_app(req, res, next) {
var orig = req.app;
fn.handle(req, res, function (err) {
setPrototypeOf(req, orig.request)
setPrototypeOf(res, orig.response)
next(err);
});
});
// mounted an app
fn.emit('mount', this);
}, this);
return this;
}
ここには、最初のパラメータがパスであれば関数であれば、パラメータを判断する論理がありますが、普段はあまり書かれていません.
実際にexpressはrouterを呼び出したuseメソッドがミドルウェアを処理していることがわかります.router.useは/router/index.jsで定義され、ソースコードは以下の通りです.
proto.use = function use(fn) {
var offset = 0;
var path = '/';
// default path to '/'
// disambiguate router.use([fn])
if (typeof fn !== 'function') {
var arg = fn;
while (Array.isArray(arg) && arg.length !== 0) {
arg = arg[0];
}
// first arg is the path
if (typeof arg !== 'function') {
offset = 1;
path = fn;
}
}
var callbacks = flatten(slice.call(arguments, offset));
if (callbacks.length === 0) {
throw new TypeError('Router.use() requires middleware functions');
}
for (var i = 0; i < callbacks.length; i++) {
var fn = callbacks[i];
if (typeof fn !== 'function') {
throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn));
}
// add the middleware
debug('use %o %s', path, fn.name || '' )
var layer = new Layer(path, {
sensitive: this.caseSensitive,
strict: false,
end: false
}, fn);
layer.route = undefined;
this.stack.push(layer);
}
return this;
};
その中の前の大半は主にいくつかの準備作業です(この書き方はexpressでよく見られるようです).後でkoaが直接ミドルウェアを配列にpushするのとは異なり、expressはミドルウェアをLayerにカプセル化し、これもミドルウェアの実行をよりよく制御するためである.Layerのコードは/router/layer.jsにあります.(ここでは分析しない)
次にexpressがリクエストにどのように応答するかを分析します.上のlisten部分のコードから、
createServer
にthisを送信しました.このthisはexpress()
の戻り値で、application.jsに定義されています.ソースコードは以下の通りです.var app = function(req, res, next) {
app.handle(req, res, next);
};
実際にappはhandleメソッドを呼び出し、このメソッドはアプリケーションオブジェクトから継承され、アプリケーション.jsを表示すると次のコードが見つかります.
// this._router
this._router = new Router({
caseSensitive: this.enabled('case sensitive routing'),
strict: this.enabled('strict routing')
});
this._router.use(query(this.get('query parser fn')));
this._router.use(middleware.init(this));
// this._router
app.handle = function handle(req, res, callback) {
var router = this._router;
// final handler
var done = callback || finalhandler(req, res, {
env: this.get('env'),
onerror: logerror.bind(this)
});
// no routes
if (!router) {
debug('no routes defined on app');
done();
return;
}
router.handle(req, res, done);
};
実際にここで呼び出されたのはrouter.handleで、routerのソースコードを見てみましょう.
proto.handle = function handle(req, res, out) {
var self = this;
debug('dispatching %s %s', req.method, req.url);
var idx = 0;
var protohost = getProtohost(req.url) || ''
var removed = '';
var slashAdded = false;
var paramcalled = {};
// store options for OPTIONS request
// only used if OPTIONS request
var options = [];
// middleware and routes
var stack = self.stack;
// manage inter-router variables
var parentParams = req.params;
var parentUrl = req.baseUrl || '';
var done = restore(out, req, 'baseUrl', 'next', 'params');
// setup next layer
req.next = next;
// for options requests, respond with a default if nothing else responds
if (req.method === 'OPTIONS') {
done = wrap(done, function(old, err) {
if (err || options.length === 0) return old(err);
sendOptionsResponse(res, options, old);
});
}
// setup basic req values
req.baseUrl = parentUrl;
req.originalUrl = req.originalUrl || req.url;
next();
function next(err) {
var layerError = err === 'route'
? null
: err;
// remove added slash
if (slashAdded) {
req.url = req.url.substr(1);
slashAdded = false;
}
// restore altered req.url
if (removed.length !== 0) {
req.baseUrl = parentUrl;
req.url = protohost + removed + req.url.substr(protohost.length);
removed = '';
}
// signal to exit router
if (layerError === 'router') {
setImmediate(done, null)
return
}
// no more matching layers
if (idx >= stack.length) {
setImmediate(done, layerError);
return;
}
// get pathname of request
var path = getPathname(req);
if (path == null) {
return done(layerError);
}
// find next matching layer
var layer;
var match;
var route;
while (match !== true && idx < stack.length) {
layer = stack[idx++];
match = matchLayer(layer, path);
route = layer.route;
if (typeof match !== 'boolean') {
// hold on to layerError
layerError = layerError || match;
}
if (match !== true) {
continue;
}
if (!route) {
// process non-route handlers normally
continue;
}
if (layerError) {
// routes do not match with a pending error
match = false;
continue;
}
var method = req.method;
var has_method = route._handles_method(method);
// build up automatic options response
if (!has_method && method === 'OPTIONS') {
appendMethods(options, route._options());
}
// don't even bother matching route
if (!has_method && method !== 'HEAD') {
match = false;
continue;
}
}
// no match
if (match !== true) {
return done(layerError);
}
// store route for dispatch on change
if (route) {
req.route = route;
}
// Capture one-time layer values
req.params = self.mergeParams
? mergeParams(layer.params, parentParams)
: layer.params;
var layerPath = layer.path;
// this should be done for the layer
self.process_params(layer, paramcalled, req, res, function (err) {
if (err) {
return next(layerError || err);
}
if (route) {
return layer.handle_request(req, res, next);
}
trim_prefix(layer, layerError, layerPath, path);
});
}
function trim_prefix(layer, layerError, layerPath, path) {
if (layerPath.length !== 0) {
// Validate path breaks on a path separator
var c = path[layerPath.length]
if (c && c !== '/' && c !== '.') return next(layerError)
// Trim off the part of the url that matches the route
// middleware (.use stuff) needs to have the path stripped
debug('trim prefix (%s) from url %s', layerPath, req.url);
removed = layerPath;
req.url = protohost + req.url.substr(protohost.length + removed.length);
// Ensure leading slash
if (!protohost && req.url[0] !== '/') {
req.url = '/' + req.url;
slashAdded = true;
}
// Setup base URL (no trailing slash)
req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
? removed.substring(0, removed.length - 1)
: removed);
}
debug('%s %s : %s', layer.name, layerPath, req.originalUrl);
if (layerError) {
layer.handle_error(layerError, req, res, next);
} else {
layer.handle_request(req, res, next);
}
}
};
この関数は長いが,ほとんどの内容がマッチングルーティング,タイプ検出などの操作であり,実際の操作は
next()
関数に集中しており,koaと同様に,ここでも閉パケットを用いてミドルウェア配列をループする.next()
の実行部分を見ると、通常、実際の操作はlayer.handle_request
によって行われています.次に、layer.js
のソースコードを見ます.//
function Layer(path, options, fn) {
if (!(this instanceof Layer)) {
return new Layer(path, options, fn);
}
debug('new %o', path)
var opts = options || {};
this.handle = fn;
this.name = fn.name || '' ;
this.params = undefined;
this.path = undefined;
this.regexp = pathRegexp(path, this.keys = [], opts);
// set fast path flags
this.regexp.fast_star = path === '*'
this.regexp.fast_slash = path === '/' && opts.end === false
}
//
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next);
} catch (err) {
next(err);
}
};
bingo,ここでは
use
を呼び出すときに初期化されたLayer構造体であり,実際にはlayer.handleにミドルウェア関数を付与しているが,実際の処理関数handle_request
ではthis.handle
を呼び出す.やっとデータ処理の根源が见つかりました....ここで実际にはrouter
のnext()
はミドルウェアコールバックのプロセスを开始しただけで、次のミドルウェアに自分を伝え、后のミドルウェアはnext()
をアクティブに呼び出すことで伝えることができます.ミドルウェアを処理する論理的にexpressは、1つのミドルウェアが実行されるたびに、次のミドルウェアを起動するために「センター」に自発的に通知することを理解することができる.koaはチェーンプロセスとして理解でき、各ミドルウェアは後のミドルウェアを起動する.
ここまで、koaとexpressの比較分析を基本的に完成し、両者にはそれぞれ独自の特徴があり、使用中にニーズに応じて最適なソリューションを選択することができます.