koa源码分析
最近项目要使用koa,所以提前学习一下,顺便看了koa框架的源码.
注:源码是koa2.x
koa的源码很简洁,关键代码只有4个文件,当然还包括一些依赖npm包
const Koa = require('koa'); const app = new Koa(); app.use(async (ctx, next) => {
await next();
ctx.type = 'text/html';
ctx.body = '<h1>Hello, koa2!</h2>';
}); app.listen();
console.log('app started at port 3000....');
我们由上面的代码开始深入到koa的源码:
application.js文件:
上面代码的开头引入koa框架,接着const app = new Koa();创建koa实例app,koa的构造函数很简单,如下:
constructor() {
super(); this.proxy = false;
this.middleware = []; //用来存放中间件
this.subdomainOffset = ;
this.env = process.env.NODE_ENV || 'development'; //运行环境
this.context = Object.create(context); //创建context对象
this.request = Object.create(request); //创建request对象
this.response = Object.create(response); //创建response对象
}
use函数:
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
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;
}
use函数首先判断参数是否是函数,不是就报错,然后判断这个函数是否是generator函数,如果是generator则需要转换一下,通过两个判断后,将这个函数push到middleware数组中保存.最后返回this(也是app实例)
listen函数:
isten() {
debug('listen');
const server = http.createServer(this.callback());
return server.listen.apply(server, arguments);
}
listen函数里面调用http.createServe()创建http服务,关键是参数this.callback(),看一下代码:
callback() {
const fn = compose(this.middleware); if (!this.listeners('error').length) this.on('error', this.onerror); const handleRequest = (req, res) => {
res.statusCode = ;
const ctx = this.createContext(req, res);
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx);
onFinished(res, onerror);
return fn(ctx).then(handleResponse).catch(onerror);
}; return handleRequest;
}
首先,把所有middleware进行了组合,使用了koa-compose,我们也不用去管他的内部实现,简单来说就是返回了一个promise数组的递归调用。
这里的 const fn = compose(this.middleware);对应的调用代码如下:(查看koa-compose源码):
return function (context, next) {
// last called middleware #
let index = -
return dispatch()
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, function next () {
return dispatch(i + )
}))
} catch (err) {
return Promise.reject(err)
}
}
}
}
return前面的作用是做参数检测,return后面也就是上面贴出来的代码才是我们要关注的:
运行流程:
i = 0 ==> index = 0 ==> fn = middleware[0] ==> return Promise.resolve(//如果fn里面存在await,则functions next()函数会被掉用, 则运行return dispatch(i + 1),以此类推)
知道中间件(当然一般指最后一个中间件)中不存在await或者所有的中间件都加载完毕(i === middleware.length) ,compose函数则最终返回.
例如:
const Koa = require('koa')
const app = new Koa()
app.use(async function (ctx, next) {
console.log('>> one');
await next();
console.log('<< one');
});
app.use(ctx => {
ctx.body='hello world gcy';
})
上面代码注册了两个中间件,经过const fn = compose(this.middleware)后返回:
fn的形式如下:
Promise.resolve(function(ctx) {
console.log('>> one');
return Promose.resolve(function(ctx){
ctx.body='hello world gcy';
}).then(() => {
console.log('<< one');
})
})
执行结果: 后台输出:>> one -- > 页面输出:hello world gcy --> 后台输出:<< one
接下来:
if (!this.listeners('error').length) this.on('error', this.onerror);
在处理http请求之前,koa会注册一个默认的错误处理函数,但我们每次http请求错误实际上是由ctx.onerror处理的:
const onerror = err => ctx.onerror(err);
onFinished(res, onerror);
fn(ctx).then(() => respond(ctx)).catch(onerror)
ctx.onFinished 是确保一个流在关闭、完成和报错时都会执行相应的回调函数。onerror 就是我们http请求错误处理函数.
我们看看这个匿名函数,把http code默认设置为404,接着利用createContext函数把node返回的req和res进行了组合创建出context
来看下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.cookies = new Cookies(req, res, {
keys: this.keys,
secure: request.secure
});
request.ip = request.ips[0] || req.socket.remoteAddress || '';
context.accept = request.accept = accepts(req);
context.state = {};
return context;
}
这里面都是一堆的组合和赋值,context,request, response相互挂载
值得注意的是:context.req/context.res 和context.request/context.response的区别,context.req/context.res代表nodejs的req和res对象,而context.request/context.response是koa的request和response对象
这个函数最后返回context,然后传入fn函数,此时fn函数被执行.
callback()函数中调用的respond函数里面不过是一些收尾工作,例如判断http code为空如何输出啦,http method是head如何输出啦,body返回是流或json时如何输出。
context.js
delegate(proto, 'request') //Request相关方法委托,从而让context作为调用入口
onerror(err) //中间件执行过程中异常处理逻辑
request.js,response.js
分别对res和req进行了抽象和封装
koa源码分析的更多相关文章
- Koa源码分析(三) -- middleware机制的实现
Abstract 本系列是关于Koa框架的文章,目前关注版本是Koa v1.主要分为以下几个方面: Koa源码分析(一) -- generator Koa源码分析(二) -- co的实现 Koa源码分 ...
- Koa源码分析(二) -- co的实现
Abstract 本系列是关于Koa框架的文章,目前关注版本是Koa v1.主要分为以下几个方面: Koa源码分析(一) -- generator Koa源码分析(二) -- co的实现 Koa源码分 ...
- Koa源码分析(一) -- generator
Abstract 本系列是关于Koa框架的文章,目前关注版本是Koa v1.主要分为以下几个方面: 1. Koa源码分析(一) -- generator 2. Koa源码分析(二) -- co的实现 ...
- Node.js躬行记(19)——KOA源码分析(上)
本次分析的KOA版本是2.13.1,它非常轻量,诸如路由.模板等功能默认都不提供,需要自己引入相关的中间件. 源码的目录结构比较简单,主要分为3部分,__tests__,lib和docs,从名称中就可 ...
- Node.js躬行记(20)——KOA源码分析(下)
在上一篇中,主要分析了package.json和application.js文件,本文会分析剩下的几个文件. 一.context.js 在context.js中,会处理错误,cookie,JSON格式 ...
- koa2中间件koa和koa-compose源码分析原理(一)
koa是基于nodejs平台的下一代web开发框架,它是使用generator和promise,koa的中间件是一系列generator函数的对象.当对象被请求过来的时候,会依次经过各个中间件进行处理 ...
- co源码分析及其实践
本文始发于我的个人博客,如需转载请注明出处. 为了更好的阅读体验,可以直接进去我的个人博客看. 前言 知识储备 阅读本文需要对Generator和Promise有一个基本的了解. 这里我简单地介绍一下 ...
- redux middleware 源码分析
原文链接 middleware 的由来 在业务中需要打印每一个 action 信息来调试,又或者希望 dispatch 或 reducer 拥有异步请求的功能.面对这些场景时,一个个修改 dispat ...
- koa-convert源码分析
koa-convert最主要的作用是:将koa1包中使用的Generator函数转换成Koa2中的async函数.更准确的说是将Generator函数转换成使用co包装成的Promise对象.然后执行 ...
随机推荐
- shell script-条件语句、循环语句
条件语句 #!/bin/bash read -p "input your name:" name #第一种判断 if [ "$name" == "Mi ...
- [转]hadoop运行mapreduce作业无法连接0.0.0.0/0.0.0.0:10020
14/04/04 17:15:12 INFO mapreduce.Job: map 0% reduce 0% 14/04/04 17:19:42 INFO mapreduce.Job: map 4 ...
- charles请求入参中有乱码
工作中,需要入参,但是发现入参中,有中文的都是乱码,仔细查阅headers,发现Content-Type是application/x-www-form-urlencoded类型,而实际上,入参是jso ...
- 快速搭建LAMP
1.安装Apache sudo apt-get install apache2 2.安装Mysql sudo apt-get install mysql-server 中间会出现输入 Mysql 的 ...
- HDU 5974 A Simple Math Problem (解方程)
题意:给定a和b,求一组满足x+y=a && lcm(x, y)=b. 析:x+y = a, lcm(x, y) = b,=>x + y = a, x * y = b * k,其 ...
- ES Docs-2:Exploring ES cluster
The REST API Now that we have our node (and cluster) up and running, the next step is to understand ...
- Django 之 logging
1. logging 1.1 什么是 logging logging 模块是 Python 内置的日志管理模块,不需要额外安装. 使用: import logging logging.critical ...
- C#事件2
今天又来说一下C#中的事件,为什么会有这个又呢?一个是因为以前写过一篇关于事件的东西,二来呢是因为感觉接口这个东西完全可以替换委托来写事件.因为这两个方面的原因,重新过了一遍C#中的事件. 事件这个东 ...
- Ubuntu16.4下安装Chrome浏览器以及Chromedriver
一.Chrome浏览器的安装 对于谷歌Chrome32位版本,使用如下链接: wget https://dl.google.com/linux/direct/google-chrome-stable_ ...
- java知识点积累(一)
知识点积累 1.关于final的重要知识点: final关键字可以用于成员变量.本地变量.方法以及类: final修饰的成员变量必须在声明时被初始化,或者在构造器中初始化,否则就会报编译错误: 不能够 ...