次世代nodejs 的 web框架html
koa是由Express幕後團隊打造的,目的是更小,更快,更穩定的web應用和apis。經過槓桿生成器(leveraging generators)Koa可讓你引導(ditch)回調函數,極大的提高錯誤處理。Koa核心不集成任何的中間件,其自己提供的優雅的功能套件就可以寫出既快又nice的服務器。前端
Koa須要node7.6.0或更高的版本,由於須要async function
支持。
你可使用本身的版本管理器很快的安裝一個支持的版本。node
nvm install 7 npm i koa node my-koa-app.js
想要在較低版本的node中使用async
函數,咱們建議使用babel。git
require('babel-core/register') //而後在加載應用的主代碼,這個必須在babel後面 const app = require('./app')
爲了編譯和轉化async functions
你須要在最後的壓縮版本中使用'transform-async-to-generator'或者transform-async-to-module-method
插件。例如,在你的.babelrc
文件中,進行以下設置。github
{ "plugins":["transform-async-to-generator"] }
你也可使用stage-3 persent
來代替。web
一個Koa應用是一個對象,其包含一個數組,數組有不少函數組成的中間件,這些函數集合起來等待請求,而且執行時是按照類棧的方式。koa和不少其餘中間件系統類似,你也許是用過Ruby
的Rack
,Connect
等。然而一個設計的決定行因素是提供高等級"sugar",與此同時低等級中間件層。所以提高了交互性,魯棒性(軟件設計術語,即穩定性)而且使得編寫中間件更加的帶勁!數據庫
這包括一些經常使用任務的方法——例如連接協調,緩存,代理支持,別用等。儘管提供了大量的有用的方法,可是koa仍然保持了一個較小的體積,由於沒有綁定中間件。npm
怎麼能偶少得了一個hello world
應用。json
const Koa = require('koa'); const app = new Koa(); app.use(ctx => { ctx.body = 'hello world'; }) app.listen(3000)
koa 的串聯中間件使用了一個比較傳統的方式,跟你一般用的工具很像。這是由於原先很難讓用戶有好的使用node的回調函數。然而使用異步函數咱們能偶「真正得」是有中間件。相較於鏈接的實現,這個更容易提供一些列函數/功能來控制,在最後返回即可。koa調用"downstream",控制流返回"upstream".api
下面的例子返回「hello world」,然而最開始請求先經過x-response-time
和logging
中間件來記錄請求的開始。而後經過返回的中間件產出控制。當一箇中間件執行next()
函數,來延遲和傳遞控制給下一個聲明的中間件。而後直到沒有中間件須要執行downstream
了,棧將會鬆開而後每一箇中間件復原去展示本身的「upstream」行爲。
應用設置即在實例app上的屬性。當前支持以下:
app.env 默認是NODE_ENV或者「development」。
app.proxy 當設置爲true時,porxy頭部將被信任。
app.subdomainOffset 設置.subdomains
的偏移量。替代[2]。
app.listen(...)
一個Koa應用不是一對一的呈現一個htpp服務器。一個或者多個應用也許被添加到一塊造成大的應用對應一個http服務器。
Server#listen()
。這些參數在nodejs.org都有說明。下面是一個無心義的Koa應用,綁定了端口3000app.listen(...)方法是以下的一個語法糖。
const http = require('http') const Koa = require('koa') const app = new Koa() http.createServer(app.callback()).listen(3000)
const http = require('http'); const Koa = require('koa'); const app = new Koa(); http.createServer(app.callback()).listen(3000); http.createServer(app.callback()).listen(3001);
app.callback()
返回一個回調函數,至關於http.createServer()
方法,來出了請求。
你也可使用這個方法在你的Connect/Express應用中添加koa應用。
app.use(function)
添加一個給定的中間件方法來實現它的功能。查看Middleware瞭解更多。
app.keys=
設置cookie的鍵。
這些鍵被傳遞到KeyGrip,也許你想使用本身的KeyGrip
,能夠以下作。
app.keys = ['im a newer secret', 'i like turtle']; app.keys = new KeyGrip(['im a newer secret', 'i like turtle'], 'sha256');
這些鍵也許是循環的,而且能夠設置{signed:true}
來使用。
ctx.cookies.set('name','tobi',{signed:true})
app.context
app.context是ctx的來源。你可使用app.context
添加額外的屬性到ctx。這對於建立跨越整個app應用的屬性或者方法來講是有用的,並且性能更好,在依賴上也跟簡單,能夠考慮作一個anti-pattern
。
例子,從ctx添加一個數據庫引用。
add.context.db = db() app.use(async (ctx)=>{ console.log(ctx.db) })
注意:
經過getter和setter以及Object.difineProperty()設置的屬性,你只能在app.context使用Object.defineProperty()來編輯他們。(不推薦)
使用父級的ctx
和設置來添加當前的應用。這樣添加的app就能使用到那些中間件。
除非設置app.silent是true,否則全部的出無輸出都是標準輸出。默認的錯誤輸出不會處理像是err.sttus是404或者err.expose是true。爲了自定義錯誤輸出例如日誌,你能夠添加錯誤事件監聽。
app.on('error', err => log.error('server error', err) );
當 req/res 週期中出現任何錯誤且沒法響應客戶端時,Koa 會把 Context(上下文) 實例做爲第二個參數傳遞給 error 事件:
app.on('error', (err, ctx) => log.error('server error', err, ctx) );
若是有錯誤發生, 而且還能響應客戶端(即沒有數據被寫入到 socket), Koa 會返回 500 "Internal Server Error". 這兩種狀況都會觸發 app-level 的 error 事件, 用於 logging.
一個Koa環境(實例)封裝了node原生的請求和返回對象到一個單獨的對象中,這個單獨的對象提供了許多使用的方法,可以編寫web應用和API。這些HTTP服務器開發中常常用到的操做被添加到當前等級,而不是高等級。他將強制中間件從新實現這些經常使用的功能。
一個環境Context
在每次請求時被建立,而且被引用至中間件做爲接收器,或者定義成this
。以下所示。
app.use(function *(){ this;//這裏是koa環境context this.request;//是一個koa請求 this.response;//是一個koa返回 })
不少context
環境訪問器和方法只是ctx.request
koa請求或者ctx.response
koa返回的代理,主要是爲了方便。例如ctx.type
和ctx.length
表明response
返回對象,ctx.paht
和ctx.methos
表明請求。
API 接口。
環境(Context)定義的方法和訪問器。
ctx.req Node的request
對象。
ctx.res Node的response
對象。
繞開使用koa的response處理是不支持的。避免使用下面的node屬性。
res.statusCode
res.writeHead()
res.write()
res.end()
ctx.request 一個Koa的request
對象。
ctx.response 一個Koa的response
對象。
ctx.state
推薦的經過中間件傳遞信息給前端view(顯示)的命名空間。
ctx.app 應用實例的引用。
ctx.cookies.get(name,[options])
經過options
得到cookie
名字。
signed 要求cookie已經簽名。
koa使用cookie模塊,這裏只是傳入選項便可。
ctx.coolies.set(name,value,[options])
使用options
設置name
的值value
。
signed 簽名cookie的值。
expires 使cookie的有效期到期。
path cookie路徑,默認/
。
domain cookie域
secure 保護coolie
httpOnly 服務器端cookie,默認值true
。
經過設置options
來使用cookie模塊。
ctx.throw([msg],[status],[properties])
處理拋出錯誤的輔助方法,默認.status
的值爲500時拋出,koa在返回的信息中適當處理。限免的組合也是可疑的。
this.throw(403);
this.throw('name require', 400);
this.throw(400,'name require');
this.throw('something exploded');
例如:this.throw('name require', 400)
等於
var err = new Error('name require'); err.status = 400; throw err;
注意這些是用戶自定義的錯誤,使用err.expose
發出。所以只適合某些客戶端的反饋。這些錯誤不一樣於內置的錯誤信息提醒,由於錯誤的詳細信息不會泄露。
你也許傳遞一個properties
選項對象,他和原來的錯誤信息進行了整合,對於人性化體驗頗有幫助,它報告個給請求者一個回溯流(upsteam
)。
this.throw(401,'access_denied',{user:user}); this.throw('access_denied',{user:user});
koa使用http-errors
來建立錯誤。
ctx.assert(value,[msg],[status],[properties])
跑出錯誤輔助方法,相似`.throw()`,當`!value`是相似node的`assert()`方法。
this.assert(this.sate.user,401,'User not found, Please login!');
koa使用http-assert
實現斷言(assertions
)
ctx.response
經過繞開koa內置的返回處理(response handling),你能夠明確的設置this.response = false;
若是你想使用原生的res對象處理而不是koa的response處理,那麼就使用它。
注意那種用法koa不支持。這也許會打斷koa中間件原本的功能,或者koa也被打斷。使用這個屬性最好考慮一下hack,這是使用傳統的fn(req,res)
方法和koa中間件的惟一方便的方法。
請求別名Request aliases
下面的訪問起和Request別名相等。
ctx.header
ctx.headers
ctx.method
ctx.method=
ctx.url
ctx.url=
ctx.originalUrl
ctx.origin
ctx.href
ctx.path
ctx.query
ctx.query=
ctx.querystring
ctx.querystring=
ctx.host
ctx.hostname
ctx.fresh
ctx.stale
ctx.socket
ctx.protocol
ctx.secure
ctx.ip
ctx.ips
ctx.subdomains
ctx.is()
ctx.accepts()
ctx.acceptsEncodings()
ctx.acceptsCharsets()
ctx.acceptsLanguages()
ctx.get()
返回別名Response aliases
下面的訪問起和返回別名相等
ctx.body
ctx.body=
ctx.status
ctx.status=
ctx.message
ctx.message=
ctx.length=
ctx.length
ctx.type
ctx.type=
ctx.handerSent
ctx.redirect()
ctx.attachment()
ctx.set()
ctx.append()
ctx.remove()
ctx.lastModified=
ctx.etag=
一個koa請求Request對象是個創建在node請求request之上的抽象。提供了一些額外的功能,這對每一個http服務器開發者來講很是有用。
request.header
Request header 對象
request.headers
Requests header 對象,別名`request.header`。
request.method
request.method
request.method=
設置request method,實現中間件頗有用,例如`methodoverride()`。
request.length
返回request Content-lenght值是數字或者undefined。
request.url
返回rquest URL
request.url=
設置rquest URL,重寫url時有用。
request.originalUrl
返回request 原始 URL
request.orgin
獲得URL的域,包括協議和host(主機號)。
this.request.origin //=>http://example.com
request.href
返回所有request URL,包括協議,主機號,和url。
this.request.href //=>http://example.com/foo/bar?q=1
request.path
返回路徑名(pathname)。
request.path=
設置請求路徑名字,保存查詢參數
rquest.querystring
獲得原始的查詢參數,不包含`?`。
request.querystring=
設置原始的查詢參數。
request.search
獲得原始的查詢字符,帶`?`。
request.search=
設置原始的查詢字符。
rquest.host
獲得主機號(hostname:port)當呈現時。支持`X-Forwarded-Host`當`app.proxy`是true,不然是經常使用的`host`。
request.hostname
當有時返回hostname,支持`X-Frowarded-Host`當`app.proxy`是true,不然是經常使用的。
request.type
返回request的`Content-type`,無效的一些參數,如`charset`。
var ct = this.request.type. //=>'image/png'
request.charset
當有時返回request的charset,或者`undefined`。
this.request.charset //=>'utf-8'
request.query
返回解析過的查詢字符query-string,若是沒有則返回一個空對象。注意,這個getter不支持嵌套的解析nested parsing。例如:`color=blue&size=small`。
{ color:'blue', size:'small' }
request.query=
設置查詢字符query-string到給定的對象。注意給設置setter不支持嵌套對象。
this.query = {next:'/login'};
request.fresh
檢查請求緩存是否「刷新fresh」,或者內容是否發生改變。這個方法是爲了`if-None-Match`和`if-Modified-Since`以及`last-modified`之間的緩存溝通。他必須可以引用到更改以後的返回頭部response headers
//freshness check requeire stats 20x or 304 this.status = 200; this.set('ETag','123'); //cache is ok if(this.fresh) { this.status = 304; return; } //cache is stale //fetch new data shis.body = yield db.find('something');
request.stale
與`request.fresh`相反
request.protocol
返回請求協議,`https`或者`http`。支持`X-Forwarded-Proto`當`app.proxy`是true。
request.secure
`this.protocol == "https"`的速記,用以檢查一個求情可否經過安全傳輸層。
request.ip
請求的遠程地址。支持`X-Forwarded-For`當`app.proxy`爲true。
request.ips
當有`X-Forwarded-For`而且`app.proxy`可用,那麼返回這些的ip的一個數組。 從回溯upstream——>downstream預約,當上述不可用則返回一個空數組。
request.subdomains
返回子域數組。 子域是在主域以前的部分,由點`.`分開。默認狀況下,應用的主域都被假設成倒數的那兩個。能夠經過`app.subdomainOffset`來改變。 例如,若是域是`tobi.ferrest.example.com`,而且`app.subdomainOffset`沒有設置,那麼這個子域是['ferrets','tobi']。若是設置`app.subdomainOffset`爲3,那麼子域是['tobi']。
request.is(type...)
檢查接下來的請求是否包含`Content-Type`頭部內容,它包含任何的mime類型。若是這裏沒有請求體,返回undefined。若是沒有內容類型,或者匹配失敗,返回false。其餘的直接返回內容類型(mime)。
//Contetn-type:text/html;charset=utf-8 this.is('html');//=>'html' this.is('text/html');//=>'text/html' this.is('text/*', 'test/html');//=>'test/html' //when Content-type is application/json this.is('json','urlencoded');//=>'json' this.is('application/json',);//=>'application/json' this.is('html','application/*',);//=>'application/json' this.is('html');//=>false
例子:你只想只有圖片可以發送到路由
if(this.is('image/*')) { //process }else{ this.throw(415,'image only!'); }
內容協商 Content Negotiation
koa請求request包含有用的內容寫上工具,由accepts
和negotaitor
支持實現,這些工具是:
request accepts(types)
rquest acceptsEncoding(types)
rquest acceptsCharsets(charsets)
rquest acceptsLanguages(langs)
若是沒有提供類型,那麼全部可接受的類型將被返回。
若是提供了多個類型,最優匹配獎盃返回。若是沒有匹配到,返回false,而且你應該發送406 "Not Acceptable"
返回response給客戶端。
在能夠接受任何類型的地方丟失了accept頭部。第一個匹配到的將被返回。所以提供科技收的類型是很重要的。
request.accepts(types)
檢查給定的類型是不是可接受的。當爲true則返回最佳匹配,不然false。類型`type`的值也許是一個或者多個mime類型字符,例如'application/json',擴展名是'josn',或者一個數組`['josn','html','text/plain']`。
//Accept:text/html this.accepts('html') //=>'html' //Accept:text/*, application/json this.accepts('html') //=>'html' this.accepts('json', 'text') //=>'json' this.accepts('application/json') //=>'application/json' //Accept.text/*, application/json this.accepts('image/png') this.accepts('png') //=>false //Accept:text/*,q=.5, application/json this.accepts(['html', 'json']) this.accepts('html', 'json') //=>json //No Accepts header this.accpts('html', 'json') //=>html this.accepts('json','html') //=> json
你也許調用this.accepts()
不少次,或者使用switch語句。
switch(this.accepts('json', 'html', 'text')) { case 'json': bareak; case 'html': bareak; case 'text': bareak; default: this.throw(406, 'json , html or text only'); }
request.acceptsEncodings(encodings)
檢查編碼`encodings`是否可接受,true時返回最優匹配,不然返回false。 注意,你應該包含一個`indentity`做爲編碼`encodings`之一。
//Accept-Encoding:gzip this.acceptsEncodings('gzip', 'deflate', 'identify'); //=>gzip this.acceptsEncodings(['gzip', 'deflate', 'identify']) //=>gzip
當沒有參數時,全部可接受的編碼做爲數組元素返回
//Accept-Encoding:gzip, deflate this.acceptsEncodings(); //=>['gzip','deflate','identify']
注意若是用戶明確發送identify爲identify,q=0
。雖然這是個特殊例子,你仍然須要處理這個狀況,當方法返回false時。
request.acceptsCharsets(charsets)
檢查charset是否可接受,爲true時返回最優匹配,不然返回false。
//Accept-Charset:utf-8, iso-8859-1;q=0.2,utf-7;q=0.5
this.acceptsCharsets('utf-8','utf-7')
//=>utf-8
this.acceptsCharsets(['utf-7','utf-8']);
//=>utf-8
若是沒有參數是則返回全部可接受的編碼到一個數組。
//Accept-Charset:utf-8,iso-8859-1;q=0.2,utf-7;q=0.5 this.acceptsCharsets(); //=>['utf-8','utf-7','iso-8859-7']
request.acceptLanguages(langs)
檢查langs是否可接受,若是爲true則返回最有匹配,不然返回false。
//Accept-Language:en;q=0.8,es,pt this.acceptsLanguages('es','en'); //=>'es' this.acceptsLanguages(['en','es']); //=>'es'
當沒有傳入參數則返回全部的語言。
//Accept-Language:en;q=0.8, es,pt this.acceptsLanguages(); //=>['es', 'pt', 'en']
request.idempotent
價差請求是否idempotent(冪等)
request.socket
返回請求的socket
request.get(field)
返回請求頭header
一個koa返回Response對象是個創建在node請求request之上的抽象。提供了一些額外的功能,這對每一個http服務器開發者來講很是有用。
response.header
返回header對象
response。headers
返回header對象。response.header的別名
response.status
返回response的狀態,默認狀況下response.status
沒有默認值,而res.statusCode
的默認值是200。
response.status =
經過數字設置狀態值
100 'continue'繼續
101 'switch protocols'換協議
102 'processing'處理中
200 'ok' ok
201 'created'已建立
202 'accepted' 已接受
203 'non-authoritative information'無做者信息
204 'no content' 無內容
205 'reset content' 重置內容
206 "partial content" 部份內容
207 "multi-status" 多狀態
300 "multiple choices" 多選擇
301 "moved permanently" 移動到永久
302 "moved temporarily" 移動到暫時
303 "see other" 看其餘
304 "not modified" 沒有改動
305 "use proxy" 使用代理
307 "temporary redirect" 暫時改向
400 "bad request" 壞請求
401 "unauthorized" 未經受權
402 "payment required" 要求付款
403 "forbidden" 禁止
404 "not found" 沒有發現
405 "method not allowed" 方法不容許
406 "not acceptable" 不接受
407 "proxy authentication required" 要求代理受權
408 "request time-out" 請求超時
409 "conflict" 衝突
410 "gone" 消失
411 "length required" 要求長度
412 "precondition failed" 預處理失敗
413 "request entity too large" 請求量太大
414 "request-uri too large" 請求贊成資源太大
415 "unsupported media type" 不支持的媒體類型
416 "requested range not satisfiable" 不知足請求範圍
417 "expectation failed" 不是指望值
418 "i'm a teapot" 我是個茶壺???
422 "unprocessable entity" 錯誤實體
423 "locked" 已鎖定
424 "failed dependency" 依賴錯誤
425 "unordered collection" 未預約集合
426 "upgrade required" 要求更新
428 "precondition required" 要求前提
429 "too many requests" 過多請求
431 "request header fields too large" 請求頭的域太大
500 "internal server error" 服務器內部錯誤
501 "not implemented" 沒有實現
502 "bad gateway" 網關錯誤
503 "service unavailable" 不可服務
504 "gateway time-out" 網關超時
505 "http version not supported" http版本不支持
506 "variant also negotiates" 多樣協商
507 "insufficient storage" 存儲不足
509 "bandwidth limit exceeded" 超過帶寬
510 "not extended" 擴展錯誤
511 "network authentication required" 要求網路受權證實
注意:不要擔憂要記太多東西,你能夠隨時查看。
response.message
獲得返回狀態的信息。默認狀況下,response.message
是和response.status
匹配的。
response.message=
設置返回狀態信息。
response.length=
設置內容的長度
response.length
返回內容的長度,或者計算出的this.body
的大小。值爲數字。或者undifined
response.body
獲得response的body。
response.body=
設置返回體(response.body)爲以下之一:
String written
Buffer written
Stream piped
Object json-stringified
null no content response
String
Content-type是text/html或者text/plain,charset是utf-8.Content-length也須要設置。
Buffer
Content-type是application/octet-stream,Content-length也要設置。
Stream
Content-type是application/octet-stream.
Object
Content-type是application/json.
response.get(field)
獲得response頭部的field的值,不區分大小寫。
var etag = this.get('ETag');
response.set(field, value)
設置response頭部field的值。
this.set('Cache-control', 'no-cache');
response.append(field, value)
給頭部添加額爲的域和值。
this.append('Link', '<http://127.0.0.1/>');
response.set(fields)
使用對象設置頭部的fields
this.set({ 'Etag':'1234', 'Last-modified':date });
response.remove(field)
移除頭部的某個域。
resposne.type
返回Content-type的類型,沒有其餘參數——如‘charset’。
var ct = this.type; //=>image/png
response.type
經過名字或者擴展名設置Content-type
this.type = 'text/plain;charset=utf-8'; this.type = 'image/png'; this.type='.png'; this.type='png';
注意,每一個字符編碼charset都是爲你選的最合適的,例如response.type='html'
,那麼默認的字符編碼是utf-8
,然而明肯定義一個完整的類型,如response.type='text/html'
,將不會有指定的字符編碼。
response.is(type...)
很相似於`this.request.is()`。檢查response的類型是不是被支持的類型。這在建立那些對返回進行操做的中間件是很是有用。 示例:這是一個壓縮html返回response的中間件,除了stream不被壓縮。
var minify = require('html-minifier'); app.use(function *minifyHtml(next){ yield next; if(!this.response.is('html')) return; var body = this.body; if(!body||body.pipe) return; if(Buffer.isBuffer(body)) body = body.toString(); this.body = minify(body); })
response.redirect(url, [alt])
把[302]狀態碼重導向至`url`。 字符串`back`是一個特殊的例子,提供了引用這支持,當引用者不存在或者`/`沒有使用。
this.redirect('back'); this.redirect('back','/index.html'); this.redirect('login'); this.redirect('http://google.com');
爲了改變默認的狀態302,只需在這個狀態嗎出現以前或者出現以後進行重導向便可。爲了改變body,在其調用以後進行重定向。
this.status = 301; this.redirect('/cart'); this.body = 'Redirecting to shopping cart';
response.attachment([filename])
設置`Content-disposition`爲"attachment"爲客戶端發出下載的信號。 文件的名字是能夠指定的。
response.headerSent
檢查返回頭response header是否早已發送。查看客戶端是否通知錯誤信號很是有用。
response.lastModified
返回`Last-Modified`最後修改頭部的數據(若是存在)。
response.LastModified=
設置`Last-Modified`頭部爲一個合適的UTC(國際標準時間)字符串。你也能夠設置其爲一個日期或者日期字符串。
this.response.lastModified = new Date();
response.etag=
設置ETag到一個返回中,包括外面的雙引號。注意,這裏沒有相應的response.etag的getter。
this.response.etag = crypto.createHash('md5'),update(this.body).digest('hex');
response.vary(field) 激活field。