前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >koa源码阅读[1]-koa与koa-compose

koa源码阅读[1]-koa与koa-compose

作者头像
贾顺名
发布于 2019-12-09 07:23:12
发布于 2019-12-09 07:23:12
70900
代码可运行
举报
文章被收录于专栏:全沾开发(huā)全沾开发(huā)
运行总次数:0
代码可运行

koa源码阅读[1]-koa与koa-compose

接上次挖的坑,对koa2.x相关的源码进行分析 第一篇。 不得不说,koa是一个很轻量、很优雅的http框架,尤其是在2.x以后移除了co的引入,使其代码变得更为清晰。

expresskoa同为一批人进行开发,与express相比,koa显得非常的迷你。 因为express是一个大而全的http框架,内置了类似router之类的中间件进行处理。 而在koa中,则将类似功能的中间件全部摘了出来,早期koa里边是内置了koa-compose的,而现在也是将其分了出来。 koa只保留一个简单的中间件的整合,http请求的处理,作为一个功能性的中间件框架来存在,自身仅有少量的逻辑。 koa-compose则是作为整合中间件最为关键的一个工具、洋葱模型的具体实现,所以要将两者放在一起来看。

koa基本结构

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.
├── application.js
├── request.js
├── response.js
└── context.js

关于koa整个框架的实现,也只是简单的拆分为了四个文件。

就象在上一篇笔记中模拟的那样,创建了一个对象用来注册中间件,监听http服务,这个就是application.js在做的事情。 而框架的意义呢,就是在框架内,我们要按照框架的规矩来做事情,同样的,框架也会提供给我们一些更易用的方式来让我们完成需求。 针对http.createServer回调的两个参数requestresponse进行的一次封装,简化一些常用的操作。 例如我们对Header的一些操作,在原生http模块中可能要这样写:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 获取Content-Type
request.getHeader('Content-Type')

// 设置Content-Type
response.setHeader('Content-Type', 'application/json')
response.setHeader('Content-Length', '18')
// 或者,忽略前边的statusCode,设置多个Header
response.writeHead(200, {
  'Content-Type': 'application/json',
  'Content-Length': '18'
})

而在koa中可以这样处理:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 获取Content-Type
context.request.get('Content-Type')

// 设置Content-Type
context.response.set({
  'Content-Type': 'application/json',
  'Content-Length': '18'
})

简化了一些针对requestresponse的操作,将这些封装在了request.jsresponse.js文件中。 但同时这会带来一个使用上的困扰,这样封装以后其实获取或者设置header变得层级更深,需要通过context找到requestresponse,然后才能进行操作。 所以,koa使用了node-delegates来进一步简化这些步骤,将request.getresponse.set通通代理到context上。 也就是说,代理后的操作是这样子的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
context.get('Content-Type')

// 设置Content-Type
context.set({
  'Content-Type': 'application/json',
  'Content-Length': '18'
})

这样就变得很清晰了,获取Header,设置Header再也不会担心写成request.setHeader,一气呵成,通过context.js来整合request.jsresponse.js的行为。 同时context.js也会提供一些其他的工具函数,例如Cookie之类的操作。

application引入contextcontext中又整合了requestresponse的功能,四个文件的作用已经很清晰了:

file

desc

applicaiton

中间件的管理、http.createServer的回调处理,生成Context作为本次请求的参数,并调用中间件

request

针对http.createServer -> request功能上的封装

response

针对http.createServer -> response功能上的封装

context

整合request与response的部分功能,并提供一些额外的功能

而在代码结构上,只有application对外的koa是采用的Class的方式,其他三个文件均是抛出一个普通的Object

拿一个完整的流程来解释

创建服务

首先,我们需要创建一个http服务,在koa2.x中创建服务与koa1.x稍微有些区别,要求使用实例化的方式来进行创建:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const app = new Koa()

而在实例化的过程中,其实koa只做了有限的事情,创建了几个实例属性。 将引入的contextrequest以及response通过Object.create拷贝的方式放到实例中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
this.middleware = [] // 最关键的一个实例属性

// 用于在收到请求后创建上下文使用
this.context = Object.create(context)
this.request = Object.create(request)
this.response = Object.create(response)

在实例化完成后,我们就要进行注册中间件来实现我们的业务逻辑了,上边也提到了,koa仅用作一个中间件的整合以及请求的监听。 所以不会像express那样提供router.getrouter.post之类的操作,仅仅存在一个比较接近http.createServeruse()。 接下来的步骤就是注册中间件并监听一个端口号启动服务:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const port = 8000

