本文严重抄自 可能是目前市面上比较有诚意的Koa2源码解读
恕在下直言刚开始我自己也写了,也参考了别人写的。但是相对于这个简直来说 天地之别,我会在这个的基础上稍微添加一些东西,如果我的实在看不下去可以去看原作者的
为什么学习Koa?
简介
koa 是由 express 原班人马打造的,小巧的,健壮的 web 开发框架,也正因为它这些的优点,eggjs 等很多 web 框架的核心也是由 koa 驱动的,熟悉 koa 代码,不仅对于使用 koa 进行开发有很大帮助,也有助于深入理解像 eggjs 等这样的,功能更加强大的框架
安装
$ nvm install 7
$ npm i koa
$ node my-koa-app.js
基本的使用 hello world
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
级联 【洋葱模型】
下面会详细介绍:可以大概看一下
官方介绍::Koa 中间件以更传统的方式级联,您可能习惯使用类似的工具 - 之前难以让用户友好地使用 node 的回调。然而,使用 async 功能,我们可以实现 “真实” 的中间件。对比 Connect 的实现,通过一系列功能直接传递控制,直到一个返回,Koa 调用“下游”,然后控制流回“上游”。
下面以 “Hello World” 的响应作为示例,当请求开始时首先请求流通过 x-response-time 和 logging 中间件,然后继续移交控制给 response 中间件。当一个中间件调用 next() 则该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为。
const Koa = require('koa');
const app = new Koa();
// logger
app.use(async (ctx, next) => {
await next();
const rt = ctx.response.get('X-Response-Time');
console.log(`${ctx.method} ${ctx.url} - ${rt}`);
});
// x-response-time
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
// response
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
详细介绍
上下文(Context)
上面代码输出的ctx:
{ request:
{ method: 'GET',
url: '/',
header:
{ host: 'localhost:3000',
connection: 'keep-alive',
'cache-control': 'max-age=0',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36',
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'accept-encoding': 'gzip, deflate, sdch, br',
'accept-language': 'zh-CN,zh;q=0.8' } },
response:
{ status: 200,
message: 'OK',
header:
{ 'content-type': 'text/plain; charset=utf-8',
'content-length': '25' } },
app: { subdomainOffset: 2, proxy: false, env: 'development' },
originalUrl: '/',
req: '<original node req>',
res: '<original node res>',
socket: '<original node socket>'
}
Koa Context 将 node 的 request 和 response 对象封装到单个对象中,为编写 Web 应用程序和 API 提供了许多有用的方法。 这些操作在 HTTP 服务器开发中频繁使用,它们被添加到此级别而不是更高级别的框架,这将强制中间件重新实现此通用功能。
每个 请求都将创建一个 Context,并在中间件中作为接收器引用,或者 ctx 标识符,如以下代码片段所示:
app.use(async ctx => {
ctx; // 这是 Context
ctx.request; // 这是 koa Request
ctx.response; // 这是 koa Response
});
为方便起见许多上下文的访问器和方法直接委托给它们的 ctx.request或 ctx.response ,不然的话它们是相同的。 例如 ctx.type 和 ctx.length 委托给 response 对象,ctx.path 和 ctx.method 委托给 request。**后面会有一篇分析源码的文章介绍**
API
** Context 具体方法和访问器.**
ctx.req
**Node 的 request 对象.**
ctx.res
** Node 的 response 对象.**
绕过 Koa 的 response 处理是 不被支持的. 应避免使用以下 node 属性:
res.statusCode
res.writeHead()
res.write()
res.end()
**ctx.request**
koa 的 Request 对象.
**ctx.response**
koa 的 Response 对象.
Request 别名
以下访问器和 Request 别名等效:
有些方法只提供getter 有些方法只提供setter 有些都提供
ctx.header // = ctx.request.header 下面的一样的 实际是koa源码 将request的属性和方法暴露给 ctx。 //请求头对象 //=设置请求头对象
ctx.headers //请求或者设置 请求头对象
ctx.method //请求方法
ctx.method= //设置请求方法
ctx.url //获取请求的url
ctx.url= //设置请求的url
ctx.originalUrl //获取请求的原始的url
ctx.origin //获取的请求的原始url涞源 包括protocol和host
ctx.href //获取完整的url 包括protocol host 和 url
ctx.path //获取请求的路径名
ctx.path= //设置请求的路径名
ctx.query //获取解析解析的查询字符串 例如 "color=blue&size=small"::{color: 'blue',size: 'small'}
ctx.query= //将查询字符串设置为给定对象
ctx.querystring //根据 ? 获取原始查询字符串.
ctx.querystring= //设置原始查询字符串。
ctx.host //获取当前主机(hostname:port)。当 app.proxy 是 true 时支持 X-Forwarded-Host,否则使用 Host。
ctx.hostname //存在时获取主机名。当 app.proxy 是 true 时支持 X-Forwarded-Host,否则使用 Host。
ctx.fresh //检查请求缓存是否“新鲜”,也就是内容没有改变。此方法用于 If-None-Match / ETag, 和 If-Modified-Since 和 Last-Modified 之间的缓存协商。 在设置一个或多个这些响应头后应该引用它。
ctx.stale //相反与 request.fresh.
ctx.socket //返回请求套接字。
ctx.protocol //返回请求协议,“https” 或 “http”。当 app.proxy 是 true 时支持 X-Forwarded-Proto。
ctx.secure //通过 ctx.protocol == "https" 来检查请求是否通过 TLS 发出。
ctx.ip //请求远程地址。 当 app.proxy 是 true 时支持 X-Forwarded-Proto。
ctx.ips //当 X-Forwarded-For 存在并且 app.proxy 被启用时,这些 ips 的数组被返回,从上游 - >下游排序。 禁用时返回一个空数组
ctx.subdomains //将子域返回为数组。
ctx.is() //检查传入请求是否包含 Content-Type 头字段, 并且包含任意的 mime type。 如果没有请求主体,返回 null。 如果没有内容类型,或者匹配失败,则返回 false。 反之则返回匹配的 content-type。
ctx.accepts() //检查给定的 type(s) 是否可以接受,如果 true,返回最佳匹配,否则为 false。 type 值可能是一个或多个 mime 类型的字符串,如 application/json,扩展名称如 json,或数组 ["json", "html", "text/plain"]。
ctx.acceptsEncodings() //检查 encodings 是否可以接受,返回最佳匹配为 true,否则为 false。 请注意,您应该将identity 作为编码之一!
ctx.acceptsCharsets() //检查 charsets 是否可以接受,在 true 时返回最佳匹配,否则为 false。
ctx.acceptsLanguages() //检查 langs 是否可以接受,如果为 true,返回最佳匹配,否则为 false。
ctx.get() //返回请求标头。
Response 别名
以下访问器和 Response 别名等效:
ctx.body // = ctx.response.body 下面的一样的 实际是koa源码 将response的属性和方法暴露给 ctx。 获取响应主体。
ctx.body= //将响应体设置
ctx.status //获取响应状态。默认情况下,response.status 设置为 404 而不是像 node 的 res.statusCode 那样默认为 200。
ctx.status= //通过数字代码设置响应状态:
ctx.message //获取响应的状态消息. 默认情况下, response.message 与 response.status 关联.
ctx.message= //将响应的状态消息设置为给定值。
ctx.length= //将响应的 Content-Length 设置为给定值
ctx.length //以数字返回响应的 Content-Length,或者从ctx.body推导出来,或者undefined。
ctx.type= //设置响应 Content-Type 通过 mime 字符串或文件扩展名。
ctx.type //获取响应 Content-Type 不含参数 "charset"。
ctx.headerSent //检查是否已经发送了一个响应头。 用于查看客户端是否可能会收到错误通知。
ctx.redirect() //执行 [302] 重定向到 url. 字符串 “back” 是特别提供Referrer支持的,当Referrer不存在时,使用 alt 或“/”。
ctx.attachment() //将 Content-Disposition 设置为 “附件” 以指示客户端提示下载。(可选)指定下载的 filename 和部
ctx.set() //设置响应标头 field 到 value:
ctx.append()//用值 val 附加额外的标头 field
ctx.remove()//删除标头 field。
ctx.lastModified= //将 Last-Modified 标头返回为 Date, 如果存在 //将 Last-Modified 标头设置为适当的 UTC 字符串。您可以将其设置为 Date 或日期字符串
ctx.etag= //设置包含 " 包裹的 ETag 响应
提一点 有关post参数的提取
*在使用koa-bodyparser 中间件时,为前端提供的接口如果post传过来的数据是 form-data 类型的, 此时通过 ctx.request.body 获取不到 post 的参数。
*原因是koa-bodyparser 中间件不支持 form-data 类型,**只有application/json请求方式的数据才能解析**
*可以使用 koa-body 中间件代替,用法跟 koa-bodyparser 差不多
中间件(middleware)执行原理
代码实例
//每一个app.use()都是一个中间件,然后把中间件push到一个数组中 先执行middleware[0]
app.use(async (ctx, next) => {
console.log('1');
await next(); // 调用下一个middleware[1]
console.log('5')
});
app.use(async (ctx, next) => {
console.log('2');
await next(); // 调用下一个middleware[2]
console.log('4');
});
app.use(async (ctx, next) => {
console.log('3');
});
输出结果: 12345
原理
*初始化koa实例后,我们会用use方法来加载中间件(middleware),会有一个数组来存储中间件,use调用顺序会决定中间件的执行顺序。
*每个中间件都是一个函数(不是函数将报错),接收两个参数,第一个是ctx上下文对象,另一个是next函数(由koa-compose定义)
*在建立好http服务器后,会调用koa-compose模块对middleware中间件数组进行处理。具体代码这里就不贴了,原理就是:会从middleware数组中取第一个函数开始执行,中间件函数中调用next方法就会去取下一个中间件函数继续执行。每个中间件函数执行完毕后都会返回一个promise对象。(ps:调用next方法并不是表示当前中间件函数执行完毕了,调用next之后仍可以继续执行其他代码)
洋葱模型
* 上面是在网上找的一个示意图,就是说中间件执行就像洋葱一样,最早use的中间件,就放在最外层。处理顺序从左到右,左边接收一个request,右边输出返回response。
* koa官方文档上把外层的中间件称为"上游",内层的中间件为"下游"。
* 一般的中间件都会执行两次,调用next之前为第一次,调用next时把控制传递给下游的下一个中间件。当下游不再有中间件或者没有执行next函数时,就将依次恢复上游中间件的行为,让上游中间件执行next之后的代码