Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

koa灵魂拷问之koa源码 #1

Open
Stream-web opened this issue Jan 11, 2023 · 0 comments
Open

koa灵魂拷问之koa源码 #1

Stream-web opened this issue Jan 11, 2023 · 0 comments

Comments

@Stream-web
Copy link
Owner

前言

随着前端的生态逐渐变好,前端一定程度上也进入了内卷时代。从目前来看,node.js逐渐成为前端必会的技术栈之一,他可以让我们快速的搭建一个后端,从而让我们不再依赖后端。
而在node.js里面的一个主流框架就是koa框架, koa干的核心的话其实就是将api优雅化以及aop模式;所谓aop就是面向切面编程,koa采用所谓的洋葱模型,使用者可以通过级联的方式顺序调用中间件。

绪论

接下来我们来看koa的源码,首先从github上面克隆一份koa源码,具体目录如下。其中核心文件在lib下面,其中包括application.js、context.js、request.js、response.js。koa源码给我的第一感觉就是难度和vuex源码的难度差不多,相比较vue-router,koa源码的难度要低一些。它们实现的功能分别如下;
application主要做的事情就是实例化应用,
context主要做的事情就是实例上下文,
request.js由原生request事件的http.IncomingMessage类过滤而来;
response.js对应ctx.response,由原生request事件的http.ServerResponse类过滤而来。
image

1、Application分析

1.1、创建服务

如下代码所示,node里面创建服务的api不多,koa用的是http模块来创建服务。其中this.callback作为参数
` listen (...args) {
debug('listen')
// 用http创建服务
const server = http.createServer(this.callback())
return server.listen(...args)

查看callback代码可以看到返回的是handleRequest,这里面是闭包的应用。 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

}

`

1.2中间件实现原理

继续往下面翻application.js源码,会看到use方法,koa注册中间件的时候,会不断向middleware里面进行push。
` use (fn) {
// 入参必须是函数
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!')
debug('use %s', fn._name || fn.name || '-')
// koa注册中间件的时候,会不断的向里面push;
this.middleware.push(fn)
return this
}

我们知道,在koa的中间件执行的过程中,await next之前的部分是按顺序执行的,await next之后的部分是按照逆序执行的。这块我们可以通过demo测试可以直观看到。由于有next的分割,一个中间件会分为两部分执行。那么middleware怎么执行的呢,我们还要到callback里面查看源码。在callback里面我们可以看到下面这行代码,其中compose是引入的一个库。我们需要去查看compose的实现。onst fn = compose(this.middleware)
这里我直接从官网复制到我的本地了,也可以去官网上查看。代码以及代码的分析如下。在这里面我们再次可以看到闭包的存在,到这里,顺便说一下,面试的时候如果面试官问我们闭包的应用的时候,我们就可以扩展到koa源码里面的闭包。function compose (middleware) {
// 入参必须是一个数组
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) {
  // last called middleware #
  // 初始化数组执行的下标
  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 {
        // 用递归方法执行await next()后面的逻辑;
      return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
    } catch (err) {
      return Promise.reject(err)
    }
  }
}

}

`
整体来看,compose干的事情就是处理middleware数组,而且context贯穿于所有的中间件,接下来我们细说context。

1.3 koa里面如何封装ctx

我们在使用中间件的时候有两个参数,一个是next,一个是ctx,next就是把当前的中间件的执行权力交给了下一个中间件,那么ctx到底是什么东西呢?contex对象其实就是中间件中的ctx对象。koa为了能够简化API,引⼊上下⽂context概念,将原始请求对象req和响应对象res封装并挂载到 context上,并且在context上设置getter和setter,从⽽简化操作。
在构造器里面可以看到如下三行代码 ,通过Object.create方法分别继承context、request、response。
` this.context = Object.create(context)
this.request = Object.create(request)
this.response = Object.create(response)

我们在使用中间件的时候有两个参数,一个是next,一个是ctx,next就是把当前的中间件的执行权力交给了下一个中间件,那么ctx到底是什么东西呢?contex对象其实就是中间件中的ctx对象。koa为了能够简化API,引⼊上下⽂context概念,将原始请求对象req和响应对象res封装并挂载到 context上,并且在context上设置getter和setter,从⽽简化操作。 在构造器里面可以看到如下三行代码 ,通过Object.create方法分别继承context、request、response。 this.context = Object.create(context)
this.request = Object.create(request)
this.response = Object.create(response)

`
继续翻callback代码,可以看到在handleRequest里面创建了ctx对象,通过createContext来创建的。