app.use(async (ctx, next) => {
  console.time('request')
  await next()
  console.timeEnd('request')
})
app.use(async (ctx, next) => {
  await next()
  ctx.body = ctx.body.toUpperCase()
})

app.use(ctx => {
  ctx.body = 'Hello World'
})

app.use(ctx => {
  console.log('never output')
})

app.listen(port, () => console.log(`Server run as http://127.0.0.1:${port}`))

在翻看application.js的源码时,可以看到,暴露给外部的方法,常用的基本上就是uselisten。 一个用来加载中间件,另一个用来监听端口并启动服务。

而这两个函数实际上并没有过多的逻辑,在use中仅仅是判断了传入的参数是否为一个function,以及在2.x版本针对Generator函数的一些特殊处理,将其转换为了Promise形式的函数,并将其push到构造函数中创建的middleware数组中。 这个是从1.x过渡到2.x的一个工具,在3.x版本将直接移除Generator的支持。 其实在koa-convert内部也是引用了cokoa-compose来进行转化,所以也就不再赘述。

而在listen中做的事情就更简单了,只是简单的调用http.createServer来创建服务,并监听对应的端口之类的操作。 有一个细节在于,createServer中传入的是koa实例的另一个方法调用后的返回值callback,这个方法才是真正的回调处理,listen只是http模块的一个快捷方式。 这个是为了一些用socket.iohttps或者一些其他的http模块来进行使用的。 也就意味着,只要是可以提供与http模块一致的行为,koa都可以很方便的接入。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
listen(...args) {
  debug('listen')
  const server = http.createServer(this.callback())
  return server.listen(...args)
}

使用koa-compose合并中间件

所以我们就来看看callback的实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
}

在函数内部的第一步,就是要处理中间件,将一个数组中的中间件转换为我们想要的洋葱模型格式的。 这里就用到了比较核心的koa-compose

其实它的功能上与co类似,只不过把co处理Generator函数那部分逻辑全部去掉了,本身co的代码也就是一两百行,所以精简后的koa-compose代码仅有48行。

我们知道,async函数实际上剥开它的语法糖以后是长这个样子的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
async function func () {
  return 123
}

// ==>

function func () {
  return Promise.resolve(123)
}
// or
function func () {
  return new Promise(resolve => resolve(123))
}

所以拿上述use的代码举例,实际上koa-compose拿到的是这样的参数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[
  function (ctx, next) {
    return new Promise(resolve => {
      console.time('request')
      next().then(() => {
        console.timeEnd('request')
        resolve()
      })
    })
  },
  function (ctx, next) {
    return new Promise(resolve => {
      next().then(() => {
        ctx.body = ctx.body.toUpperCase()
        resolve()
      })
    })
  },
  function (ctx, next) {
    return new Promise(resolve => {
      ctx.body = 'Hello World'
      resolve()
    })
  },
  function (ctx, next) {
    return new Promise(resolve => {
      console.log('never output')
      resolve()
    })
  }
]

就像在第四个函数中输出表示的那样,第四个中间件不会被执行,因为第三个中间件并没有调用next,所以实现类似这样的一个洋葱模型是很有意思的一件事情。 首先抛开不变的ctx不谈,洋葱模型的实现核心在于next的处理。 因为next是你进入下一层中间件的钥匙,只有手动触发以后才会进入下一层中间件。 然后我们还需要保证next要在中间件执行完毕后进行resolve,返回到上一层中间件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 {
      return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
    } catch (err) {
      return Promise.reject(err)
    }
  }
}

所以明确了这两点以后,上边的代码就会变得很清晰:

  1. next用来进入下一个中间件
  2. next在当前中间件执行完成后会触发回调通知上一个中间件,而完成的前提是内部的中间件已经执行完成(resolved)

可以看到在调用koa-compose以后实际上会返回一个自执行函数。 在执行函数的开头部分,判断当前中间件的下标来防止在一个中间件中多次调用next。 因为如果多次调用next,就会导致下一个中间件的多次执行,这样就破坏了洋葱模型。

其次就是compose实际上提供了一个在洋葱模型全部执行完毕后的回调,一个可选的参数,实际上作用与调用compose后边的then处理没有太大区别。

