咱们从以下几个方面来学习Koa
- 创立应用程序函数
- 扩大res和req
- 中间件实现原理
创立应用程序函数
Koa 是依赖 node 原生的 http 模块来实现 http server 的能力,原生 http 模块能够通过几行代码就启动一个监听在 8080 端口的http服务,createServer 的第一个参数是一个回调函数,这个回调函数有两个参数,一个是申请对象,一个是响应对象,能够依据申请对象的内容来决定响应数据的内容;
const http = require("http");const server = http.createServer((req, res) => { // 每一次申请解决的办法 console.log(req.url); res.writeHead(200, { "Content-Type": "text/plain" }); res.end("Hello NodeJS");});server.listen(8080);
在 Koa 中,createServer 回调函数中的 req 和 res 会被保留到 ctx 对象上,随同整个解决申请的生命周期,Koa 源码中的 request.js 和 response.js 就是对这两个对象增加了大量便捷获取数据和设置数据的办法,如获取申请的办法、申请的门路、设置返回数据体、设置返回状态码等操作。
而Koa在封装创立应用程序的办法中次要执行了以下流程:
- 组织中间件(监听申请之前)
- 生成context上下文对象
- 执行中间件
- 执行默认响应办法或者异样解决办法
// application.js// 这个办法是封装了http模块提供的http.createServer和listen办法listen(...args) { debug('listen'); const server = http.createServer(this.callback()); return server.listen(...args);}callback() { //组织中间件,在监听申请之前实现的 const fn = compose(this.middleware); if (!this.listenerCount('error')) this.on('error', this.onerror); const handleRequest = (req, res) => { //创立context上下文对象 const ctx = this.createContext(req, res); return this.handleRequest(ctx, fn); }; return handleRequest;}handleRequest(ctx, fnMiddleware) { const res = ctx.res; // 默认状态码为404 res.statusCode = 404; const onerror = err => ctx.onerror(err); const handleResponse = () => respond(ctx); onFinished(res, onerror); // 执行中间件 return fnMiddleware(ctx).then(handleResponse).catch(onerror);}// 创立context上下文对象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;}
扩大res和req
NodeJS中原生的res和req是http.IncomingMessage和http.ServerResponse的实例,Koa中则是自定义request和response对象,放弃对原生的res和req援用,而后通过getter和setter办法实现扩大。
// application.jscreateContext(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; // 保留原生 req 对象 context.res = request.res = response.res = res; // 保留原生 res 对象 request.ctx = response.ctx = context; request.response = response; // response 拓展 response.request = request; // request 拓展 context.originalUrl = request.originalUrl = req.url; context.state = {}; // 最终返回残缺的context上下文对象 return context;}
在Koa中要区别这两组对象:
- request、response: Koa扩大的对象
- res、req: NodeJS原生对象
// request.jsget header() { return this.req.headers;},set header(val) { this.req.headers = val;}
此时曾经能够采纳这样的形式拜访header属性:
ctx.request.header
delegates 属性代理
koa对response和request应用了属性代理,使咱们能够间接在context中应用request和response的办法,其中method办法是委托办法,getter办法用来委托getter,access办法委托getter+setter
// context.js/** * Response delegation. */delegate(proto, 'response') .method('attachment') .method('redirect') .method('remove') .method('vary') .method('has') .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') .access('accept') .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');
delegates 实现
对于 setter 和 getter办法,是通过调用对象上的 __defineSetter__ 和 __defineGetter__ 来实现的
// delegates/index.js// getterDelegator.prototype.getter = function(name){ var proto = this.proto; var target = this.target; this.getters.push(name); proto.__defineGetter__(name, function(){ return this[target][name]; }); return this;};// setterDelegator.prototype.setter = function(name){ var proto = this.proto; var target = this.target; this.setters.push(name); proto.__defineSetter__(name, function(val){ return this[target][name] = val; }); return this;};// accessDelegator.prototype.access = function(name){ return this.getter(name).setter(name);};// methodDelegator.prototype.method = function(name){ var proto = this.proto; var target = this.target; this.methods.push(name); proto[name] = function(){ return this[target][name].apply(this[target], arguments); }; return this;};
中间件实现原理
在Koa中,通过app.use() 来注册中间件,Koa反对三种不同类型的中间件:一般函数,async 函数,Generator函数,如果是Generator函数,那就用 convert 把函数包起来,而后在push到 this.middleware
use(fn) { if (typeof fn !== 'function') throw new TypeError('middleware must be a function!'); if (isGeneratorFunction(fn)) { fn = convert(fn); } debug('use %s', fn._name || fn.name || '-'); this.middleware.push(fn); return this; }
convert作用
convert是用于将koa中以前基于generator写法的中间件转为基于promise写法。convert()的源码实现逻辑如下:
- convert 办法首先判断传入的中间件是否是一个函数,如果不是就抛出异样;
- 接着判断是否是一个 generator 函数,如果不是就间接返回,不做解决;
利用co将 generator 函数模式的中间件转成 promise 模式的中间件。
function convert (mw) { if (typeof mw !== 'function') { throw new TypeError('middleware must be a function') } // assume it's Promise-based middleware if ( mw.constructor.name !== 'GeneratorFunction' && mw.constructor.name !== 'AsyncGeneratorFunction' ) { return mw } const converted = function (ctx, next) { return co.call( ctx, mw.call( ctx, (function * (next) { return yield next() })(next) )) } converted._name = mw._name || mw.name return converted}
中间件的执行
Koa中间件的执行流程次要通过koa-compose中的compose函数实现,基于洋葱圈模型:
koa-compose 的代码很短,一共才不到50行,次要执行程序如下:// koa-compose function compose (middleware) { //传入middleware数组 // 不是数组抛出异样 if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') // 判断每个middleware中的每一项是否为函数 // 不是函数抛出异样 for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') } // 返回一个函数 return function (context, next) { //index计数 let index = -1 return dispatch(0) //调用dispatch,从第一个中间件开始 function dispatch (i) { // i小于index,证实在中间件内调用了不止一次的next(),抛出谬误 if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i // 更新index的值 let fn = middleware[i] //middleware中的函数,从第i个开始 if (i === middleware.length) fn = next //如果i走到最初一个的前面,就让fn为next,此时fn为undefined if (!fn) return Promise.resolve()// 那么这时候就间接resolve try { // 把下一个中间件作为以后中间件的next传入 return Promise.resolve(fn(context, dispatch.bind(null, i + 1))); } catch (err) { return Promise.reject(err) } } }}