選取的版本爲koa2
koa的源碼由四個文件組成git
application.js koa的骨架 context.js ctx的原型 request.js request的原型 response.js response的原型
const Koa = require('koa'); const app = new Koa(); app.use(async ctx => { ctx.body = 'Hello World'; }); app.listen(3000);
利用http模塊建立服務器github
const app = http.createServer((req, res) => { ... }) app.listen(3000)
事實上koa把這些包在了其listen方法中json
listen(...args) { debug('listen'); const server = http.createServer(this.callback()); return server.listen(...args); }
顯然this.callback()返回的是一個形以下面的函數promise
(req, res) => {}
callback方法以下服務器
callback() { const fn = compose(this.middleware); if (!this.listeners('error').length) this.on('error', this.onerror); const handleRequest = (req, res) => { const ctx = this.createContext(req, res); return this.handleRequest(ctx, fn); }; return handleRequest; }
ctx在koa中事實上是一個包裝了request和response的對象,從createContext中能夠看到起繼承自contextcookie
createContext(req, res) { const context = Object.create(this.context); const request = context.request = Object.create(this.request); const 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.originalUrl = request.originalUrl = req.url; context.cookies = new Cookies(req, res, { keys: this.keys, secure: request.secure }); request.ip = request.ips[0] || req.socket.remoteAddress || ''; context.accept = request.accept = accepts(req); context.state = {}; return context; }
能夠看到ctx.request繼承自request,ctx.response繼承自response,查看response和request能夠看到裏面大都是set和get方法(獲取query,設置header)等等。而且ctx代理了ctx.request和ctx.response的方法,在源碼中能夠看到app
delegate(proto, 'response') .method('attachment') .method('redirect') .method('remove') .method('vary') .method('set') .method('append') .method('flushHeaders') .access('status') .access('message') .access('body') .access('length') .access('type') .access('lastModified') .access('etag') .getter('headerSent') .getter('writable'); /** * Request delegation. */ delegate(proto, 'request') .method('acceptsLanguages') .method('acceptsEncodings') .method('acceptsCharsets') .method('accepts') .method('get') .method('is') .access('querystring') .access('idempotent') .access('socket') .access('search') .access('method') .access('query') .access('path') .access('url') .getter('origin') .getter('href') .getter('subdomains') .getter('protocol') .getter('host') .getter('hostname') .getter('URL') .getter('header') .getter('headers') .getter('secure') .getter('stale') .getter('fresh') .getter('ips') .getter('ip');
因此咱們能夠直接這麼寫dom
ctx.url 等價於 ctx.request.url
咱們再看一下callback函數,觀察發現compose模塊十分的神奇,我暫且把它稱爲是一個迭代器,它實現了中間件的順序執行koa
const fn = compose(this.middleware); 打印fn以下 function (context, next) { // last called middleware # let index = -1 return dispatch(0) function dispatch (i) { if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i let fn = middleware[i] if (i === middleware.length) fn = next if (!fn) return Promise.resolve() try { return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } }
最初接觸koa的時候我疑惑爲何我寫了socket
ctx.body = 'hello world'
並無ctx.response.end()之類的方法,事實上koa已經幫咱們作了處理,在handleRequest方法中
const handleResponse = () => respond(ctx); // fnMiddleware即爲上面compose以後的fn fnMiddleware(ctx).then(handleResponse).catch(onerror)
fnMiddleware返回的是一個promise,在中間件邏輯完成後在respond函數中最終去處理ctx.body
function respond(ctx) { // allow bypassing koa if (false === ctx.respond) return; const res = ctx.res; if (!ctx.writable) return; let body = ctx.body; const code = ctx.status; // ignore body if (statuses.empty[code]) { // strip headers ctx.body = null; return res.end(); } if ('HEAD' == ctx.method) { if (!res.headersSent && isJSON(body)) { ctx.length = Buffer.byteLength(JSON.stringify(body)); } return res.end(); } // status body if (null == body) { body = ctx.message || String(code); if (!res.headersSent) { ctx.type = 'text'; ctx.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); if (!res.headersSent) { ctx.length = Buffer.byteLength(body); } res.end(body); }
對於每一箇中間件可能發生的錯誤,能夠直接在該中間件捕獲
app.use((ctx, next) => { try { ... } catch(err) { ... } })
事實上,咱們只要在第一個中間件添加try... catch... ,整個中間件組的錯誤都是能夠捕獲的到的。
app.on('error', (err) = {})
在上面中間件執行時看到,koa會自動幫咱們捕獲錯誤並處理,以下
try { return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { // 捕獲錯誤 return Promise.reject(err) } // 在ctx.onerror中處理 const onerror = err => ctx.onerror(err); fnMiddleware(ctx).then(handleResponse).catch(onerror)
咱們看ctx.onerror發現它事實上是出發app監聽的error事件
onerror(err) { // delegate this.app.emit('error', err, this);
假如咱們沒有定義error回調怎麼辦呢,koa也爲咱們定義了默認的錯誤處理函數
callback方法作了判斷
callback() { ... if (!this.listeners('error').length) this.on('error', this.onerror); ... }
全文完