阅读Koa.js源码之application application.js是整个koajs的入口文件,对外暴露的是一个Application
类,下面以一段代码作为演示分析koa执行过程。
1 2 3 4 5 6 7 8 9 10 const Koa = require ('koa' );const app = new Koa ();app.use (async ctx => { ctx.body = 'Hello World' ; }); app.listen (3000 );
use实现
1 2 3 4 5 6 7 8 9 10 11 12 use (fn ) { if (typeof fn !== 'function' ) throw new TypeError ('middleware must be a function!' ); if (isGeneratorFunction (fn)) { deprecate ('Support for generators will be removed in v3. ' + 'See the documentation for examples of how to convert old middleware ' + 'https://github.com/koajs/koa/blob/master/docs/migration.md' ); fn = convert (fn); } debug ('use %s' , fn._name || fn.name || '-' ); this .middleware .push (fn); return this ; }
use方法的参数一定要是个函数,并且如果为generator函数,此处会利用convert函数做转换,但是koa 3就不再支持generator函数了。此外application实例拥有一个middleware数组,每当我们调用app.use方法,就会将当前中间件函数push进这个application实例的middleware数组,最后我们返回this实例,因此我们可以链式在application实例上调用方法。
经过分析知,这一步我们只是在application的实例的middleware数组属性上增加了一个item。
listen实现 1 2 3 4 5 listen (...args ) { debug ('listen' ); const server = http.createServer (this .callback ()); return server.listen (...args); }
listen方法接受任意数量参数,利用了ES6的rest parameters特性,在函数体内作为数组体现。可以看到,listen方法不过是http.createServer(app.callback()).listen(…args)的语法糖。我们创建了一个server实例,并传递this.callback()作为请求监听器,然后通过调用server实力的listen方法,返回了一个server实例。
TODO: compose源码阅读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 callback ( ) { const fn = compose (this .middleware ); if (!this .listenerCount ('error' )) this .on ('error' , this .onerror ); const handleRequest = (req, res ) => { const ctx = this .createContext (req, res); return this .handleRequest (ctx, fn); }; return handleRequest; }
listen
方法调用了application实例的callback方法。与callback方法相关的有三个函数,分别是compose(从koa-compose包引入),createContext和handleRequest函数。compose函数接受一个中间件数组并返回一个函数,返回的这个函数接受一个context对象和一个next参数并返回一个promise。此时我们定义了一个handleRequest函数,这个函数就是作为createServer的请求监听器参数传入的 。而这个handleRequest通过调用createContext创建了一个ctx对象 ,并将其和通过compose生成的fn传入了application实例的handleRequest方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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.state = {}; return context; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 handleRequest (ctx, fnMiddleware ) { const res = ctx.res ; res.statusCode = 404 ; const onerror = err => ctx.onerror (err); const handleResponse = ( ) => respond (ctx); onFinished (res, onerror); return fnMiddleware (ctx).then (handleResponse).catch (onerror); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 function respond (ctx ) { if (false === ctx.respond ) return ; if (!ctx.writable ) return ; const res = ctx.res ; let body = ctx.body ; const code = ctx.status ; if (statuses.empty [code]) { 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 (); } if (null == body) { if (ctx.req .httpVersionMajor >= 2 ) { body = String (code); } else { body = ctx.message || String (code); } if (!res.headersSent ) { ctx.type = 'text' ; ctx.length = Buffer .byteLength (body); } return res.end (body); } 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 .stringify (body); if (!res.headersSent ) { ctx.length = Buffer .byteLength (body); } res.end (body); }