` 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

}

那么这个时候我们就要翻createContext函数的代码 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
}

`
在这里可以看到,ctx是通过Object.create()方法继承了this.context,而context又来源于lib下面的context;与此同时,又挂载了request里面的req和response里面的res到context上面,后面会单独将request和response。

Ps:这里创建context符合单一上下文原则,这使得信息是高内聚的,因此改动的风险很小。

1.4 handleRequest实现

在callback里面我们可以看到this.handleRequest这个方法,根据this执行判定,this执行全局window,接下来继续翻handleRequest源码,源码以及分析如下,到这步,application.js源码我们只剩下reponse和onerror两个方法没有翻了。
`return this.handleRequest(ctx, fn)
handleRequest (ctx, fnMiddleware) {

const res = ctx.res
res.statusCode = 404
// 错误处理
const onerror = err => ctx.onerror(err)
// 处理返回结果
const handleResponse = () => respond(ctx)
// 添加错误相应处理
onFinished(res, onerror)
// 执行中间件的所有函数,在结束时候调用response函数
return fnMiddleware(ctx).then(handleResponse).catch(onerror)

}

`

1.5 respond做了什么事

respond源码以及分析如下,respond源码以及分析如下。
`function respond (ctx) {
// allow bypassing koa
// 允许跳出koa
if (ctx.respond === false) return
// 检查是否为原生的可写入流
if (!ctx.writable) return

const res = ctx.res
let body = ctx.body
const code = ctx.status

// ignore body
// 如果相应的statuses是body为空的类型,这个时候直接将body设置为空
if (statuses.empty[code]) {
// strip headers
// 带响应头
ctx.body = null
return res.end()
}

if (ctx.method === 'HEAD') {
// headersSent属性是Node原生的response对象上的,用于检查HTTP响应头是否被发送
// 如果头未被发送,并且响应头没有Content-Length属性,那么添加length头
if (!res.headersSent && !ctx.response.has('Content-Length')) {
const { length } = ctx.response
if (Number.isInteger(length)) ctx.length = length
}
return res.end()
}

// 如果body为null
if (body == null) {
if (ctx.response._explicitNullBody) {
// 移出Content-Type 和 Transfer-Encoding 并返回结果
ctx.response.remove('Content-Type')
ctx.response.remove('Transfer-Encoding')
ctx.length = 0
return res.end()
}
// Http为2+的版本的时候,设置body为对应的http状态码
if (ctx.req.httpVersionMajor >= 2) {
body = String(code)
} else {
body = ctx.message || String(code)
}
// 如果headersSent不为真,直接返回ctx.type为text,txt.length为Buffer.byteLength(body)
if (!res.headersSent) {
ctx.type = 'text'
ctx.length = Buffer.byteLength(body)
}
return res.end(body)
}

// responses
// body为Buffer或者string类型的时候
if (Buffer.isBuffer(body)) return res.end(body)
if (typeof body === 'string') return res.end(body)
// body为Stream时,开启管道body.pipe
if (body instanceof Stream) return body.pipe(res)
// body为json的时候,转化为字符串,并设置ctx.length后返回结果。
// body: json
body = JSON.stringify(body)
if (!res.headersSent) {
ctx.length = Buffer.byteLength(body)
}
res.end(body)
}

`

1.6 异常处理

到此为止,application.js里面的逻辑我们就剩下异常处理没有看了,接下来我们看异常处理。
可以看到在koa里面处理错误很简单,在真实业务里面,我们可以做一些额外的操作。
onerror (err) { // When dealing with cross-globals a normalinstanceof` check doesn't work properly.
// See koajs/koa#1466
// We can probably remove it once jest fixes jestjs/jest#2549.
const isNativeError =
Object.prototype.toString.call(err) === '[object Error]' ||
err instanceof Error
if (!isNativeError) throw new TypeError(util.format('non-error thrown: %j', err))

if (err.status === 404 || err.expose) return
if (this.silent) return

const msg = err.stack || err.toString()
console.error(`\n${msg.replace(/^/gm, '  ')}\n`)

}

继续返回到callback里面,寻找异常处理的代码 if (!this.listenerCount('error')) this.on('error', this.onerror)

