前言

  • 找到koa源码的package.json文件,找到”main”: “lib/application.js”,此时我们调用的Koa的类就是在application.js中的
  • application主要做的事情就是实例化应用,
  • context主要做的事情就是实例上下文,
  • request.js由原生request事件的http.IncomingMessage类过滤而来;
  • response.js对应ctx.response,由原生request事件的http.ServerResponse类过滤而来。

里面的Application类实现了koa的各个方法

处理中间件

  • 我们调用中间件的时候传入的callback,callback循环被组合的中间件,递归调用中间件 ```javascript /**

    • Shorthand for: *
    • http.createServer(app.callback()).listen(…) *
    • @param {Mixed} …
    • @return {Server}
    • @api public */

    listen(…args) { debug(‘listen’); const server = http.createServer(this.callback()); return server.listen(…args); }

callback() { const fn = compose(this.middleware);

  1. if (!this.listenerCount('error')) this.on('error', this.onerror);
  2. const handleRequest = (req, res) => {
  3. const ctx = this.createContext(req, res);
  4. return this.handleRequest(ctx, fn);
  5. };
  6. return handleRequest;

}

  1. <a name="f7n8H"></a>
  2. #### context->ctx别名处理
  3. - koa内部使用的是proto.__defineSetter__和proto.__defineGetter__ [__defineGetter__(mdn已废弃)](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/__defineGetter__)
  4. - 也可以使用Object.defineProperty
  5. ```javascript
  6. const context = {
  7. // get method() {
  8. // return this.request.method
  9. // },
  10. // get url(){
  11. // return this.request.url
  12. // }
  13. }
  14. defineProperty('request', 'method')
  15. defineProperty('request', 'url')
  16. defineProperty('request', 'body')
  17. function defineProperty(target, name) {
  18. // context.__defineGetter__(name, function () {
  19. // return this[target][name]
  20. // })
  21. Object.defineProperty(context, name, {
  22. get() {
  23. return this[target][name]
  24. },
  25. set(value) {
  26. this[target][name] = value
  27. }
  28. })
  29. }
  30. module.exports = context

简单的实现

application.js

  • 洋葱结构,当初为什么要这样设计呢?因为是为了解决复杂应用中频繁的回调而设计的级联代码,它并不会把控制权完全交给一个中间件的代码,而是碰到next就会去下一个中间件,等下面所有中间件执行完成后,就会再回来执行中间件未完成的代码。

    为什么要返回promise呢
  • 所以Promise.resolve的作用, 在普通函数中依旧能使用next().then()的形式,保证中间件的正确运行

  • 如果所有的中间件都为普通函数并且没有异步,洋葱模型的实现与调用栈有关,

由于async,await的特性,所有的中间件都使用async/await 也可以保持异步的情况下保持洋葱模型,
此时都可以使用compose-sync 达到与compose-async同样的效果
但当要在普通函数中实现异步保持洋葱模型,则需要compose-async 返回promise, 以达到与async/await 同样的效果。

  1. const http = require('http')
  2. const context = require('./context')
  3. const request = require('./request')
  4. const response = require('./response')
  5. class Application {
  6. constructor() {
  7. // 保存用户添加的中间件函数
  8. this.middleware = []
  9. // 为了方便实用,直接添加到application实例上
  10. // 如果有多个实例共享的时候,数据容易受到污染
  11. this.context = Object.create(context)
  12. this.request = Object.create(request)
  13. this.response = Object.create(response)
  14. }
  15. listen(...args) {
  16. // 把相关逻辑抽离出去
  17. const server = http.createServer(this.callback())
  18. server.listen(...args)
  19. }
  20. // 当调用use的时候,调用的处理函数(中间件),往middleware中push中间件
  21. use(fn) {
  22. this.middleware.push(fn)
  23. }
  24. // 异步递归遍历调用中间件处理函数 next
  25. compose(middleware) {
  26. return function (context) {
  27. const dispatch = index => {
  28. // next就是dispatch,处理index边界问题
  29. // 整个返回的都是promise,所以在超出边界的时候也要返回promise.resolve成功的状态
  30. if (index >= middleware.length) return Promise.resolve()
  31. const fn = middleware[index]
  32. // 把fn强制的包装到一个promise里面
  33. return Promise.resolve(
  34. // 上下文对象
  35. fn(context, () => dispatch(index + 1)) // 这是next函数
  36. )
  37. }
  38. // 返回第一个中间件处理函数
  39. // dispatch不是一数组。是一个函数,调用dispatch
  40. return dispatch(0)
  41. }
  42. }
  43. // 创建上下文对象的函数
  44. // 构造上文对象
  45. createContext(req, res) {
  46. // 一个实例会处理多个请求,而不同的请求应该拥有不同的上下文对象,为了避免请求期间的数据交叉污染
  47. // 所以这里又对数据做了一份新的拷贝
  48. const context = Object.create(this.context);
  49. const request = context.request = Object.create(this.request);
  50. const response = context.response = Object.create(this.response);
  51. context.app = request.app = response.app = this;
  52. context.req = request.req = response.req = req;
  53. context.res = request.res = response.res = res;
  54. request.ctx = response.ctx = context;
  55. request.response = response;
  56. response.request = request;
  57. console.log(req);
  58. context.originalUrl = request.originalUrl = req.url;
  59. context.state = {};
  60. return context;
  61. }
  62. callback() {
  63. // compose进行递归遍历,把当前存储中间件函数的数组存储进来
  64. // 扩展性更好,middleware可以通过参数传进来
  65. const fnMiddleware = this.compose(this.middleware)
  66. const handleRequest = (req, res) => {
  67. // 最终对应的结果
  68. // 每个请求都会创建一个独立的context对象,他们之间不会互相污染
  69. const context = this.createContext()
  70. fnMiddleware(context).then(() => {
  71. // console.log('end');
  72. res.end(context.body)
  73. // res.end('My Koa')
  74. }).catch(err => {
  75. res.end(err.message)
  76. })
  77. }
  78. return handleRequest
  79. }
  80. }
  81. module.exports = Application

context

  1. const context = {
  2. // get method() {
  3. // return this.request.method
  4. // },
  5. // get url(){
  6. // return this.request.url
  7. // }
  8. }
  9. defineProperty('request', 'method')
  10. defineProperty('request', 'url')
  11. defineProperty('request', 'body')
  12. function defineProperty(target, name) {
  13. // context.__defineGetter__(name, function () {
  14. // return this[target][name]
  15. // })
  16. Object.defineProperty(context, name, {
  17. get() {
  18. return this[target][name]
  19. },
  20. set(value) {
  21. this[target][name] = value
  22. }
  23. })
  24. }
  25. module.exports = context

response

  1. const response = {
  2. set status(val) {
  3. this.res.statusCode = val
  4. },
  5. _body: '', // 真正用来存储数据的
  6. get body() {
  7. return this._body
  8. },
  9. set body(value) {
  10. this._body = value
  11. }
  12. }
  13. module.exports = response

request

  1. const url = require('url')
  2. const request = {
  3. // 对象属性访问器
  4. get method() {
  5. console.log(this);
  6. return this.req.method
  7. },
  8. get header() {
  9. return this.req.headers
  10. },
  11. get url() {
  12. return this.req.url
  13. },
  14. get path() {
  15. return url.parse(this.req.url).pathname
  16. },
  17. get query() {
  18. return url.parse(this.req.url, true).query
  19. }
  20. }
  21. module.exports = request

推荐大佬文章

若川大佬详解koa源码