<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                Koa是一個類似于Express的Web開發框架,開發人員也是同一組人,但是使用了Generator函數,進行了架構的重新設計。也就是說,Koa的原理和內部結構很像Express,但是語法和內部結構進行了升級。 官方[faq](https://github.com/koajs/koa/blob/master/docs/faq.md#why-isnt-koa-just-express-40)有這樣一個問題:”為什么koa不是Express 4.0?“,回答是這樣的:”Koa與Express有很大差異,整個設計都是不同的,所以如果將Express 3.0按照這種寫法升級到4.0,就意味著重寫整個程序。所以,我們覺得創造一個新的庫,是更合適的做法。“ [TOC] ## Koa應用 一個Koa應用就是一個對象,包含了一個middleware數組,這個數組由一組Generator函數組成。這些函數負責對HTTP請求進行各種加工,比如生成緩存、指定代理、請求重定向等等。 ~~~ var koa = require('koa'); var app = koa(); app.use(function *(){ this.body = 'Hello World'; }); app.listen(3000); ~~~ 上面代碼中,變量app就是一個Koa應用。它監聽3000端口,返回一個內容為Hello World的網頁。 app.use方法用于向middleware數組添加Generator函數。 listen方法指定監聽端口,并啟動當前應用。它實際上等同于下面的代碼。 ~~~ var http = require('http'); var koa = require('koa'); var app = koa(); http.createServer(app.callback()).listen(3000); ~~~ ## 中間件 Koa的中間件很像Express的中間件,也是對HTTP請求進行處理的函數,但是必須是一個Generator函數。而且,Koa的中間件是一個級聯式(Cascading)的結構,也就是說,屬于是層層調用,第一個中間件調用第二個中間件,第二個調用第三個,以此類推。上游的中間件必須等到下游的中間件返回結果,才會繼續執行,這點很像遞歸。 中間件通過當前應用的use方法注冊。 ~~~ app.use(function* (next){ var start = new Date; // (1) yield next; // (2) var ms = new Date - start; // (3) console.log('%s %s - %s', this.method, this.url, ms); // (4) }); ~~~ 上面代碼中,`app.use`方法的參數就是中間件,它是一個Generator函數,最大的特征就是function命令與參數之間,必須有一個星號。Generator函數的參數next,表示下一個中間件。 Generator函數內部使用yield命令,將程序的執行權轉交給下一個中間件,即`yield next`,要等到下一個中間件返回結果,才會繼續往下執行。上面代碼中,Generator函數體內部,第一行賦值語句首先執行,開始計時,第二行yield語句將執行權交給下一個中間件,當前中間件就暫停執行。等到后面的中間件全部執行完成,執行權就回到原來暫停的地方,繼續往下執行,這時才會執行第三行,計算這個過程一共花了多少時間,第四行將這個時間打印出來。 下面是一個兩個中間件級聯的例子。 ~~~ app.use(function *() { this.body = "header\n"; yield saveResults.call(this); this.body += "footer\n"; }); function *saveResults() { this.body += "Results Saved!\n"; } ~~~ 上面代碼中,第一個中間件調用第二個中間件saveResults,它們都向`this.body`寫入內容。最后,`this.body`的輸出如下。 ~~~ header Results Saved! footer ~~~ 只要有一個中間件缺少`yield next`語句,后面的中間件都不會執行,這一點要引起注意。 ~~~ app.use(function *(next){ console.log('>> one'); yield next; console.log('<< one'); }); app.use(function *(next){ console.log('>> two'); this.body = 'two'; console.log('<< two'); }); app.use(function *(next){ console.log('>> three'); yield next; console.log('<< three'); }); ~~~ 上面代碼中,因為第二個中間件少了`yield next`語句,第三個中間件并不會執行。 如果想跳過一個中間件,可以直接在該中間件的第一行語句寫上`return yield next`。 ~~~ app.use(function* (next) { if (skip) return yield next; }) ~~~ 由于Koa要求中間件唯一的參數就是next,導致如果要傳入其他參數,必須另外寫一個返回Generator函數的函數。 ~~~ function logger(format) { return function *(next){ var str = format .replace(':method', this.method) .replace(':url', this.url); console.log(str); yield next; } } app.use(logger(':method :url')); ~~~ 上面代碼中,真正的中間件是logger函數的返回值,而logger函數是可以接受參數的。 ### 多個中間件的合并 由于中間件的參數統一為next(意為下一個中間件),因此可以使用`.call(this, next)`,將多個中間件進行合并。 ~~~ function *random(next) { if ('/random' == this.path) { this.body = Math.floor(Math.random()*10); } else { yield next; } }; function *backwards(next) { if ('/backwards' == this.path) { this.body = 'sdrawkcab'; } else { yield next; } } function *pi(next) { if ('/pi' == this.path) { this.body = String(Math.PI); } else { yield next; } } function *all(next) { yield random.call(this, backwards.call(this, pi.call(this, next))); } app.use(all); ~~~ 上面代碼中,中間件all內部,就是依次調用random、backwards、pi,后一個中間件就是前一個中間件的參數。 Koa內部使用koa-compose模塊,進行同樣的操作,下面是它的源碼。 ~~~ function compose(middleware){ return function *(next){ if (!next) next = noop(); var i = middleware.length; while (i--) { next = middleware[i].call(this, next); } yield *next; } } function *noop(){} ~~~ 上面代碼中,middleware是中間件數組。前一個中間件的參數是后一個中間件,依次類推。如果最后一個中間件沒有next參數,則傳入一個空函數。 ## 路由 可以通過`this.path`屬性,判斷用戶請求的路徑,從而起到路由作用。 ~~~ app.use(function* (next) { if (this.path === '/') { this.body = 'we are at home!'; } }) // 等同于 app.use(function* (next) { if (this.path !== '/') return yield next; this.body = 'we are at home!'; }) ~~~ 下面是多路徑的例子。 ~~~ let koa = require('koa') let app = koa() // normal route app.use(function* (next) { if (this.path !== '/') { return yield next } this.body = 'hello world' }); // /404 route app.use(function* (next) { if (this.path !== '/404') { return yield next; } this.body = 'page not found' }); // /500 route app.use(function* (next) { if (this.path !== '/500') { return yield next; } this.body = 'internal server error' }); app.listen(8080) ~~~ 上面代碼中,每一個中間件負責一個路徑,如果路徑不符合,就傳遞給下一個中間件。 復雜的路由需要安裝koa-router插件。 ~~~ var app = require('koa')(); var Router = require('koa-router'); var myRouter = new Router(); myRouter.get('/', function *(next) { this.response.body = 'Hello World!'; }); app.use(myRouter.routes()); app.listen(3000); ~~~ 上面代碼對根路徑設置路由。 Koa-router實例提供一系列動詞方法,即一種HTTP動詞對應一種方法。典型的動詞方法有以下五種。 * router.get() * router.post() * router.put() * router.del() * router.patch() 這些動詞方法可以接受兩個參數,第一個是路徑模式,第二個是對應的控制器方法(中間件),定義用戶請求該路徑時服務器行為。 ~~~ router.get('/', function *(next) { this.body = 'Hello World!'; }); ~~~ 上面代碼中,`router.get`方法的第一個參數是根路徑,第二個參數是對應的函數方法。 注意,路徑匹配的時候,不會把查詢字符串考慮在內。比如,`/index?param=xyz`匹配路徑`/index`。 有些路徑模式比較復雜,Koa-router允許為路徑模式起別名。起名時,別名要添加為動詞方法的第一個參數,這時動詞方法變成接受三個參數。 ~~~ router.get('user', '/users/:id', function *(next) { // ... }); ~~~ 上面代碼中,路徑模式`\users\:id`的名字就是`user`。路徑的名稱,可以用來引用對應的具體路徑,比如url方法可以根據路徑名稱,結合給定的參數,生成具體的路徑。 ~~~ router.url('user', 3); // => "/users/3" router.url('user', { id: 3 }); // => "/users/3" ~~~ 上面代碼中,user就是路徑模式的名稱,對應具體路徑`/users/:id`。url方法的第二個參數3,表示給定id的值是3,因此最后生成的路徑是`/users/3`。 Koa-router允許為路徑統一添加前綴。 ~~~ var router = new Router({ prefix: '/users' }); router.get('/', ...); // 等同于"/users" router.get('/:id', ...); // 等同于"/users/:id" ~~~ 路徑的參數通過`this.params`屬性獲取,該屬性返回一個對象,所有路徑參數都是該對象的成員。 ~~~ // 訪問 /programming/how-to-node router.get('/:category/:title', function *(next) { console.log(this.params); // => { category: 'programming', title: 'how-to-node' } }); ~~~ param方法可以針對命名參數,設置驗證條件。 ~~~ router .get('/users/:user', function *(next) { this.body = this.user; }) .param('user', function *(id, next) { var users = [ '0號用戶', '1號用戶', '2號用戶']; this.user = users[id]; if (!this.user) return this.status = 404; yield next; }) ~~~ 上面代碼中,如果`/users/:user`的參數user對應的不是有效用戶(比如訪問`/users/3`),param方法注冊的中間件會查到,就會返回404錯誤。 redirect方法會將某個路徑的請求,重定向到另一個路徑,并返回301狀態碼。 ~~~ router.redirect('/login', 'sign-in'); // 等同于 router.all('/login', function *() { this.redirect('/sign-in'); this.status = 301; }); ~~~ redirect方法的第一個參數是請求來源,第二個參數是目的地,兩者都可以用路徑模式的別名代替。 ## context對象 中間件當中的this表示上下文對象context,代表一次HTTP請求和回應,即一次訪問/回應的所有信息,都可以從上下文對象獲得。context對象封裝了request和response對象,并且提供了一些輔助方法。每次HTTP請求,就會創建一個新的context對象。 ~~~ app.use(function *(){ this; // is the Context this.request; // is a koa Request this.response; // is a koa Response }); ~~~ context對象的很多方法,其實是定義在ctx.request對象或ctx.response對象上面,比如,ctx.type和ctx.length對應于ctx.response.type和ctx.response.length,ctx.path和ctx.method對應于ctx.request.path和ctx.request.method。 context對象的全局屬性。 * request:指向Request對象 * response:指向Response對象 * req:指向Node的request對象 * req:指向Node的response對象 * app:指向App對象 * state:用于在中間件傳遞信息。 ~~~ this.state.user = yield User.find(id); ~~~ 上面代碼中,user屬性存放在`this.state`對象上面,可以被另一個中間件讀取。 context對象的全局方法。 * throw():拋出錯誤,直接決定了HTTP回應的狀態碼。 * assert():如果一個表達式為false,則拋出一個錯誤。 ~~~ this.throw(403); this.throw('name required', 400); this.throw('something exploded'); this.throw(400, 'name required'); // 等同于 var err = new Error('name required'); err.status = 400; throw err; ~~~ assert方法的例子。 ~~~ // 格式 ctx.assert(value, [msg], [status], [properties]) // 例子 this.assert(this.user, 401, 'User not found. Please login!'); ~~~ 以下模塊解析POST請求的數據。 * co-body * [https://github.com/koajs/body-parser](https://github.com/koajs/body-parser) * [https://github.com/koajs/body-parsers](https://github.com/koajs/body-parsers) ~~~ var parse = require('co-body'); // in Koa handler var body = yield parse(this); ~~~ ## 錯誤處理機制 Koa提供內置的錯誤處理機制,任何中間件拋出的錯誤都會被捕捉到,引發向客戶端返回一個500錯誤,而不會導致進程停止,因此也就不需要forever這樣的模塊重啟進程。 ~~~ app.use(function *() { throw new Error(); }); ~~~ 上面代碼中,中間件內部拋出一個錯誤,并不會導致Koa應用掛掉。Koa內置的錯誤處理機制,會捕捉到這個錯誤。 當然,也可以額外部署自己的錯誤處理機制。 ~~~ app.use(function *() { try { yield saveResults(); } catch (err) { this.throw(400, '數據無效'); } }); ~~~ 上面代碼自行部署了try...catch代碼塊,一旦產生錯誤,就用`this.throw`方法拋出。該方法可以將指定的狀態碼和錯誤信息,返回給客戶端。 對于未捕獲錯誤,可以設置error事件的監聽函數。 ~~~ app.on('error', function(err){ log.error('server error', err); }); ~~~ error事件的監聽函數還可以接受上下文對象,作為第二個參數。 ~~~ app.on('error', function(err, ctx){ log.error('server error', err, ctx); }); ~~~ 如果一個錯誤沒有被捕獲,koa會向客戶端返回一個500錯誤“Internal Server Error”。 this.throw方法用于向客戶端拋出一個錯誤。 ~~~ this.throw(403); this.throw('name required', 400); this.throw(400, 'name required'); this.throw('something exploded'); this.throw('name required', 400) // 等同于 var err = new Error('name required'); err.status = 400; throw err; ~~~ `this.throw`方法的兩個參數,一個是錯誤碼,另一個是報錯信息。如果省略狀態碼,默認是500錯誤。 `this.assert`方法用于在中間件之中斷言,用法類似于Node的assert模塊。 ~~~ this.assert(this.user, 401, 'User not found. Please login!'); ~~~ 上面代碼中,如果this.user屬性不存在,會拋出一個401錯誤。 由于中間件是層級式調用,所以可以把`try { yield next }`當成第一個中間件。 ~~~ app.use(function *(next) { try { yield next; } catch (err) { this.status = err.status || 500; this.body = err.message; this.app.emit('error', err, this); } }); app.use(function *(next) { throw new Error('some error'); }) ~~~ ## cookie cookie的讀取和設置。 ~~~ this.cookies.get('view'); this.cookies.set('view', n); ~~~ get和set方法都可以接受第三個參數,表示配置參數。其中的signed參數,用于指定cookie是否加密。如果指定加密的話,必須用`app.keys`指定加密短語。 ~~~ app.keys = ['secret1', 'secret2']; this.cookies.set('name', '張三', { signed: true }); ~~~ this.cookie的配置對象的屬性如下。 * signed:cookie是否加密。 * expires:cookie何時過期 * path:cookie的路徑,默認是“/”。 * domain:cookie的域名。 * secure:cookie是否只有https請求下才發送。 * httpOnly:是否只有服務器可以取到cookie,默認為true。 ## session ~~~ var session = require('koa-session'); var koa = require('koa'); var app = koa(); app.keys = ['some secret hurr']; app.use(session(app)); app.use(function *(){ var n = this.session.views || 0; this.session.views = ++n; this.body = n + ' views'; }) app.listen(3000); console.log('listening on port 3000'); ~~~ ## Request對象 Request對象表示HTTP請求。 (1)this.request.header 返回一個對象,包含所有HTTP請求的頭信息。它也可以寫成`this.request.headers`。 (2)this.request.method 返回HTTP請求的方法,該屬性可讀寫。 (3)this.request.length 返回HTTP請求的Content-Length屬性,取不到值,則返回undefined。 (4)this.request.path 返回HTTP請求的路徑,該屬性可讀寫。 (5)this.request.href 返回HTTP請求的完整路徑,包括協議、端口和url。 ~~~ this.request.href // http://example.com/foo/bar?q=1 ~~~ (6)this.request.querystring 返回HTTP請求的查詢字符串,不含問號。該屬性可讀寫。 (7)this.request.search 返回HTTP請求的查詢字符串,含問號。該屬性可讀寫。 (8)this.request.host 返回HTTP請求的主機(含端口號)。 (9)this.request.hostname 返回HTTP的主機名(不含端口號)。 (10)this.request.type 返回HTTP請求的Content-Type屬性。 ~~~ var ct = this.request.type; // "image/png" ~~~ (11)this.request.charset 返回HTTP請求的字符集。 ~~~ this.request.charset // "utf-8" ~~~ (12)this.request.query 返回一個對象,包含了HTTP請求的查詢字符串。如果沒有查詢字符串,則返回一個空對象。該屬性可讀寫。 比如,查詢字符串`color=blue&size=small`,會得到以下的對象。 ~~~ { color: 'blue', size: 'small' } ~~~ (13)this.request.fresh 返回一個布爾值,表示緩存是否代表了最新內容。通常與If-None-Match、ETag、If-Modified-Since、Last-Modified等緩存頭,配合使用。 ~~~ this.response.set('ETag', '123'); // 檢查客戶端請求的內容是否有變化 if (this.request.fresh) { this.response.status = 304; return; } // 否則就表示客戶端的內容陳舊了, // 需要取出新內容 this.response.body = yield db.find('something'); ~~~ (14)this.request.stale 返回`this.request.fresh`的相反值。 (15)this.request.protocol 返回HTTP請求的協議,https或者http。 (16)this.request.secure 返回一個布爾值,表示當前協議是否為https。 (17)this.request.ip 返回發出HTTP請求的IP地址。 (18)this.request.subdomains 返回一個數組,表示HTTP請求的子域名。該屬性必須與app.subdomainOffset屬性搭配使用。app.subdomainOffset屬性默認為2,則域名“tobi.ferrets.example.com”返回["ferrets", "tobi"],如果app.subdomainOffset設為3,則返回["tobi"]。 (19)this.request.is(types...) 返回指定的類型字符串,表示HTTP請求的Content-Type屬性是否為指定類型。 ~~~ // Content-Type為 text/html; charset=utf-8 this.request.is('html'); // 'html' this.request.is('text/html'); // 'text/html' this.request.is('text/*', 'text/html'); // 'text/html' // Content-Type為s application/json this.request.is('json', 'urlencoded'); // 'json' this.request.is('application/json'); // 'application/json' this.request.is('html', 'application/*'); // 'application/json' ~~~ 如果不滿足條件,返回false;如果HTTP請求不含數據,則返回undefined。 ~~~ this.is('html'); // false ~~~ 它可以用于過濾HTTP請求,比如只允許請求下載圖片。 ~~~ if (this.is('image/*')) { // process } else { this.throw(415, 'images only!'); } ~~~ (20)this.request.accepts(types) 檢查HTTP請求的Accept屬性是否可接受,如果可接受,則返回指定的媒體類型,否則返回false。 ~~~ // Accept: text/html this.request.accepts('html'); // "html" // Accept: text/*, application/json this.request.accepts('html'); // "html" this.request.accepts('text/html'); // "text/html" this.request.accepts('json', 'text'); // => "json" this.request.accepts('application/json'); // => "application/json" // Accept: text/*, application/json this.request.accepts('image/png'); this.request.accepts('png'); // false // Accept: text/*;q=.5, application/json this.request.accepts(['html', 'json']); this.request.accepts('html', 'json'); // "json" // No Accept header this.request.accepts('html', 'json'); // "html" this.request.accepts('json', 'html'); // => "json" ~~~ 如果accepts方法沒有參數,則返回所有支持的類型(text/html,application/xhtml+xml,image/webp,application/xml,_/_)。 如果accepts方法的參數有多個參數,則返回最佳匹配。如果都不匹配則返回false,并向客戶端拋出一個406”Not Acceptable“錯誤。 如果HTTP請求沒有Accept字段,那么accepts方法返回它的第一個參數。 accepts方法可以根據不同Accept字段,向客戶端返回不同的字段。 ~~~ switch (this.request.accepts('json', 'html', 'text')) { case 'json': break; case 'html': break; case 'text': break; default: this.throw(406, 'json, html, or text only'); } ~~~ (21)this.request.acceptsEncodings(encodings) 該方法根據HTTP請求的Accept-Encoding字段,返回最佳匹配,如果沒有合適的匹配,則返回false。 ~~~ // Accept-Encoding: gzip this.request.acceptsEncodings('gzip', 'deflate', 'identity'); // "gzip" this.request.acceptsEncodings(['gzip', 'deflate', 'identity']); // "gzip" ~~~ 注意,acceptEncodings方法的參數必須包括identity(意為不編碼)。 如果HTTP請求沒有Accept-Encoding字段,acceptEncodings方法返回所有可以提供的編碼方法。 ~~~ // Accept-Encoding: gzip, deflate this.request.acceptsEncodings(); // ["gzip", "deflate", "identity"] ~~~ 如果都不匹配,acceptsEncodings方法返回false,并向客戶端拋出一個406“Not Acceptable”錯誤。 (22)this.request.acceptsCharsets(charsets) 該方法根據HTTP請求的Accept-Charset字段,返回最佳匹配,如果沒有合適的匹配,則返回false。 ~~~ // Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5 this.request.acceptsCharsets('utf-8', 'utf-7'); // => "utf-8" this.request.acceptsCharsets(['utf-7', 'utf-8']); // => "utf-8" ~~~ 如果acceptsCharsets方法沒有參數,則返回所有可接受的匹配。 ~~~ // Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5 this.request.acceptsCharsets(); // ["utf-8", "utf-7", "iso-8859-1"] ~~~ 如果都不匹配,acceptsCharsets方法返回false,并向客戶端拋出一個406“Not Acceptable”錯誤。 (23)this.request.acceptsLanguages(langs) 該方法根據HTTP請求的Accept-Language字段,返回最佳匹配,如果沒有合適的匹配,則返回false。 ~~~ // Accept-Language: en;q=0.8, es, pt this.request.acceptsLanguages('es', 'en'); // "es" this.request.acceptsLanguages(['en', 'es']); // "es" ~~~ 如果acceptsCharsets方法沒有參數,則返回所有可接受的匹配。 ~~~ // Accept-Language: en;q=0.8, es, pt this.request.acceptsLanguages(); // ["es", "pt", "en"] ~~~ 如果都不匹配,acceptsLanguages方法返回false,并向客戶端拋出一個406“Not Acceptable”錯誤。 (24)this.request.socket 返回HTTP請求的socket。 (25)this.request.get(field) 返回HTTP請求指定的字段。 ## Response對象 Response對象表示HTTP回應。 (1)this.response.header 返回HTTP回應的頭信息。 (2)this.response.socket 返回HTTP回應的socket。 (3)this.response.status 返回HTTP回應的狀態碼。默認情況下,該屬性沒有值。該屬性可讀寫,設置時等于一個整數。 (4)this.response.message 返回HTTP回應的狀態信息。該屬性與`this.response.message`是配對的。該屬性可讀寫。 (5)this.response.length 返回HTTP回應的Content-Length字段。該屬性可讀寫,如果沒有設置它的值,koa會自動從this.request.body推斷。 (6)this.response.body 返回HTTP回應的信息體。該屬性可讀寫,它的值可能有以下幾種類型。 * 字符串:Content-Type字段默認為text/html或text/plain,字符集默認為utf-8,Content-Length字段同時設定。 * 二進制Buffer:Content-Type字段默認為application/octet-stream,Content-Length字段同時設定。 * Stream:Content-Type字段默認為application/octet-stream。 * JSON對象:Content-Type字段默認為application/json。 * null(表示沒有信息體) 如果`this.response.status`沒設置,Koa會自動將其設為200或204。 (7)this.response.get(field) 返回HTTP回應的指定字段。 ~~~ var etag = this.get('ETag'); ~~~ 注意,get方法的參數是區分大小寫的。 (8)this.response.set() 設置HTTP回應的指定字段。 ~~~ this.set('Cache-Control', 'no-cache'); ~~~ set方法也可以接受一個對象作為參數,同時為多個字段指定值。 ~~~ this.set({ 'Etag': '1234', 'Last-Modified': date }); ~~~ (9)this.response.remove(field) 移除HTTP回應的指定字段。 (10)this.response.type 返回HTTP回應的Content-Type字段,不包括“charset”參數的部分。 ~~~ var ct = this.reponse.type; // "image/png" ~~~ 該屬性是可寫的。 ~~~ this.reponse.type = 'text/plain; charset=utf-8'; this.reponse.type = 'image/png'; this.reponse.type = '.png'; this.reponse.type = 'png'; ~~~ 設置type屬性的時候,如果沒有提供charset參數,Koa會判斷是否自動設置。如果`this.response.type`設為html,charset默認設為utf-8;但如果`this.response.type`設為text/html,就不會提供charset的默認值。 (10)this.response.is(types...) 該方法類似于`this.request.is()`,用于檢查HTTP回應的類型是否為支持的類型。 它可以在中間件中起到處理不同格式內容的作用。 ~~~ var minify = require('html-minifier'); app.use(function *minifyHTML(next){ yield next; if (!this.response.is('html')) return; var body = this.response.body; if (!body || body.pipe) return; if (Buffer.isBuffer(body)) body = body.toString(); this.response.body = minify(body); }); ~~~ 上面代碼是一個中間件,如果輸出的內容類型為HTML,就會進行最小化處理。 (11)this.response.redirect(url, [alt]) 該方法執行302跳轉到指定網址。 ~~~ this.redirect('back'); this.redirect('back', '/index.html'); this.redirect('/login'); this.redirect('http://google.com'); ~~~ 如果redirect方法的第一個參數是back,將重定向到HTTP請求的Referrer字段指定的網址,如果沒有該字段,則重定向到第二個參數或“/”網址。 如果想修改302狀態碼,或者修改body文字,可以采用下面的寫法。 ~~~ this.status = 301; this.redirect('/cart'); this.body = 'Redirecting to shopping cart'; ~~~ (12)this.response.attachment([filename]) 該方法將HTTP回應的Content-Disposition字段,設為“attachment”,提示瀏覽器下載指定文件。 (13)this.response.headerSent 該方法返回一個布爾值,檢查是否HTTP回應已經發出。 (14)this.response.lastModified 該屬性以Date對象的形式,返回HTTP回應的Last-Modified字段(如果該字段存在)。該屬性可寫。 ~~~ this.response.lastModified = new Date(); ~~~ (15)this.response.etag 該屬性設置HTTP回應的ETag字段。 ~~~ this.response.etag = crypto.createHash('md5').update(this.body).digest('hex'); ~~~ 注意,不能用該屬性讀取ETag字段。 (16)this.response.vary(field) 該方法將參數添加到HTTP回應的Vary字段。 ## CSRF攻擊 CSRF攻擊是指用戶的session被劫持,用來冒充用戶的攻擊。 koa-csrf插件用來防止CSRF攻擊。原理是在session之中寫入一個秘密的token,用戶每次使用POST方法提交數據的時候,必須含有這個token,否則就會拋出錯誤。 ~~~ var koa = require('koa'); var session = require('koa-session'); var csrf = require('koa-csrf'); var route = require('koa-route'); var app = module.exports = koa(); app.keys = ['session key', 'csrf example']; app.use(session(app)); app.use(csrf()); app.use(route.get('/token', token)); app.use(route.post('/post', post)); function* token () { this.body = this.csrf; } function* post() { this.body = {ok: true}; } app.listen(3000); ~~~ POST請求含有token,可以是以下幾種方式之一,koa-csrf插件就能獲得token。 * 表單的_csrf字段 * 查詢字符串的_csrf字段 * HTTP請求頭信息的x-csrf-token字段 * HTTP請求頭信息的x-xsrf-token字段 ## 數據壓縮 koa-compress模塊可以實現數據壓縮。 ~~~ app.use(require('koa-compress')()) app.use(function* () { this.type = 'text/plain' this.body = fs.createReadStream('filename.txt') }) ~~~ ## 源碼解讀 每一個網站就是一個app,它由`lib/application`定義。 ~~~ function Application() { if (!(this instanceof Application)) return new Application; this.env = process.env.NODE_ENV || 'development'; this.subdomainOffset = 2; this.middleware = []; this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); } var app = Application.prototype; exports = module.exports = Application; ~~~ `app.use()`用于注冊中間件,即將Generator函數放入中間件數組。 ~~~ app.use = function(fn){ if (!this.experimental) { // es7 async functions are allowed assert(fn && 'GeneratorFunction' == fn.constructor.name, 'app.use() requires a generator function'); } debug('use %s', fn._name || fn.name || '-'); this.middleware.push(fn); return this; }; ~~~ `app.listen()`就是`http.createServer(app.callback()).listen(...)`的縮寫。 ~~~ app.listen = function(){ debug('listen'); var server = http.createServer(this.callback()); return server.listen.apply(server, arguments); }; app.callback = function(){ var mw = [respond].concat(this.middleware); var fn = this.experimental ? compose_es7(mw) : co.wrap(compose(mw)); var self = this; if (!this.listeners('error').length) this.on('error', this.onerror); return function(req, res){ res.statusCode = 404; var ctx = self.createContext(req, res); onFinished(res, ctx.onerror); fn.call(ctx).catch(ctx.onerror); } }; ~~~ 上面代碼中,`app.callback()`會返回一個函數,用來處理HTTP請求。它的第一行`mw = [respond].concat(this.middleware)`,表示將respond函數(這也是一個Generator函數)放入`this.middleware`,現在mw就變成了`[respond, S1, S2, S3]`。 `compose(mw)`將中間件數組轉為一個層層調用的Generator函數。 ~~~ function compose(middleware){ return function *(next){ if (!next) next = noop(); var i = middleware.length; while (i--) { next = middleware[i].call(this, next); } yield *next; } } function *noop(){} ~~~ 上面代碼中,下一個generator函數總是上一個Generator函數的參數,從而保證了層層調用。 `var fn = co.wrap(gen)`則是將Generator函數包裝成一個自動執行的函數,并且返回一個Promise。 ~~~ //co package co.wrap = function (fn) { return function () { return co.call(this, fn.apply(this, arguments)); }; }; ~~~ 由于`co.wrap(compose(mw))`執行后,返回的是一個Promise,所以可以對其使用catch方法指定捕捉錯誤的回調函數`fn.call(ctx).catch(ctx.onerror)`。 將所有的上下文變量都放進context對象。 ~~~ app.createContext = function(req, res){ var context = Object.create(this.context); var request = context.request = Object.create(this.request); var response = context.response = Object.create(this.response); context.app = request.app = response.app = this; context.req = request.req = response.req = req; context.res = request.res = response.res = res; request.ctx = response.ctx = context; request.response = response; response.request = request; context.onerror = context.onerror.bind(context); context.originalUrl = request.originalUrl = req.url; context.cookies = new Cookies(req, res, this.keys); context.accept = request.accept = accepts(req); context.state = {}; return context; }; ~~~ 真正處理HTTP請求的是下面這個Generator函數。 ~~~ function *respond(next) { yield *next; // allow bypassing koa if (false === this.respond) return; var res = this.res; if (res.headersSent || !this.writable) return; var body = this.body; var code = this.status; // ignore body if (statuses.empty[code]) { // strip headers this.body = null; return res.end(); } if ('HEAD' == this.method) { if (isJSON(body)) this.length = Buffer.byteLength(JSON.stringify(body)); return res.end(); } // status body if (null == body) { this.type = 'text'; body = this.message || String(code); this.length = Buffer.byteLength(body); return res.end(body); } // responses if (Buffer.isBuffer(body)) return res.end(body); if ('string' == typeof body) return res.end(body); if (body instanceof Stream) return body.pipe(res); // body: json body = JSON.stringify(body); this.length = Buffer.byteLength(body); res.end(body); } ~~~ ## 參考鏈接 * [Koa Guide](https://github.com/koajs/koa/blob/master/docs/guide.md) * William XING,?[Is Koa.js right for me?](http://william.xingyp.com/is-koa-js-right-for-me/)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看