簡介
熟悉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的部落格
歡迎關注我的公眾號:「程式那些事」最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!