簡介
熟悉Spring MVC的朋友應該都清楚Spring MVC是基于servlet的代碼架構,這是最傳統的web架構。然後在Spring5中引入了Spring WebFlux,這是基于reactive-netty的異步IO架構。
同樣的,nodejs在最初的Express 3基礎上發展起來了異步的koa架構。koa使用了promises和aysnc來避免JS中的回調地獄,并且簡化了錯誤處理。
今天我們要來介紹一下這個優秀的nodejs架構koa。
koa和express
koa不再使用nodejs的req和res,而是封裝了自己的ctx.request和ctx.response。
express可以看做是nodejs的一個應用架構,而koa則可以看成是nodejs 的http子產品的抽象。
和express提供了Middleware,Routing,Templating,Sending Files和JSONP等特性不同的是,koa的功能很單一,如果你想使用其他的一些功能比如routing,sending files等功能,可以使用koa的第三方中間件。
koa并不是來替換express的,就像spring webFlux并不是用來替換spring MVC的。koa隻是用Promises改寫了控制流,并且避免了回調地獄,并提供了更好的異常處理機制。
koa使用介紹
koa需要node v7.6.0+版本來支援ES2015和async function。
我們看一個最最簡單的koa應用:
const Koa = require('koa');
const app = module.exports = new Koa();
app.use(async function(ctx) {
ctx.body = 'Hello World';
});
if (!module.parent) app.listen(3000);
koa應用程式就是一個包含了很多個中間件的對象,這些中間件将會按照類似stack的執行順序一個相應request。
中間件的級聯關系
koa.use中傳入的是一個function,我們也可以稱之為中間件。
koa可以use很多個中間件,舉個例子:
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
await next();
console.log('log3');
});
app.use(async (ctx, next) => {
await next();
console.log('log2');
});
app.use(async ctx => {
console.log('log3');
});
app.listen(3000);
上面的例子中,我們調用了多次next,隻要我們調用next,調用鍊就會傳遞到下一個中間件進行處理,一直到某個中間件不再調用next
為止。
上面的代碼運作輸出:
log1
log2
log3
koa的構造函數
我們看下koa的構造函數:
constructor(options) {
super();
options = options || {};
this.proxy = options.proxy || false;
this.subdomainOffset = options.subdomainOffset || 2;
this.proxyIpHeader = options.proxyIpHeader || 'X-Forwarded-For';
this.maxIpsCount = options.maxIpsCount || 0;
this.env = options.env || process.env.NODE_ENV || 'development';
if (options.keys) this.keys = options.keys;
this.middleware = [];
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
// util.inspect.custom support for node 6+
/* istanbul ignore else */
if (util.inspect.custom) {
this[util.inspect.custom] = this.inspect;
}
}
可以看到koa接收下面幾個參數:
- app.env 預設值是NODE_ENV或者development
- app.keys 為cookie簽名的keys
看下怎麼使用:
app.keys = ['secret1', 'secret2'];
app.keys = new KeyGrip(['secret1', 'secret2'], 'sha256');
ctx.cookies.set('name', 'jack', { signed: true });
- app.proxy 是否支援代理
- app.subdomainOffset 表示子域名是從第幾級開始的,這個參數決定了request.subdomains的傳回結果,預設值為2
- app.proxyIpHeader proxy ip header預設值是X-Forwarded-For
- app.maxIpsCount 從proxy ip header讀取的最大ip個數,預設值是0表示無限制。
我們可以這樣用:
const Koa = require('koa');
const app = new Koa({ proxy: true });
或者這樣用:
const Koa = require('koa');
const app = new Koa();
app.proxy = true;
啟動http server
koa是一種web架構,web架構就需要開啟http服務,要啟動http服務,需要調用nodejs中的Server#listen()方法。
在koa中,我們可以很友善的使用koa#listen方法來啟動這個http server:
const Koa = require('koa');
const app = new Koa();
app.listen(3000);
上面的代碼相當于:
const http = require('http');
const Koa = require('koa');
const app = new Koa();
http.createServer(app.callback()).listen(3000);
當然你可以同時建立http和https的服務:
const http = require('http');
const https = require('https');
const Koa = require('koa');
const app = new Koa();
http.createServer(app.callback()).listen(3000);
https.createServer(app.callback()).listen(3001);
自定義中間件
koa中的中間件是參數值為(ctx, next)的function。在這些方法中,需要手動調用next()以傳遞到下一個middleware。
下面看一下自定義的中間件:
async function responseTime(ctx, next) {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
}
app.use(responseTime);
- 給中間件起個名字:
雖然中間件function隻接收參數(ctx, next),但是我可以将其用一個wrapper方法包裝起來,在wrapper方法中,我們給中間件起個名字 :
function logger(name) {
return async function logger(ctx, next) {
console.log(name);
await next();
};
}
- 自定義中間件的擴充:
上面的wrapper建立方式還有另外一個好處,就是可以在自定義中間件中通路傳入的參數,進而可以根據傳入的參數,對自定義中間件進行擴充。
function logger(format) {
format = format || ':method ":url"';
return async function (ctx, next) {
const str = format
.replace(':method', ctx.method)
.replace(':url', ctx.url);
console.log(str);
await next();
};
}
app.use(logger());
app.use(logger(':method :url'));
- 組合多個中間件:
當有多個中間件的情況下,我們可以使用compose将其合并:
const compose = require('koa-compose');
const Koa = require('koa');
const app = module.exports = new Koa();
// x-response-time
async function responseTime(ctx, next) {
const start = new Date();
await next();
const ms = new Date() - start;
ctx.set('X-Response-Time', ms + 'ms');
}
// logger
async function logger(ctx, next) {
const start = new Date();
await next();
const ms = new Date() - start;
if ('test' != process.env.NODE_ENV) {
console.log('%s %s - %s', ctx.method, ctx.url, ms);
}
}
// response
async function respond(ctx, next) {
await next();
if ('/' != ctx.url) return;
ctx.body = 'Hello World';
}
// composed middleware
const all = compose([
responseTime,
logger,
respond
]);
app.use(all);
if (!module.parent) app.listen(3000);
異常處理
在koa中怎麼進行異常處理呢?
通用的方法就是try catch:
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
err.status = err.statusCode || err.status || 500;
throw err;
}
});
當然你也可以自定義預設的error處理器:
app.on('error', err => {
log.error('server error', err)
});
我們還可以傳入上下文資訊:
app.on('error', (err, ctx) => {
log.error('server error', err, ctx)
});
本文作者:flydean程式那些事
本文連結:
http://www.flydean.com/koa-startup/本文來源:flydean的部落格
歡迎關注我的公衆号:「程式那些事」最通俗的解讀,最深刻的幹貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!