图灵学院JAVA高级架构师【第四期】最新完结无密

biancheng · · 15 次点击 · 开始浏览    置顶
### [图灵学院JAVA高级架构师【第四期】最新完结无密](http://www.51xuebc.com/thread-332-1-1.html) 源码学习思绪 Koa的中心文件一共有四个:application.js、context.js、request.js、response.js。一切的代码加起来不到 2000 行,非常笨重,而且大量代码集中在 request.js 和 response.js 关于恳求头和响应头的处置,真正的中心代码只要几百行。 另外,为了更直观地梳理 koa 的运转原理和逻辑,还是经过调试来走一遍流程,本文将分离调试源码停止剖析。 以下面代码调试为例: const Koa = require('koa') const app = new Koa() const convert = require('koa-convert'); // 日志中间件 app.use(async(ctx, next) => { console.log('middleware before await'); const start = new Date() await next(); console.log('middleware after await'); const ms = new Date() - start console.log(`${ctx.method} ${ctx.url} - ${ms}ms`) }) app.use(async(ctx, next) => { console.log('response'); ctx.body = "response" }) app.listen(3000); 复制代码 node 的调试方式比拟多,可参考 Node.js 调试大法稍做理解。 一、application application.js 是 koa 的入口文件,里面导出了 koa 的结构函数,结构函数中包含了 koa 的主要功用完成。 导出一个结构函数 Application,这个结构函数对外提供功用 API 办法,从主要 API 办法动手剖析功用完成。 1. listen application 结构函数经过 node 中 http 模块,完成了 listen 功用: /** * Shorthand for: * * http.createServer(app.callback()).listen(...) * * @param {Mixed} ... * @return {Server} * @api public */ listen (...args) { debug('listen') const server = http.createServer(this.callback()) // 返回 http.Server 类的新实例,并运用this.callback()回调处置每个单独恳求 return server.listen(...args) // 启动 HTTP 效劳器监听衔接 完成 KOA 效劳器监听衔接 } 复制代码 2. use use 办法将接纳到的中间件函数,全部添加到了 this.middleware ,以便后面按次第调用各个中间件,假如该办法接纳了非函数类型将会报错 'middleware must be a function!'。 /** * Use the given middleware `fn`. * * Old-style middleware will be converted. * * @param {Function} fn * @return {Application} self * @api public */ use(fn) { if (typeof fn !== 'function') throw new TypeError('middleware must be a function!'); // 关于 generator 类型的中间件函数,经过 koa-convert 库将其停止转换,以兼容 koa2 中的koa的递归调用。 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; } 复制代码 3. callback 上面 listen 函数在效劳启动时,createServer 函数会返回 callback 函数的执行结果。 在效劳启动时,callback函数执行将会完成中间件的兼并以及监听框架层的错误恳求等功用。 然后返回了 handleRequest 的办法,它接纳 req 和 res 两个参数,每次效劳端收到恳求时,会依据 node http 原生的 req 和 res,创立一个新的 koa 的上下文 ctx。 /** * Return a request handler callback * for node's native http server. * * @return {Function} * @api public */ callback () { const fn = compose(this.middleware) // 经过 compose 兼并中间件,后面分离koa-compose源码剖析 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 } 复制代码 在 application.js 中,经过 compose 将中间件停止了兼并,是 koa 的一个中心完成。 能够看到 koa-compose 的源码,完成十分简单,只要几十行: /** * @param {Array} middleware 参数为 middleware 中间件函数数组, 数组中是一个个的中间件函数 * @return {Function} * @api public */ function compose (middleware) { // compose函数需求传入一个函数数组队列 [fn,fn,fn...] // 假如传入的不是数组,则抛出错误 if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') for (const fn of middleware) { // 数组队列中有一项不为函数,则抛出错误 if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') } /** * @param {Object} context * @return {Promise} * @api public */ return function (context, next) { // 初始下标为-1 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, dispatch.bind(null, i + 1))); } catch (err) { return Promise.reject(err) } } } } 复制代码 compose 接纳一个中间件函数的数组,返回了一个闭包函数,闭包中维护了一个 index 去记载当前调用的中间件。 里面创立了一个 dispatch 函数,dispatch(i) 会经过 Promise.resolve() 返回 middleware 中的第 i 项函数执行结果,即第 i + 1 个 app.use() 传入的函数。 app.use() 回调的第二个参数是 next,所以当 app.use() 中的代码执行到 next() 时,便会执行 dispatch.bind(null, i + 1)),即执行下一个 app.use() 的回调。 依次类推,便将一个个 app.use() 的回调给串联了起来,直至没有下一个 next,边会按次第返回执行每个 app.use() 的 next() 后面的逻辑。最终经过 Promise.resolve() 返回第一个 app.use() 的执行结果。这里能够分离洋葱模型去了解。 4. createContext 再来看 createContext 函数,一大串的赋值骚操作,我们细细解读一下: 已知从 context.js、request.js、response.js 引入对象context、request和response,并依据这三个对象经过 Object.create() 生成新的context、request和response对象,避免引入的原始对象被污染; 经过 context.request = Object.create(this.request) 和 context.response = Object.create(this.response) 将 request 和 response 对象挂载到了 context 对象上。这局部对应了 context.js 中delegate 的拜托局部(有关 delegate 可见后面 koa 中心库局部的解读),能让 ctx 直接经过 ctx.xxx 去访问到 ctx.request.xxx 和 ctx.response.xxx; 经过一系列的赋值操作,将原始的 http 恳求的 res 和 req,以及 Koa 实例app 等等分别挂载到了 context、request 和 response 对象中,以便于在 context.js、request.js 和response.js 中针对原始的恳求、相应参数等做一些系列的处置访问,便于用户运用。 const response = require('./response') const context = require('./context') const request = require('./request') /** * Initialize a new context. * * @api private */ 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 } 复制代码 5. handleRequest callback 中执行完 createContext 后,会将创立好的 ctx 以及兼并中间件后生成的次第执行函数传给 handleRequest 并执行该函数。 handleRequest 中会经过 onFinished 这个办法监听 res,当 res 完成、关闭或者出错时,便会执行 onerror 回调。 之后返回中间件执行的结果,当中间件全部执行完之后,执行 respond 停止数据返回操作。 /** * Handle request in callback. * * @api private */ 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) } 复制代码 6. toJSON /** * 返回JSON表示 * only() - only办法返回对象的白名单属性 * @return {Object} * @api public */ toJSON () { return only(this, [ 'subdomainOffset', 'proxy', 'env' ]) } /** * Inspect implementation. * * @return {Object} * @api public */ inspect () { return this.toJSON() } 复制代码 7. respond /** * Response helper. */ function respond (ctx) { // allow bypassing koa if (ctx.respond === false) return if (!ctx.writable) return const res = ctx.res let body = ctx.body const code = ctx.status // ignore body if (statuses.empty[code]) { // strip headers ctx.body = null return res.end() } if (ctx.method === 'HEAD') { if (!res.headersSent && !ctx.response.has('Content-Length')) { const { length } = ctx.response if (Number.isInteger(length)) ctx.length = length } return res.end() } // status body if (body == null) { if (ctx.response._explicitNullBody) { ctx.response.remove('Content-Type') ctx.response.remove('Transfer-Encoding') ctx.length = 0 return res.end() } 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) } // responses if (Buffer.isBuffer(body)) return res.end(body) if (typeof body === 'string') 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) } 复制代码 二、context.js 1. cookie context.js 中经过 get 和 set 办法做了 cookie 的设置和读取操作。 // 获取cookie get cookies () { if (!this[COOKIES]) { this[COOKIES] = new Cookies(this.req, this.res, { keys: this.app.keys, secure: this.request.secure }) } return this[COOKIES] }, // 设置cookie set cookies (_cookies) { this[COOKIES] = _cookies } 复制代码 2. delegate context.js 中有大量的 delegate 操作。 经过 delegate,能够让 ctx 可以直接访问其上面 response 和 request 中的属性和办法,即能够经过 ctx.xxx 获取到 ctx.request.xxx 或 ctx.response.xxx 。 delegate 是经过 delegates 这个库完成的,经过 proto.defineGetter 和 proto.defineSetter 去代理对象下面节点的属性和办法等。(proto.defineGetter 和 proto.defineSetter 现已被 mdn 废弃,改用 Object.defineProperty()) const delegate = require('delegates') delegate(proto, 'response') .method('attachment') .method('redirect') .access('lastModified') .access('etag') .getter('headerSent') .getter('writable'); // ... delegate(proto, 'request') .method('acceptsLanguages') .getter('ip'); // ...

关注本站微信公众号(和以上内容无关)Debiancn ,扫码关注:DebianCN

15 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传