`
由此发现,在执行回调函数中,如果application中监听error事件个数大于0,则用我们自己的异常监听,否则用koa的异常监听。
到此为止,application.js已经完整分析了一遍;

2、Context的实现

翻源码发现像inspect、toJSON等方法在application.js里面都有所提到;Context两大核心功能就是委托机制以及Cookie的操作;

2.1委托机制

我们首先来看委托机制。在引入库这一块,我们可以看到如下代码,委托机制其实将其他属性挂载在自身。这里顺便提一下,我在之前面试的时候被问到过Symbol的应用,下次面试官如果再次问我这个问题,那么我就可以扩展到koa源码中。
`const delegate = require('delegates')
const COOKIES = Symbol('context#cookies')

`
前言
随着前端的生态逐渐变好,前端一定程度上也进入了内卷时代。从目前来看,node.js逐渐成为前端必会的技术栈之一,他可以让我们快速的搭建一个后端,从而让我们不再依赖后端。
而在node.js里面的一个主流框架就是koa框架, koa干的核心的话其实就是将api优雅化以及aop模式;所谓aop就是面向切面编程,koa采用所谓的洋葱模型,使用者可以通过级联的方式顺序调用中间件。

绪论
接下来我们来看koa的源码,首先从github上面克隆一份koa源码,具体目录如下。其中核心文件在lib下面,其中包括application.js、context.js、request.js、response.js。koa源码给我的第一感觉就是难度和vuex源码的难度差不多,相比较vue-router,koa源码的难度要低一些。它们实现的功能分别如下;
application主要做的事情就是实例化应用,
context主要做的事情就是实例上下文,
request.js由原生request事件的http.IncomingMessage类过滤而来;
response.js对应ctx.response,由原生request事件的http.ServerResponse类过滤而来。

1、Application分析
1.1、创建服务
如下代码所示,node里面创建服务的api不多,koa用的是http模块来创建服务。其中this.callback作为参数

listen (...args) {
debug('listen')
// 用http创建服务
const server = http.createServer(this.callback())
return server.listen(...args)
查看callback代码可以看到返回的是handleRequest,这里面是闭包的应用。

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

}

1.2中间件实现原理
继续往下面翻application.js源码,会看到use方法,koa注册中间件的时候,会不断向middleware里面进行push。

use (fn) {
// 入参必须是函数
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!')
debug('use %s', fn._name || fn.name || '-')
// koa注册中间件的时候,会不断的向里面push;
this.middleware.push(fn)
return this
}
我们知道,在koa的中间件执行的过程中,await next之前的部分是按顺序执行的,await next之后的部分是按照逆序执行的。这块我们可以通过demo测试可以直观看到。由于有next的分割,一个中间件会分为两部分执行。那么middleware怎么执行的呢,我们还要到callback里面查看源码。在callback里面我们可以看到下面这行代码,其中compose是引入的一个库。我们需要去查看compose的实现。

onst fn = compose(this.middleware)

这里我直接从官网复制到我的本地了,也可以去官网上查看。代码以及代码的分析如下。在这里面我们再次可以看到闭包的存在,到这里,顺便说一下,面试的时候如果面试官问我们闭包的应用的时候,我们就可以扩展到koa源码里面的闭包。

function compose (middleware) {
// 入参必须是一个数组
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) {
  // last called middleware #
  // 初始化数组执行的下标
  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 {
        // 用递归方法执行await next()后面的逻辑;
      return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
    } catch (err) {
      return Promise.reject(err)
    }
  }
}

}
整体来看,compose干的事情就是处理middleware数组,而且context贯穿于所有的中间件,接下来我们细说context。

1.3 koa里面如何封装ctx
我们在使用中间件的时候有两个参数,一个是next,一个是ctx,next就是把当前的中间件的执行权力交给了下一个中间件,那么ctx到底是什么东西呢?contex对象其实就是中间件中的ctx对象。koa为了能够简化API,引⼊上下⽂context概念,将原始请求对象req和响应对象res封装并挂载到 context上,并且在context上设置getter和setter,从⽽简化操作。
在构造器里面可以看到如下三行代码 ,通过Object.create方法分别继承context、request、response。

this.context = Object.create(context)
this.request = Object.create(request)
this.response = Object.create(response)

继续翻callback代码,可以看到在handleRequest里面创建了ctx对象,通过createContext来创建的。

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

}

那么这个时候我们就要翻createContext函数的代码

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
}

在这里可以看到,ctx是通过Object.create()方法继承了this.context,而context又来源于lib下面的context;与此同时,又挂载了request里面的req和response里面的res到context上面,后面会单独将request和response。

Ps:这里创建context符合单一上下文原则,这使得信息是高内聚的,因此改动的风险很小。

1.4 handleRequest实现
在callback里面我们可以看到this.handleRequest这个方法,根据this执行判定,this执行全局window,接下来继续翻handleRequest源码,源码以及分析如下,到这步,application.js源码我们只剩下reponse和onerror两个方法没有翻了。

return this.handleRequest(ctx, fn)
handleRequest (ctx, fnMiddleware) {

const res = ctx.res
res.statusCode = 404
// 错误处理
const onerror = err => ctx.onerror(err)
// 处理返回结果
const handleResponse = () => respond(ctx)
// 添加错误相应处理
onFinished(res, onerror)
// 执行中间件的所有函数,在结束时候调用response函数
return fnMiddleware(ctx).then(handleResponse).catch(onerror)

}
1.5 respond做了什么事
respond源码以及分析如下,respond源码以及分析如下。

function respond (ctx) {
// allow bypassing koa
// 允许跳出koa
if (ctx.respond === false) return
// 检查是否为原生的可写入流
if (!ctx.writable) return

const res = ctx.res
let body = ctx.body
const code = ctx.status

// ignore body
// 如果相应的statuses是body为空的类型,这个时候直接将body设置为空
if (statuses.empty[code]) {
// strip headers
// 带响应头
ctx.body = null
return res.end()
}

if (ctx.method === 'HEAD') {
// headersSent属性是Node原生的response对象上的,用于检查HTTP响应头是否被发送
// 如果头未被发送,并且响应头没有Content-Length属性,那么添加length头
if (!res.headersSent && !ctx.response.has('Content-Length')) {
const { length } = ctx.response
if (Number.isInteger(length)) ctx.length = length
}
return res.end()
}

// 如果body为null
if (body == null) {
if (ctx.response._explicitNullBody) {
// 移出Content-Type 和 Transfer-Encoding 并返回结果
ctx.response.remove('Content-Type')
ctx.response.remove('Transfer-Encoding')
ctx.length = 0
return res.end()
}
// Http为2+的版本的时候,设置body为对应的http状态码
if (ctx.req.httpVersionMajor >= 2) {
body = String(code)
} else {
body = ctx.message || String(code)
}
// 如果headersSent不为真,直接返回ctx.type为text,txt.length为Buffer.byteLength(body)
if (!res.headersSent) {
ctx.type = 'text'
ctx.length = Buffer.byteLength(body)
}
return res.end(body)
}

// responses
// body为Buffer或者string类型的时候
if (Buffer.isBuffer(body)) return res.end(body)
if (typeof body === 'string') return res.end(body)
// body为Stream时,开启管道body.pipe
if (body instanceof Stream) return body.pipe(res)
// body为json的时候,转化为字符串,并设置ctx.length后返回结果。
// body: json
body = JSON.stringify(body)
if (!res.headersSent) {
ctx.length = Buffer.byteLength(body)
}
res.end(body)
}

1.6 异常处理
到此为止,application.js里面的逻辑我们就剩下异常处理没有看了,接下来我们看异常处理。
可以看到在koa里面处理错误很简单,在真实业务里面,我们可以做一些额外的操作。

onerror (err) {
// When dealing with cross-globals a normal instanceof check doesn't work properly.
// See koajs/koa#1466
// We can probably remove it once jest fixes jestjs/jest#2549.
const isNativeError =
Object.prototype.toString.call(err) === '[object Error]' ||
err instanceof Error
if (!isNativeError) throw new TypeError(util.format('non-error thrown: %j', err))

if (err.status === 404 || err.expose) return
if (this.silent) return

const msg = err.stack || err.toString()
console.error(`\n${msg.replace(/^/gm, '  ')}\n`)

}

继续返回到callback里面,寻找异常处理的代码

if (!this.listenerCount('error')) this.on('error', this.onerror)

由此发现,在执行回调函数中,如果application中监听error事件个数大于0,则用我们自己的异常监听,否则用koa的异常监听。
到此为止,application.js已经完整分析了一遍;

2、Context的实现
翻源码发现像inspect、toJSON等方法在application.js里面都有所提到;Context两大核心功能就是委托机制以及Cookie的操作;

2.1委托机制
我们首先来看委托机制。在引入库这一块,我们可以看到如下代码,委托机制其实将其他属性挂载在自身。这里顺便提一下,我在之前面试的时候被问到过Symbol的应用,下次面试官如果再次问我这个问题,那么我就可以扩展到koa源码中。

const delegate = require('delegates')
const COOKIES = Symbol('context#cookies')

翻到底部我们可以看到在context里面将reponse和request的一些属性挂载到proto(ctx)
`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')

