koa文檔
簡介
koa 是由 Express 原班人馬打造的,致力於成為一個更小、更富有表現力、更健壯的 Web 框架。使用 koa 編寫 web 應用,通過組合不同的 generator,可以免除重復繁瑣的回調函數嵌套,並極大地提升錯誤處理的效率。koa 不在內核方法中綁定任何中間件,它僅僅提供了一個輕量優雅的函數庫,使得編寫 Web 應用變得得心應手。
koa1.x的用generator, koa2.x用async/await, node各個版本對es6的支持程度,可參考 node.green
Koa 應用是一個包含一系列中間件 generator 函數的對象。 這些中間件函數基於 request 請求以一個類似於棧的結構組成並依次執行。 Koa 類似於其他中間件系統(比如 Ruby's Rack 、Connect 等), 然而 Koa 的核心設計思路是為中間件層提供高級語法糖封裝,以增強其互用性和健壯性,並使得編寫中間件變得相當有趣。
Koa 包含了像 content-negotiation(內容協商)、cache freshness(緩存刷新)、proxy support(代理支持)和 redirection(重定向)等常用任務方法。 與提供龐大的函數支持不同,Koa只包含很小的一部分,因為Koa並不綁定任何中間件。
hello world示例:
//koa 2.x
var Koa = require('koa');
var app = new Koa();
app.use(async (ctx, next) => {
ctx.body = 'Hello World';
});
app.listen(3000);
console.log('server listening at 3000');
中間件
Koa 的中間件通過一種更加傳統(您也許會很熟悉)的方式進行級聯,摒棄了以往 node 頻繁的回調函數造成的復雜代碼邏輯。 我們通過 generators 來實現“真正”的中間件。 Connect 簡單地將控制權交給一系列函數來處理,直到函數返回。 與之不同,當執行到 yield next 語句時,Koa 暫停了該中間件,繼續執行下一個符合請求的中間件('downstrem'),然后控制權再逐級返回給上層中間件('upstream')。
koa1.x 和 koa2.x的中間件機制是一樣的,區別在於koa1.x用generator書寫,koa2.x用async/await; async/await本質上是generator的語法糖,generator的本質是一個迭代器,需要runner去觸發下一階段的執行.
中間件的棧執行順序(后進先出),類似html標簽層層嵌套,從頂層到底層,在從底層開始閉合(其實dom的事件機制也類似)
示例:
//> koa 2.x
var Koa = require('koa');
var app = new Koa();
// mw: x-response-time
app.use(async (ctx,next) => {
let start = new Date;
await next();
let ms = new Date - start;
ctx.set('X-Response-time', ms + 'ms');
});
// mw: logger
app.use(async (ctx, next) => {
let start = new Date;
await next();
let ms = new Date - start;
console.log('%s %s - %d', ctx.method, ctx.url, ms);
});
// mw: response
app.use(async (ctx, next) => {
ctx.body = 'Hello World'; // 設置響應內容后,還可以 ctx.se(..) 設置響應頭
});
app.listen(3012);
console.log('server listening at 3012');
//> koa 1.x
var koa = require('koa');
var app = koa();
// x-response-time
app.use(function* (next) {
let start = new Date;
yield next;
var ms = new Date - start;
this.set('X-Response-Time', ms + 'ms');
});
// logger
app.use(function* (next) {
let start = new Date;
yield next;
let ms = new Date - start;
console.log('%s %s - %d', this.method, this.url, ms);
});
// response
app.use(function* (next) {
this.body = 'Hello World~~';
});
app.listen(3012);
console.log('server listen at 3012');
配置
app實例的配置屬性:
- app.name 應用名稱
- app.env 環境變量(默認為 process.env.NODE_ENV || 'development')
- app.proxy 若為true, 則解析header(請求頭),並支持 X-Forwarded-Host
- app.subdomainOffset 默認2,表示 app.subdomains, 所忽略的segment個數
app.listen(port)
app.listen(3000) 創建並返回一個http服務器.
// app.listen() 同下
var http = require('http');
http.createServer(app.callback()).listen(3000);
http.createServer(app.callback()).listen(3001); // 同一個應用運行在多個端口上
app.callback()
app.callback() 返回request callback函數,可用於 http.createServer(app.callback())
app.use(middleware)
app.use(mw) 為應用添加中間件
app.keys
app.keys = ['i am secrect', 'hello'] 設置 signed cookie 的密鑰
ctx.cookies.set('name', 'sandy', {signed: true})設置加密的cookie
錯誤處理
默認所有的錯誤信息會輸出到 stderr, 可以監聽error事件,實現自動的錯誤處理.
app.on('error', function(err, context) {
// 若處理請求階段出錯,不能響應時,會有context參數傳入回調
console.error('server error:', err);
});
上下文對象
Koa Context 將 node 的 request 和 response 對象封裝在一個單獨的對象里面,其為編寫 web 應用和 API 提供了很多有用的方法。
context 在每個 request 請求中被創建.
koa 1.x 的上下文對象為中間件的 this, koa 2.x 的上下文對象,為中間件的參數1
// koa 1.x
app.use(function* (next) {
this.body = 'good'; // this is context obj
});
// koa 2.x
app.use(async (ctx, next) => {
ctx.body = 'good2'; // ctx is context obj
});
為了便於訪問和調用,許多 context 的屬性和方法,代理了 ctx.request 和 ctx.response 所對應的等價方法, 比如說 ctx.type 和 ctx.length 代理了 response 對象中對應的方法,ctx.path 和 ctx.method 代理了 request 對象中對應的方法。
-
ctx.reqnodejs的request對象 -
ctx.resnodejs的response對象 -
ctx.requestkoa的request對象, 包裝后的nodejs的request對象 -
ctx.responsekoa的response對象 -
ctx.app應用實例的引用 -
ctx.cookies.get(name, [options])獲取coolie, koa使用了Express的cookies模塊 -
ctx.cookies.set(name, value, [options])設置cookie,options = { signed: true, expires: time, path: '/', domain: 'a.com', secure: false, httpOnly: true } -
ctx.throw(msg, [status])拋出異常// 適配 msg status兩個參數的順序,缺少的則用默認值 ctx.throw(403); ctx.throw('some error'); ctx.throw('name required', 400); ctx.throw(400, 'name required'); -
ctx.responsectx.response = false 時,可以通過ctx.res直接使用原生的response對象, 但不推薦這樣做 -
ctx.request對象釋放到ctx上的方法和屬性// url and method
ctx.urlctx.originalUrlread onlyctx.protocolread onlyctx.hostread onlyctx.hostnameread onlyctx.pathctx.queryctx.querystringctx.method
// header
ctx.headerread onlyctx.get(headerName)ctx.is()判斷 content-typectx.accepts()accepts content-typectx.acceptsEncodings()ctx.acceptsLanguage()ctx.accepetsCharsets()
// others
ctx.ipread onlyctx.ipsread onlyctx.secureread onlyctx.subdomainsread onlyctx.freshread onlyctx.staleread onlyctx.socketread only
-
ctx.response對象釋放到ctx上的方法和屬性// 響應體
ctx.bodyctx.attachment()
// 響應頭
ctx.set(name,val)設置響應頭ctx.remove()刪除響應頭ctx.status狀態碼ctx.lengthcontent-lengthctx.typecontent-typectx.lastModified緩存相關響應頭ctx.etag
// 其他
ctx.headerSent是否已發送響應頭ctx.redirect()重定向
-
ctx.request對象的完整屬性方法// url and method
ctx.request.methodctx.request.urlctx.request.originalUrlctx.request.protocolctx.request.hosthostname:portctx.request.hostnamectx.request.pathctx.request.queryeg: {color: 'blue', size: 'small'} 不支持嵌套對象ctx.request.querystringeg: foo=bar&go=finectx.request.searcheg: ?foo=bar&go=fine
// header
ctx.request.get()ctx.request.headerctx.request.typecontent-type eg: image/pngctx.request.lengthcotent-lengthctx.request.charseteg: utf-8ctx.request.is()eg: ctx.is('html'), ctx.is('text/html'), ctx.is('html', 'application/*'), ctx.is('application/json')
// others
ctx.request.fresh若用(*If-None-Match/ETag, If-Modified-Since/Last-Modified)進行緩存協商,則該屬性表示協商結果ctx.request.stale與 ctx.request.fresh 相反ctx.request.securectx.request.ipctx.request.ipsctx.request.subdomainsctx.request.socketctx.request.idempotent請求是否冪等
// 內容協商
ctx.request.accepts(types)eg: ctx.accepts('html') ctx.accepets('json', 'text')ctx.request.acceptsEncoding(encodings)eg: ctx.acceptsEncodings('gzip', 'deflate','identity')ctx.request.acceptsLanguages(langs)eg: this.acceptsLanguages('es', 'en')ctx.request.acceptsCharsets(charsets)eg: ctx.acceptsCharsets('utf-8')
-
ctx.response對象的完整屬性// 響應體
ctx.response.bodyeg: ctx.body = String/Buffer/Stream/Objectctx.response.attachment([filename])
// 響應頭
ctx.response.get(field)eg: ctx.response.get('ETag') 響應頭(field)不區分大小寫ctx.response.set(field, value)eg: ctx.set('cache-control', 'no-cache')ctx.response.remove(field)ctx.response.append(field, val)為指定頭部追加值ctx.response.headerctx.response.statuseg: ctx.response.status = 301;ctx.response.statusStringctx.response.lengthcontent-lengthctx.response.typecontent-type eg: image/png , .png , pngctx.response.lastModifiedeg: ctx.lastModified = new Date()ctx.response.etageg: ctx.response.etag = crypto.createHash('md5').update(ctx.body).digest('hex');
// 其他
-
ctx.response.headerSent -
ctx.response.vary(field)等價於 ctx.response.append('Vary', field) -
ctx.response.socket -
ctx.response.redirect(url, [alt])ctx.redirect('back'); ctx.redirect('back', '/index.html') ctx.redirect('/login'); ctx.redirect('http://google.com');