以及上边提到的,next是进入下一个中间件的钥匙,可以在这一个柯里化函数的应用上看出来:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Promise.resolve(fn(context, dispatch.bind(null, i + 1)))

将自身绑定了index参数后传入本次中间件,作为调用函数的第二个参数,也就是next,效果就像调用了dispatch(1),这样就是一个洋葱模型的实现。 而fn的调用如果是一个async function,那么外层的Promise.resolve会等到内部的async执行resolve以后才会触发resolve,例如这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Promise.resolve(new Promise(resolve => setTimeout(resolve, 500))).then(console.log) // 500ms以后才会触发 console.log

P.S. 一个从koa1.x切换到koa2.x的暗坑,co会对数组进行特殊处理,使用Promise.all进行包装,但是koa2.x没有这样的操作。 所以如果在中间件中要针对一个数组进行异步操作,一定要手动添加Promise.all,或者说等草案中的await*

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// koa1.x
yield [Promise.resolve(1), Promise.resolve(2)]              // [1, 2]

// koa2.x
await [Promise.resolve(1), Promise.resolve(2)]              // [<Promise>, <Promise>]

// ==>
await Promise.all([Promise.resolve(1), Promise.resolve(2)]) // [1, 2]
await* [Promise.resolve(1), Promise.resolve(2)]             // [1, 2]

接收请求,处理返回值

经过上边的代码,一个koa服务已经算是运行起来了,接下来就是访问看效果了。 在接收到一个请求后,koa会拿之前提到的contextrequestresponse来创建本次请求所使用的上下文。 在koa1.x中,上下文是绑定在this上的,而在koa2.x是作为第一个参数传入进来的。 个人猜测可能是因为Generator不能使用箭头函数,而async函数可以使用箭头函数导致的吧:) 纯属个人YY

总之,我们通过上边提到的三个模块创建了一个请求所需的上下文,基本上是一通儿赋值,代码就不贴了,没有太多逻辑,就是有一个小细节比较有意思:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
request.response = response
response.request = request

让两者之间产生了一个引用关系,既可以通过request获取到response,也可以通过response获取到request。 而且这是一个递归的引用,类似这样的操作:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let obj = {}

obj.obj = obj

obj.obj.obj.obj === obj // true

同时如上文提到的,在context创建的过程中,将一大批的requestresponse的属性、方法代理到了自身,有兴趣的可以自己翻看源码(看着有点晕):koa.js | context.js 这个delegate的实现也算是比较简单,通过取出原始的属性,然后存一个引用,在自身的属性被触发时调用对应的引用,类似一个民间版的Proxy吧,期待后续能够使用Proxy代替它。

然后我们会将生成好的context作为参数传入koa-compose生成的洋葱中去。 因为无论何种情况,洋葱肯定会返回结果的(出错与否),所以我们还需要在最后有一个finished的处理,做一些类似将ctx.body转换为数据进行输出之类的操作。

koa使用了大量的getset访问器来实现功能,例如最常用的ctx.body = 'XXX',它是来自responseset body。 这应该是requestresponse中逻辑最复杂的一个方法了。 里边要处理很多东西,例如在body内容为空时帮助你修改请求的status code为204,并移除无用的headers。 以及如果没有手动指定status code,会默认指定为200。 甚至还会根据当前传入的参数来判断content-type应该是html还是普通的text

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// string
if ('string' == typeof val) {
  if (setType) this.type = /^\s*</.test(val) ? 'html' : 'text'
  this.length = Buffer.byteLength(val)
  return
}

以及还包含针对流(Stream)的特殊处理,例如如果要用koa实现静态资源下载的功能,也是可以直接调用ctx.body进行赋值的,所有的东西都已经在response.js中帮你处理好了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// stream
if ('function' == typeof val.pipe) {
  onFinish(this.res, destroy.bind(null, val))
  ensureErrorHandler(val, err => this.ctx.onerror(err))

  // overwriting
  if (null != original && original != val) this.remove('Content-Length')

  if (setType) this.type = 'bin'
  return
}

// 可以理解为是这样的代码
let stream = fs.createReadStream('package.json')
ctx.body = stream

// set body中的处理
onFinish(res, () => {
  destory(stream)
})

stream.pipe(res) // 使response接收流是在洋葱模型完全执行完以后再进行的

onFinish用来监听流是否结束、destory用来关闭流