接下来我们翻delegate的源码,我这里直接从官网复制到我自己的本地了 整体构造函数如下,其中创建实例的过程是典型的单例模式。然后下面的几个属性都是数组,用来存放代理的属性名。 function Delegator(proto, target) {
if (!(this instanceof Delegator)) return new Delegator(proto, target);
this.proto = proto;
this.target = target;
this.methods = [];
this.getters = [];
this.setters = [];
this.fluents = [];
}

method是如何进行代理的呢,实现源码以及分析如下; Delegator.prototype.method = function(name){
var proto = this.proto;
var target = this.target;
// 存入 methods 数组
this.methods.push(name);
// 以闭包的形式,将对proto方法的调用转为this[target]上相关方法的调用
proto[name] = function(){
// applay改变this指向
return this[target][name].apply(this[target], arguments);
};
// 返回delegator实例对象,从而实现链式调用。
return this;
};

其中setter和getter和access的源码以及分析如下 return this.getter(name).setter(name);
};

/**

  • Delegator getter name.
  • @param {String} name
  • @return {Delegator} self
  • @api public
    */

Delegator.prototype.getter = function(name){
var proto = this.proto;
var target = this.target;
this.getters.push(name);//将属性名称存入的对应类型的数组
// 利用__defineGetter__设置proto的getter,使得proto[name]获取的是proto[target[name]]的值
proto.defineGetter(name, function(){
return this[target][name];
});
// 返回delegator实例对象,从而实现链式调用。
return this;
};

