Koa2開發詳解(自官網)

Koa

次世代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

Async Function 結合 Babel

想要在較低版本的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

應用 Application

一個Koa應用是一個對象,其包含一個數組,數組有不少函數組成的中間件,這些函數集合起來等待請求,而且執行時是按照類棧的方式。koa和不少其餘中間件系統類似,你也許是用過RubyRack,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)

串聯 Cascading

koa 的串聯中間件使用了一個比較傳統的方式,跟你一般用的工具很像。這是由於原先很難讓用戶有好的使用node的回調函數。然而使用異步函數咱們能偶「真正得」是有中間件。相較於鏈接的實現,這個更容易提供一些列函數/功能來控制,在最後返回即可。koa調用"downstream",控制流返回"upstream".api

下面的例子返回「hello world」,然而最開始請求先經過x-response-timelogging中間件來記錄請求的開始。而後經過返回的中間件產出控制。當一箇中間件執行next()函數,來延遲和傳遞控制給下一個聲明的中間件。而後直到沒有中間件須要執行downstream了,棧將會鬆開而後每一箇中間件復原去展示本身的「upstream」行爲。

設置 Settings

應用設置即在實例app上的屬性。當前支持以下:

  • app.env 默認是NODE_ENV或者「development」。

  • app.proxy 當設置爲true時,porxy頭部將被信任。

  • app.subdomainOffset 設置.subdomains的偏移量。替代[2]。

  • app.listen(...)
    一個Koa應用不是一對一的呈現一個htpp服務器。一個或者多個應用也許被添加到一塊造成大的應用對應一個http服務器。

建立返回一個http服務器,傳遞給定的參數到Server#listen()。這些參數在nodejs.org都有說明。下面是一個無心義的Koa應用,綁定了端口3000

app.listen(...)方法是以下的一個語法糖。

const http = require('http')
const Koa = require('koa')
const app = new Koa()
http.createServer(app.callback()).listen(3000)

這說明你能夠定義同一個應用爲https和http或者多個地址。

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就能使用到那些中間件。

錯誤處理 Error Handling

除非設置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.

環境(Context)

一個Koa環境(實例)封裝了node原生的請求和返回對象到一個單獨的對象中,這個單獨的對象提供了許多使用的方法,可以編寫web應用和API。這些HTTP服務器開發中常常用到的操做被添加到當前等級,而不是高等級。他將強制中間件從新實現這些經常使用的功能。

一個環境Context在每次請求時被建立,而且被引用至中間件做爲接收器,或者定義成this。以下所示。

app.use(function *(){
    this;//這裏是koa環境context
    this.request;//是一個koa請求
    this.response;//是一個koa返回
})

不少context環境訪問器和方法只是ctx.requestkoa請求或者ctx.responsekoa返回的代理,主要是爲了方便。例如ctx.typectx.length表明response返回對象,ctx.pahtctx.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=

請求 Request

一個koa請求Request對象是個創建在node請求request之上的抽象。提供了一些額外的功能,這對每一個http服務器開發者來講很是有用。

API

  • 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包含有用的內容寫上工具,由acceptsnegotaitor支持實現,這些工具是:

    • 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

返回 Response

一個koa返回Response對象是個創建在node請求request之上的抽象。提供了一些額外的功能,這對每一個http服務器開發者來講很是有用。

API

  • 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。

相關文章
相關標籤/搜索