其余的访问器基本上就是一些常见操作的封装,例如针对querystring的封装。 在使用原生http模块的情况下,处理URL中的参数,是需要自己引入额外的包进行处理的,最常见的是querystringkoa也是在内部引入的该模块。 所以对外抛出的query大致是这个样子的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
get query() {
  let query = parse(this.req).query
  return qs.parse(query)
}

// use
let { id, name } = ctx.query // 因为 get query也被代理到了context上,所以可以直接引用

parse为parseurl库,用来从request中提出query参数

亦或者针对cookies的封装,也是内置了最流行的cookies。 在第一次触发get cookies时才去实例化Cookie对象,将这些繁琐的操作挡在用户看不到的地方:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
}

所以在koa中使用Cookie就像这样就可以了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
this.cookies.get('uid')

this.cookies.set('name', 'Niko')

// 如果不想用cookies模块,完全可以自己赋值为自己想用的cookie
this.cookies = CustomeCookie

this.cookies.mget(['uid', 'name'])

这是因为在get cookies里边有判断,如果没有一个可用的Cookie实例,才会默认去实例化。

洋葱模型执行完成后的一些操作

koa的一个请求流程是这样的,先执行洋葱里边的所有中间件,在执行完成以后,还会有一个回调函数。 该回调用来根据中间件执行过程中所做的事情来决定返回给客户端什么数据。 拿到ctx.bodyctx.status这些参数进行处理。 包括前边提到的流(Stream)的处理都在这里:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if (body instanceof Stream) return body.pipe(res) // 等到这里结束后才会调用我们上边`set body`中对应的`onFinish`的处理

同时上边还有一个特殊的处理,如果为false则不做任何处理,直接返回:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if (!ctx.writable) return

其实这个也是response提供的一个访问器,这里边用来判断当前请求是否已经调用过end给客户端返回了数据,如果已经触发了response.end()以后,则response.finished会被置为true,也就是说,本次请求已经结束了,同时访问器中还处理了一个bug,请求已经返回结果了,但是依然没有关闭套接字:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
get writable() {
  // can't write any more after response finished
  if (this.res.finished) return false

  const socket = this.res.socket
  // There are already pending outgoing res, but still writable
  // https://github.com/nodejs/node/blob/v4.4.7/lib/_http_server.js#L486
  if (!socket) return true
  return socket.writable
}

这里就有一个koaexpress对比的劣势了,因为koa采用的是一个洋葱模型,对于返回值,如果是使用ctx.body = 'XXX'来进行赋值,这会导致最终调用response.end时在洋葱全部执行完成后再进行的,也就是上边所描述的回调中,而express就是在中间件中就可以自由控制何时返回数据:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// express.js
router.get('/', function (req, res) {
  res.send('hello world')

  // 在发送数据后做一些其他处理
  appendLog()
})

// koa.js
app.use(ctx => {
  ctx.body = 'hello world'

  // 然而依然发生在发送数据之前
  appendLog()
})

不过好在还是可以通过直接调用原生的response对象来进行发送数据的,当我们手动调用了response.end以后(response.finished === true),就意味着最终的回调会直接跳过,不做任何处理。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
app.use(ctx => {
  ctx.res.end('hello world')

  // 在发送数据后做一些其他处理
  appendLog()
})
异常处理

koa的整个请求,实际上还是一个Promise,所以在洋葱模型后边的监听不仅仅有resolve,对reject也同样是有处理的。 期间任何一环出bug都会导致后续的中间件以及前边等待回调的中间件终止,直接跳转到最近的一个异常处理模块。 所以,如果有类似接口耗时统计的中间件,一定要记得在try-catch中执行next的操作:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
app.use(async (ctx, next) => {
  try {
    await next()
  } catch (e) {
    console.error(e)
    ctx.body = 'error' // 因为内部的中间件并没有catch 捕获异常,所以抛出到了这里
  }
})

app.use(async (ctx, next) => {
  let startTime = new Date()
  try {
    await next()
  } finally {
    let endTime = new Date() // 抛出异常,但是不影响这里的正常输出
  }
})

app.use(ctx => Promise.reject(new Error('test')))

P.S. 如果异常被捕获,则会继续执行后续的response

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
app.use(async (ctx, next) => {
  try {
    throw new Error('test')
  } catch (e) {
    await next()
  }
})

app.use(ctx => {
  ctx.body = 'hello'
})

// curl 127.0.0.1 
// > hello

