Koa

Koa初体验

___node.js的下一代web框架

  • koa注册的中间件提供了两个参数:
    • ctx:上下文(Context)对象;
    • koa并没有像express一样,将req和res分开,而是将它们作为ctx的属性;
    • ctx代表依次请求的上下文对象;
    • ctx.request:获取请求对象;
    • ctx.response:获取响应对象;
  • next:本质上是一个dispatch,类似于之前的next;
1
2
3
4
5
6
7
8
9
10
11
const Koa = require('koa');

const app = new Koa()

app.use((ctx,next)=>{
ctx.response.body="Hello"
})

app.listen(8000,()=>{
console.log("服务器启动成功");
})

Koa中间件

  • koa通过创建的app对象,注册中间件只能通过use方法:

    • Koa并没有提供methods的方式来注册中间件;

    • 也没有提供path中间件来匹配路径;

  • 但是真实开发中我们如何将路径和method分离呢?

    • 方式一:根据request自己来判断;

    • 方式二:使用第三方路由中间件;

Koa 路由

使用第三方库:npm install koa-router

  • 封装user.router.js文件
  • 在app中注册router.routes()为中间件
  • 注意:allowedMethods用于判断某一个method是否支持:
    • 如果我们请求 get,那么是正常的请求,因为我们有实现get;
    • 如果我们请求 put、delete、patch,那么就自动报错:Method Not Allowed,状态码:405;
    • 如果我们请求 link、copy、lock,那么就自动报错:Not Implemented,状态码:501;

参数解析

params - query

1
2
3
4
5
const userRouter = new Router({prefix:'/users'})
userRouter.get('/:id',(ctx,next)=>{
console.log(ctx.request.params);
console.log(ctx.request.query);
})

json - urlencoded - form-data

解析jsonx-www-form-urlencoded需要:

  • 第三方库const bodyParser = require('koa-bodyparser')
  • 使用 koa-bodyparser的中间件app.use(bodyParser())

解析:form-data

  • const multer = require('koa-multer')
  • const upload = multer() app.use(upload.any())
1
2
3
4
5
app.use((ctx,next)=>{
console.log(ctx.req.body); //form-data 需要在req的body里 而不是request
console.log(ctx.request.body); //json格式 和 urlencoded
ctx.response.body="Hello"
})
multer 文件上传处理

使用multer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const uploadRouter = new Router({prefix:'/upload'})

const storage = multer.diskStorage({
destination:(req,file,cb)=>{
cb(null,'./uploads')
},
filename:(req,file,cb)=>{
cb(null,Date.now()+path.extname(file.originalname))
}
})

const upload = multer({
// dest: './uploads/'
storage
})

uploadRouter.post('/avatar',upload.single('avatar'),(ctx,next)=>{
console.log(ctx.req.file);
ctx.request.body="上传头像成功"
})

koa数据响应

输出结果:body将响应主体设置为以下之一:

  • string :字符串数据
  • Buffer :Buffer数据
  • Stream :流数据
  • Object|| Array:对象或者数组
  • null :不输出任何内容
  • 如果response.status尚未设置,Koa会自动将状态设置为200或204(没有内容)。
1
2
ctx.status = 200
ctx.body="hello koa" //可以这样来写的原因是 本质上在源码中做了一个代理

源码中:

静态服务器

koa并没有内置部署相关的功能 所以使用第三方库 npm install koa-static

app.use(koaStatic('./build'))

错误处理方式

通过发出事件 接收事件

1
2
3
4
5
6
7
8
9
10
11
app.use((ctx,next)=>{
const isLogin = false
if(!isLogin){
ctx.app.emit("error",new Error("你还没登陆呢"),ctx)
}
})

app.on('error',(err,ctx)=>{
ctx.status = 401
ctx.body = err.message
})

koa源码

  • 创建koa

  • 开启监听

  • 中间件的注册

  • 监听回调

在之前的写多个中间件只执行最后一个的原因就是源码中handleRequest中 return fnMiddleware(ctx).then(handleResponse).catch(onerror);

  • compose

Koa和express的对比

### 从架构设计上

  • express 完整且强大 其中帮助我们内置了很多好用的功能
  • koa是简洁自由的 只包含最核心的功能 不会对使用其他中间件限制
    • 需要通过路由或者自己判断请求方式或者其他功能
  • express和koa框架他们的核心都是中间件
    • 但是他们的中间件执行机制不同,特别针对某个中间件包含异步操作时

两者中间件的执行顺序对比

案例:

假如有三个中间件在一次请求中匹配到 并且按照顺序执行

希望实现的方案是:

  • 在middleware1 中,在req.message中添加一个字符串aaa
  • 在middleware2 中,在req.message中添加一个字符串bbb
  • 在middleware3中,在req.message中添加一个字符串ccc
  • 当所有内容添加结束 在middleware1中 通过res 返回最终的结果

express实现

  • 同步数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const express = require('express')
const app = express()

const middleware1 = (req,res,next)=>{
req.message = "aaa"
next()
res.end(req.message)
}
const middleware2 = (req,res,next)=>{
req.message += "bbb"
next()
}const middleware3 = (req,res,next)=>{
req.message += "ccc"
}
app.use(middleware1,middleware2,middleware3)

源码中首先主动调用了一次next() 遇到了next后继续匹配 然后匹配到middleware2 之后执行middleware2 的next 执行到了middleware3以后逐步回到middleware1 所以返回的结果是 aaabbbcc

  • 异步数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const express = require('express')
const axios = require('axios')
const app = express()

const middleware1 = (req,res,next)=>{
req.message = "aaa"
next()
res.end(req.message)
}
const middleware2 = (req,res,next)=>{
req.message += "bbb"
next()
}const middleware3 = (req,res,next)=>{
axios.get("http://123.207.32.32:9001/lyric?id=167876").then(result =>{req.message+=result.data.lrc.lyric})
}
app.use(middleware1,middleware2,middleware3)

结果是aaabbb 并不会有请求的结果 如果想要有结果的话那么把res.end()写在 middleware3里面请求的后面 或者不把middleware3 当作一个中间件 而是在middleware1中把 middleware3当作一个函数调用才能实现

koa实现

  • 同步数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Koa = require('koa');
const app = new Koa();
const middleware1 = (ctx, next) => {
ctx.message = "aaa";
next();
ctx.body = ctx.message;
}
const middleware2 = (ctx, next) => {
ctx.message += "bbb";
next();
}
const middleware3 = (ctx, next) => {
ctx.message += "ccc";
}
app.use(middleware1);
app.use(middleware2);
app.use(middleware3);

  • 异步
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const Koa = require('koa');
const axios = require('axios')
const app = new Koa();

const middleware1 = async (ctx, next) => {
ctx.message = "aaa";
await next();
ctx.body = ctx.message;
}

const middleware2 = async (ctx, next) => {
ctx.message += "bbb";
await next();
}

const middleware3 = async (ctx, next) => {
const result = await axios.get("http://123.207.32.32:9001/lyric?id=167876")
ctx.message+=result.data.lrc.lyric
}

app.use(middleware1);
app.use(middleware2);
app.use(middleware3);

app.listen(8000, () => {
console.log("服务器启动成功~");
})

在koa中 实现next 返回的是一个Promise 而 express是一个普通同步函数 所以可以在koa中使用 async await 来实现异步操作

koa洋葱模型

两层理解含义:

  • 中间件处理代码的过程
  • Response返回body执行

request时执行到最后 然后再一层一层往外执行

如果express所有的操作都是同步的也是符合洋葱模型的 但是不能有异步的