Node.js Koa2框架的理解【一、基本使用】

1,669 阅读7分钟

本文严重抄自 可能是目前市面上比较有诚意的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之后的代码