如果自己的中间件没有捕获异常,就会走到默认的异常处理模块中。 在默认的异常模块中,基本上是针对statusCode的一些处理,以及一些默认的错误显示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const code = statuses[err.status]
const msg = err.expose ? err.message : code
this.status = err.status
this.length = Buffer.byteLength(msg)
this.res.end(msg)

statuses是一个第三方模块,包括各种http code的信息: statuses 建议在最外层的中间件都自己做异常处理,因为默认的错误提示有点儿太难看了(纯文本),自己处理跳转到异常处理页面会好一些,以及避免一些接口因为默认的异常信息导致解析失败。

redirect的注意事项

在原生http模块中进行302的操作(俗称重定向),需要这么做:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
response.writeHead(302, {
  'Location': 'redirect.html'
})
response.end()
// or
response.statusCode = 302
response.setHeader('Location', 'redirect.html')
response.end()

而在koa中也有redirect的封装,可以通过直接调用redirect函数来完成重定向,但是需要注意的是,调用完redirect之后并没有直接触发response.end(),它仅仅是添加了一个statusCodeLocation而已:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
redirect(url, alt) {
  // location
  if ('back' == url) url = this.ctx.get('Referrer') || alt || '/'
  this.set('Location', url)

  // status
  if (!statuses.redirect[this.status]) this.status = 302

  // html
  if (this.ctx.accepts('html')) {
    url = escape(url)
    this.type = 'text/html charset=utf-8'
    this.body = `Redirecting to <a href="${url}">${url}</a>.`
    return
  }

  // text
  this.type = 'text/plain charset=utf-8'
  this.body = `Redirecting to ${url}.`
}

后续的代码还会继续执行,所以建议在redirect之后手动结束当前的请求,也就是直接return,不然很有可能后续的statusbody赋值很可能会导致一些诡异的问题。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
app.use(ctx => {
  ctx.redirect('https://baidu.com')

  // 建议直接return

  // 后续的代码还在执行
  ctx.body = 'hello world'
  ctx.status = 200 // statusCode的改变导致redirect失效 
})

小记

koa是一个很好玩的框架,在阅读源码的过程中,其实也发现了一些小问题:

  1. 多人合作维护一份代码,确实能够看出各人都有不同的编码风格,例如typeof val !== 'string''number' == typeof code,很显然的两种风格。2333
  2. delegate的调用方式在属性特别多的时候并不是很好看,一大长串的链式调用,如果换成循环会更好看一下

但是,koa依然是一个很棒的框架,很适合阅读源码来进行学习,这些都是一些小细节,无伤大雅。

总结一下koakoa-compose的作用:

  • koa 注册中间件、注册http服务、生成请求上下文调用中间件、处理中间件对上下文对象的操作、返回数据结束请求
  • koa-compose 将数组中的中间件集合转换为串行调用,并提供钥匙(next)用来跳转下一个中间件,以及监听next获取内部中间件执行结束的通知
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Node学习笔记 - Koa源码阅读
最近经过一些反思,发现现在很多时候用node的框架,都缺乏对于node框架的源码理解和实现原理,所以会在接下来的一段时间里进行学习node的框架实现原理,从中去更加深入理解node当中的一些技巧以及一些细节上的问题。
LamHo
2022/09/26
6430
Node学习笔记 - Koa源码阅读
源码共读-Koa
Koa是基于 Node.js 平台的下一代 web 开发框架,它的源码可以看这里,本章通过源码来简绍一下Koa是怎么实现的。
kai666666
2024/07/11
800
源码共读-Koa
Koa源码阅读
Koa 在众多NodeJs框架中,以短小精悍而著称,核心代码只有大约570行,非常适合源码阅读。
小刀c
2022/08/16
5270
Koa源码阅读
Node进阶——之事无巨细手写Koa源码
对比发现,相对原生,Koa多了两个实例上的use、listen方法,和use回调中的ctx、next两个参数。这四个不同,几乎就是Koa的全部了,也是这四个不同让Koa如此强大。
ConardLi
2020/05/25
4780
Node进阶——之事无巨细手写Koa源码
koa 源码解析(1)
koa 是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架。 使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套, 并极大地提升错误处理的效率。koa 不在内核方法中绑定任何中间件, 它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手。
零式的天空
2022/03/25
2730
Koa源码学习
koa是一个非常流行的Node.js http框架。本文我们来学习下它的使用和相关源码
ACK
2023/10/19
2680
Koa源码学习
实现简单的 Koa
koa 为了能够简化 API,引入上下文 context,将原始请求对象 req 和 响应对象 res 封装并挂载到 context 上,并且在 context 上设置 getter 和 setter,从而简化操作
Cellinlab
2023/05/17
2800
实现简单的 Koa
KoaJS
koa致力于成为一个更小、更富有表现力、更健壮的、更轻量的web开发框架。因为它所有功能都通过插件实现,这种插拔式的架构设计模式,很符合unix哲学。
程序猿川子
2022/07/14
3580
手写Koa.js源码
Express的源码还是比较复杂的,自带了路由处理和静态资源支持等等功能,功能比较全面。与之相比,本文要讲的Koa就简洁多了,Koa虽然是Express的原班人马写的,但是设计思路却不一样。Express更多是偏向All in one的思想,各种功能都集成在一起,而Koa本身的库只有一个中间件内核,其他像路由处理和静态资源这些功能都没有,全部需要引入第三方中间件库才能实现。下面这张图可以直观的看到Express和koa在功能上的区别,此图来自于官方文档:
蒋鹏飞
2020/11/11
1.2K0
手写Koa.js源码
以小白的角度解读Koa源码
使用Koa已有一段时间,为什么会从Express转向Koa呢,那还是得从Express上说起。对于服务端的Web框架来说,Express更为贴近「Web Framework」这一概念,比如自带的路由,经过多年的运行,也使其生态丰富稳定。
JowayYoung
2020/04/01
8370
读 koa2 源码后的一些思考与实践
优点这个东西,我直接说它多好,你可能又不开心,但是我们可以对比哦!这里我只说它对比原生的 Node.js开启 http 服务 带来了哪些优点!
coder_koala
2019/12/11
9420
读 koa2 源码后的一些思考与实践
koa 源码解析
本次的文章是之前 koa 专题的延续,计划书写两篇文章,本篇从零实现一个简单版的 koa 框架(里面可能涉及一点 node 知识,不会太讲,大家如果遇到不了解的可以自行百度查看,也可以看官网文档了解使用)。包括上下文 ctx 组合 req, res 的实现,中间件机制的实现。第二篇写下 bodyparser、 router 中间件的简单实现,理解其原理。
测不准
2021/07/14
5110
【koa快速入门】之深究原理
前两节我们已经介绍了koa的基本使用和koa项目的最佳实践,今天我们来深究下koa2的原理。
luciozhang
2023/04/22
2810
【koa快速入门】之深究原理
前端面试-实现一个简版koa
在koa官网有说明在ctx挂载了一系列request和response的属性别名。
Careteen
2022/02/14
4840
【nodejs】手写简易版 koa 及常用中间件
首先我们要实现 koa 的 use 和 listen 方法,我们这样使用 MyKoa。
一尾流莺
2022/12/10
7370
【nodejs】手写简易版 koa 及常用中间件
Koa 源码剖析
跟 Express 相比,Koa 的源码异常简洁,Express 因为把路由相关的代码嵌入到了主要逻辑中,因此读 Express 的源码可能长时间不得要领,而直接读 Koa 的源码几乎没有什么障碍。
用户8921923
2022/10/24
1K0
Koa 源码剖析
koa实践及其手撸
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。
一粒小麦
2019/07/18
1.2K0
koa实践及其手撸
Koa源码解析,带你实现一个迷你版的Koa
本文是我在阅读 Koa 源码后,并实现迷你版 Koa 的过程。如果你使用过 Koa 但不知道内部的原理,我想这篇文章应该能够帮助到你,实现一个迷你版的 Koa 不会很难。
WahFung
2020/08/24
8950
Koa源码解析,带你实现一个迷你版的Koa
你需要掌握的 Koa 洋葱模型和中间件
Koa 是一个 nodejs 框架,经常用于写 web 后端服务。它是 Express 框架的原班人马开发的新一代 web 框架,使用了 async / await 来优雅处理无处不在的异步逻辑。
前端西瓜哥
2022/12/21
6090
你需要掌握的 Koa 洋葱模型和中间件
【Node】深入浅出 Koa 的洋葱模型
本文将讲解 koa 的洋葱模型,我们为什么要使用洋葱模型,以及它的原理实现。掌握洋葱模型对于理解 koa 至关重要,希望本文对你有所帮助~
GopalFeng
2022/08/01
7540
【Node】深入浅出 Koa 的洋葱模型
相关推荐
Node学习笔记 - Koa源码阅读
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文