/**

  • Delegator setter name.
  • @param {String} name
  • @return {Delegator} self
  • @api public
    */

Delegator.prototype.setter = function(name){
var proto = this.proto;
var target = this.target;
//将属性名称存入的对应类型的数组
this.setters.push(name);
// 利用__defineGetter__设置proto的setter,实现给proto[name]赋值时,实际改变的是proto[target[name]]的值
proto.defineSetter(name, function(val){
return this[target][name] = val;
});
// 返回delegator实例对象,实现链式调用。
return this;
};

`

2.2 Cookie的操作

Context还有一个核心模块就是Cookie的操作,源码中对cookies的操作比较简单,主要如下。
` get cookies () {
if (!this[COOKIES]) {
this[COOKIES] = new Cookies(this.req, this.res, {
keys: this.app.keys,
secure: this.request.secure
})
}
return this[COOKIES]
},

set cookies (_cookies) {
this[COOKIES] = _cookies
}

`

3、request的具体实现

request里面都是一些get、set的操作,比如对header和url的get和set.
` get header () {
return this.req.headers
},

/**

  • Set request header.
  • @api public
    */

set header (val) {
this.req.headers = val
},

/**

  • Return request header, alias as request.header
  • @return {Object}
  • @api public
    */

get headers () {
return this.req.headers
},

/**

  • Set request header, alias as request.header
  • @api public
    */

set headers (val) {
this.req.headers = val
},

/**

  • Get request URL.
  • @return {String}
  • @api public
    */

get url () {
return this.req.url
},

/**

  • Set request URL.
  • @api public
    */

set url (val) {
this.req.url = val
},

`

4、response的实现

response的实现也都是各种get、set操作,稍微看看就行了。

结尾

到这里,我们就把koa源码看完了。后序还会手写一个简易版的koa。可以关注俺的仓库https://gitee.com/zhang-shichuang,哈哈哈哈。整体来看,koa源码没有想象中那么难。然而koa在企业的项目应用中往往没有简单。学习源码是为了让我们学习别人的编程思想以及设计思路,然后让我们更加便捷的开发以及丰富自己的武器库。站在巨人的肩膀上,我们才能看的更远。
最后 热爱可抵岁月漫长,一起加油吧!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant