零、Koa 源码目录结构

  1. .
  2. ├── History.md
  3. ├── LICENSE
  4. ├── Readme.md
  5. ├── dist
  6. └── koa.mjs
  7. ├── lib
  8. ├── application.js # 最核心的模块
  9. ├── context.js # 上下文对象
  10. ├── request.js # Koa 自己实现的请求对象
  11. └── response.js # Koa 自己实现的响应对象
  12. └── package.json

一、基本结构

使用:

  1. const Koa = require('./koa')
  2. const app = new Koa()
  3. app.listen(3000)

application.js

  1. module.exports = class Application {
  2. listen(...args) {
  3. const server = http.createServer((req, res) => {
  4. res.end('My Koa')
  5. })
  6. return server.listen(...args)
  7. }
  8. }

二、实现中间件功能

  • Koa 会把所有中间件组合成一个大的 Promise
  • 当这个 Promise 执行完毕之后,会采用当前的 ctx.body 进行结果响应
  • next 前面必须有 await 或者 return next,否则执行顺序达不到预期
  • 如果都是同步执行,加不加 await 都无所谓
  • 我不知道后续是否有异步逻辑,所以建议写的时候都加上 await next

收集中间件

使用方式:

  1. const Koa = require('./koa')
  2. const app = new Koa()
  3. app.use((ctx, next) => {
  4. ctx.body = 'foo'
  5. })
  6. app.use((ctx, next) => {
  7. ctx.body = 'Koa'
  8. })
  9. app.listen(3000)

application.js

  1. const http = require('http')
  2. module.exports = class Application {
  3. constructor () {
  4. this.middleware = []
  5. }
  6. use(fn) {
  7. if (typeof fn !== 'function')
  8. throw new TypeError('middleware must be a function!')
  9. this.middleware.push(fn)
  10. }
  11. listen(...args) {
  12. const server = http.createServer((req, res) => {
  13. res.end('My Koa')
  14. })
  15. return server.listen(...args)
  16. }
  17. }

调用中间件

  1. const http = require('http')
  2. module.exports = class Application {
  3. constructor() {
  4. this.middleware = [] // 存储用户所有的中间件处理函数
  5. }
  6. use(fn) {
  7. this.middleware.push(fn)
  8. }
  9. listen(...args) {
  10. const server = http.createServer(this.callback())
  11. return server.listen(...args)
  12. }
  13. compose(middleware) {
  14. return function () { // 可以配置额外参数
  15. const dispatch = i => {
  16. if (i >= middleware.length) return Promise.resolve()
  17. const fn = middleware[i]
  18. return Promise.resolve(
  19. fn({}, () => dispatch(i + 1))
  20. )
  21. }
  22. return dispatch(0)
  23. }
  24. }
  25. callback() {
  26. const fnMiddleware = this.compose(this.middleware)
  27. const handleRequest = (req, res) => {
  28. fnMiddleware()
  29. .then(() => {
  30. // 处理结束
  31. res.end('end')
  32. })
  33. .catch(err => {
  34. // 发生异常
  35. res.end('error')
  36. })
  37. }
  38. return handleRequest
  39. }
  40. }

三、处理上下文对象

初始化上下文对象

context 上下文对象的使用方式:

  1. /**
  2. * Koa Context
  3. */
  4. const Koa = require('./koa')
  5. const app = new Koa()
  6. // Koa Context 将 node 的 request 和 response 对象封装到单个对象中,为编写 Web 应用程序和 API 提供了许多有用的方法。
  7. // 每个请求都将创建一个 Context,并在中间件中作为参数引用
  8. app.use((ctx, next) => {
  9. console.log(ctx) // Context 对象
  10. console.log(ctx.req) // Node 的 request 对象
  11. console.log(ctx.res) // Node 的 response 对象
  12. console.log(ctx.request) // Koa 中封装的请求对象
  13. console.log(ctx.request.header) // 获取请求头对象
  14. console.log(ctx.request.method) // 获取请求方法
  15. console.log(ctx.request.url) // 获取请求路径
  16. console.log(ctx.request.path) // 获取不包含查询字符串的请求路径
  17. console.log(ctx.request.query) // 获取请求路径中的查询字符串
  18. // Request 别名
  19. // 完整列表参见:https://koa.bootcss.com/#request-
  20. console.log(ctx.header)
  21. console.log(ctx.method)
  22. console.log(ctx.url)
  23. console.log(ctx.path)
  24. console.log(ctx.query)
  25. // Koa 中封装的响应对象
  26. console.log(ctx.response)
  27. // Response 别名
  28. ctx.status = 200
  29. ctx.message = 'Success'
  30. ctx.type = 'plain'
  31. ctx.body = 'Hello Koa'
  32. })
  33. app.listen(3000)

context.js

  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. function defineProperty (target, name) {
  12. context.__defineGetter__(name, function () {
  13. return this[target][name]
  14. })
  15. // Object.defineProperty(context, name, {
  16. // get () {
  17. // return this[target][name]
  18. // }
  19. // })
  20. }
  21. module.exports = context

request.js

  1. const url = require('url')
  2. const request = {
  3. get url () {
  4. return this.req.url
  5. },
  6. get path () {
  7. return url.parse(this.req.url).pathname
  8. },
  9. get query () {
  10. return url.parse(this.req.url, true).query
  11. },
  12. get method () {
  13. return this.req.method
  14. }
  15. }
  16. module.exports = request

response.js

  1. const response = {
  2. set status (value) {
  3. this.res.statusCode = value
  4. }
  5. }
  6. module.exports = response
  7. zz

application.js

  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. this.middleware = [] // 保存用户添加的中间件函数
  8. // 为了防止多个实例共享 Request、Response、Context 导致数据被意外修改,这里重新拷贝一份
  9. this.request = Object.create(request)
  10. this.response = Object.create(response)
  11. this.context = Object.create(context)
  12. }
  13. listen(...args) {
  14. const server = http.createServer(this.callback())
  15. server.listen(...args)
  16. }
  17. use(fn) {
  18. this.middleware.push(fn)
  19. }
  20. // 异步递归遍历调用中间件处理函数
  21. compose(middleware) {
  22. return function (context) {
  23. const dispatch = index => {
  24. if (index >= middleware.length) return Promise.resolve()
  25. const fn = middleware[index]
  26. return Promise.resolve(
  27. // TODO: 上下文对象
  28. fn(context, () => dispatch(index + 1)) // 这是 next 函数
  29. )
  30. }
  31. // 返回第 1 个中间件处理函数
  32. return dispatch(0)
  33. }
  34. }
  35. createContext(req, res) {
  36. // 为了避免请求之间 context 数据交叉污染,这里为每个请求单独创建 context 请求对象
  37. const context = Object.create(this.context)
  38. const request = Object.create(this.request)
  39. const response = Object.create(this.response)
  40. context.app = request.app = response.app = this
  41. context.req = request.req = response.req = req // 原生的请求对象
  42. context.res = request.res = response.res = res // 原生的响应对象
  43. request.ctx = response.ctx = context // 在 Request 和 Response 中也可以拿到 context 上下文对象
  44. request.response = response // Request 中也可以拿到 Response
  45. response.request = request // Response 中也可以拿到 Request
  46. context.originalUrl = request.originalUrl = req.url // 没有经过任何处理的请求路径
  47. context.state = {} // 初始化 state 数据对象,用于给模板视图提供数据
  48. return context
  49. }
  50. callback() {
  51. const fnMiddleware = this.compose(this.middleware)
  52. const handleRequest = (req, res) => {
  53. // 1、创建上下文请求对象
  54. const context = this.createContext(req, res)
  55. // 2、传入上下文对象执行中间件栈
  56. fnMiddleware(context)
  57. .then(() => {
  58. console.log('end')
  59. res.end('My Koa')
  60. })
  61. .catch(err => {
  62. res.end(err.message)
  63. })
  64. }
  65. return handleRequest
  66. }
  67. }
  68. module.exports = Application

处理上下文对象中的别名

四、处理 ctx.body

application.js

  1. const http = require('http')
  2. const { Stream } = require('stream')
  3. const context = require('./context')
  4. const request = require('./request')
  5. const { body } = require('./response')
  6. const response = require('./response')
  7. class Application {
  8. constructor() {
  9. this.middleware = [] // 保存用户添加的中间件函数
  10. this.context = Object.create(context)
  11. this.request = Object.create(request)
  12. this.response = Object.create(response)
  13. }
  14. listen(...args) {
  15. const server = http.createServer(this.callback())
  16. server.listen(...args)
  17. }
  18. use(fn) {
  19. this.middleware.push(fn)
  20. }
  21. // 异步递归遍历调用中间件处理函数
  22. compose(middleware) {
  23. return function (context) {
  24. const dispatch = index => {
  25. if (index >= middleware.length) return Promise.resolve()
  26. const fn = middleware[index]
  27. return Promise.resolve(
  28. // TODO: 上下文对象
  29. fn(context, () => dispatch(index + 1)) // 这是 next 函数
  30. )
  31. }
  32. // 返回第 1 个中间件处理函数
  33. return dispatch(0)
  34. }
  35. }
  36. // 构造上下文对象
  37. createContext(req, res) {
  38. // 一个实例会处理多个请求,而不同的请求应该拥有不同的上下文对象,为了避免请求期间的数据交叉污染,所以这里又对这个数据做了一份儿新的拷贝
  39. const context = Object.create(this.context)
  40. const request = (context.request = Object.create(this.request))
  41. const response = (context.response = Object.create(this.response))
  42. context.app = request.app = response.app = this
  43. context.req = request.req = response.req = req
  44. context.res = request.res = response.res = res
  45. request.ctx = response.ctx = context
  46. request.response = response
  47. response.request = request
  48. context.originalUrl = request.originalUrl = req.url
  49. context.state = {}
  50. return context
  51. }
  52. callback() {
  53. const fnMiddleware = this.compose(this.middleware)
  54. const handleRequest = (req, res) => {
  55. // 每个请求都会创建一个独立的 Context 上下文对象,它们之间不会互相污染
  56. const context = this.createContext(req, res)
  57. fnMiddleware(context)
  58. .then(() => {
  59. respond(context)
  60. // res.end(context.body)
  61. // res.end('My Koa')
  62. })
  63. .catch(err => {
  64. res.end(err.message)
  65. })
  66. }
  67. return handleRequest
  68. }
  69. }
  70. function respond (ctx) {
  71. const body = ctx.body
  72. const res = ctx.res
  73. if (body === null) {
  74. res.statusCode = 204
  75. return res.end()
  76. }
  77. if (typeof body === 'string') return res.end(body)
  78. if (Buffer.isBuffer(body)) return res.end(body)
  79. if (body instanceof Stream) return body.pipe(ctx.res)
  80. if (typeof body === 'number') return res.end(body + '')
  81. if (typeof body === 'object') {
  82. const jsonStr = JSON.stringify(body)
  83. return res.end(jsonStr)
  84. }
  85. }
  86. 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('response', '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

reponse.js

  1. const response = {
  2. set status (value) {
  3. this.res.statusCode = value
  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