认识 koa
express 的继任者,更轻,更小。
基本使用
koa注册的中间件提供了两个参数:
- ctx:上下文(Context)对象;
- koa并没有像express一样,将req和res分开,而是将它们作为ctx的属性;
- ctx代表依次请求的上下文对象;
- ctx.request:获取请求对象;
- ctx.response:获取响应对象;
- next:本质上是一个dispatch,类似于之前的next;
express 中如果没有在中间件中调用 end,请求会被挂起。而 koa 是所有中间件执行完就会返回 NOT FOUND 关闭连接。
没有 end,那 koa 怎么手动结束请求呢?通过 body。比如设置ctx.response.body = "请求结束"
const Koa = require('koa');
// 实例化
const app = new Koa();
app.use((ctx, next) => {
ctx.response.body = "Hello World";
});
app.listen(8000, () => {
console.log("koa初体验服务器启动成功~");
});
中间件
koa 通过创建的 app 对象,注册中间件只能通过 use 方法:
没有提供下面的注册方式:
- methods方式:
app.get('/.post')
- path方式:
app.use('/home', (ctx, next) => {})
- 连续注册:
app.use((ctx, next) => {}, (ctx, next) => {})
但是真实开发中我们如何将路径和 method 分离呢?
方式一:根据 request 自己来判断;
方式二:使用第三方路由中间件;
app.use((ctx, next) => {
if (ctx.request.url === '/login') {
if (ctx.request.method === 'GET') {
console.log("来到了这里~");
ctx.response.body = "Login Success~";
}
} else {
ctx.response.body = "other request~";
}
});
路由
koa官方并没有给我们提供路由的库,我们可以选择第三方库:koa-router
安装:npm i koa-router
koa-router 可以支持和 express 中的 use 一样,定义路径和连读定义中间件。
我们可以先封装一个 user.router.js 的文件:在app中将router.routes()注册为中间件:
const Router = require('koa-router');
// 实例化路由,并且添加公共路径前缀
const router = new Router({
prefix: "/users"});
// router 中就可以和 express 一样使用 methods 的方式注册中间件
router.get('/', (ctx, next) => {
ctx.response.body = "User Lists~";
});
router.put('/', (ctx, next) => {
ctx.response.body = "put request~";
});
module.exports = router;
路由实例的 routes 方法注册路由中间件。
const Koa = require('koa');
const userRouter = require('./router/user');
const app = new Koa();
app.use(userRouter.routes());
app.use(userRouter.allowedMethods());
app.listen(8000, () => {
console.log("koa路由服务器启动成功~");
});
当一些方法没有定义时,比如上面就没有注册 post 方法的中间件。如果客户端发起 post 的请求,返回的是 NOT FOUND。这不太合理。
那么就可以使用路由对象的 allowedMethods 方法。
allowedMethods 用于判断某一个 method 是否支持:
- 如果我们请求 get,那么是正常的请求,因为我们有实现 get;
- 如果我们请求 put、delete、patch,那么就自动报错:Method Not Allowed,状态码:405;
- 如果我们请求 link、copy、lock,那么就自动报错:Not Implemented,状态码:501;
请求数据处理
params 和 query
因为 koa 的 use 中没有路径的写法,所以一般是在路由中处理请求数据。
params 和 query 数据都是可以在请求对象 request 中直接取。
const Koa = require('koa');
const app = new Koa();
const Router = require('koa-router');
const userRouter = new Router({
prefix: '/users'});
userRouter.get('/:id', (ctx, next) => {
console.log(ctx.request.params);
console.log(ctx.request.query);
})
app.use(userRouter.routes());
app.listen(8000, () => {
console.log("参数处理服务器启动成功~");
});
json 和 urlencoded
koa 中 ctx.response.body 也是 undefined 无法获取到请求体中的数据。所以需要借助第三方库:koa-bodyparser
。之后就可以和 express 中一样直接在 body 中读取数据。
安装:npm i koa-bodyparser
而且它不仅可以解析 json,还可以解析 urlencoded 数据,但是无法解析 form-data。
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const app = new Koa();
app.use(bodyParser());
app.use((ctx, next) => {
console.log(ctx.request.body);
ctx.response.body = "Hello World";
});
app.listen(8000, () => {
console.log("koa初体验服务器启动成功~");
});
form-data
express 中解析 form-data 用的是 multer,koa 中当然也是用第三方库,用的是koa-multer
。
安装:npm i koa-multer
解析非文件数据
express 中不建议将 any 方法全局使用,尽量使用在 methods 中局部针对路径使用。koa 中也一样,尽量在路由中使用,koa 路由中也支持连续定义中间件。
虽然我们很少用 form-data 格式来上传普通数据,但是有一个大坑要注意:
- 对于普通的 form-data 数据,koa-multer 没有将它放入
ctx.request.body
中,而是放入了ctx.req.body
中!
req 和 request 有啥区别?
- request 是 koa 自己实现的请求对象。req 是原生 nodej 中 http 模块的请求对象。
const Koa = require('koa');
const Router = require('koa-router')
const multer = require('koa-multer')
const app = new Koa();
const router = new Router({
prefix: "/user"})
const upload = multer()
router.post("/", upload.any(), (ctx, next) => {
console.log(ctx.req.body); // [Object: null prototype] { name: 'zs', age: '18' }
console.log(ctx.request.body); // undefined
ctx.response.body = "test";
})
app.use(router.routes())
app.listen(8000, () => {
console.log("koa初体验服务器启动成功~");
});
文件上传
和 express 中使用 multer 是一样的。但是还是要注意,koa-multer 把数据全部写到 req 中了,不是 request。
const Koa = require('koa');
const Router = require('koa-router')
const multer = require('koa-multer')
const path = require("path")
const app = new Koa();
const uploadRouter = new Router({
prefix: "/user"})
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({
storage
})
uploadRouter.post("/", upload.single("pic"), (ctx, next) => {
console.log(ctx.req.file);
ctx.response.body = "test";
})
app.use(uploadRouter.routes())
app.listen(8000, () => {
console.log("koa初体验服务器启动成功~");
});
// {
// fieldname: 'pic',
// originalname: '@-?�-03.jpg',
// encoding: '7bit',
// mimetype: 'image/jpeg',
// destination: './uploads',
// filename: '1664906028636.jpg',
// path: 'uploads\\1664906028636.jpg',
// size: 214812
// }
数据的响应
输出结果:body 将响应主体设置为以下之一:
- string :字符串数据
- Buffer :Buffer数据
- Stream :流数据
- Object|| Array:对象或者数组(常用)
- null :不输出任何内容
如果response.status尚未设置,Koa会自动将状态设置为200或204。
const Koa = require('koa');
const app = new Koa();
app.use((ctx, next) => {
// 设置内容
// ctx.response.body
// ctx.response.body = "Hello world~"
// ctx.response.body = {
// name: "zs",
// age: 18,
// avatar_url: "https://abc.png"
// };
// 设置状态码
// ctx.response.status = 400;
// ctx.response.body = ["abc", "cba", "nba"];
// ctx.response.body = "Hello World~";
ctx.status = 404;
ctx.body = "Hello Koa~"; // 这种方式更简洁,实际背后还是执行 ctx.request.body
});
app.listen(8000, () => {
console.log("koa初体验服务器启动成功~");
});
静态服务器
koa并没有内置部署相关的功能,所以我们需要使用第三方库:koa-static
安装:npm i koa-static
部署的过程类似于express:
const Koa = require('koa');
const staticAssets = require('koa-static');
const app = new Koa();
app.use(staticAssets('./build'));
app.listen(8000, () => {
console.log("koa初体验服务器启动成功~");
});
错误处理
app 中也支持事务总线。所以可以以事务总线的方式进行错误处理:让 app emit 发出错误,然后使用 on 监听。
context 中也可以拿到 app 对象,所以可以使用 ctx 去拿 app 对象。
为什么不直接使用 app 对象,因为一般都是在路由中操作,路由中不好拿 app 对象。
const Koa = require('koa');
const app = new Koa();
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;
})
app.listen(8000, () => {
console.log("koa初体验服务器启动成功~");
});
koa 与 express 的区别
koa 与 express 最大的区别就在于对 next 函数的实现不同。koa 中的 next 函数返回一个 promise,而 express 的 next 函数就是一个普通的函数。
这个区别导致它们再处理异步的时候有所区别。
另外 koa 中执行下一个中间件的函数其实是叫 dispatch,只是为了沿袭 express 中的习惯,我们使用的时候以 next 做参数名了。
中间件是怎么链式执行的
koa 和 express 都一样,中间件的执行过程其实就相当于在递归调用。
如下:middleware1 中的 next 会去执行 middleware2 中的中间件函数,middleware2 中的 next 又会去执行 middleware3 中的中间件函数,最后返回到 middleware1 中。middleware1 中的 next 函数执行完毕,再继续往下执行 next 后面的代码 res.end。
const express = require('express');
const app = express();
const middleware1 = (req, res, next) => {
req.message = "aaa";
next();
res.end(req.message); // aaabbbccc
}
const middleware2 = (req, res, next) => {
req.message += "bbb";
next();
}
const middleware3 = (req, res, next) => {
req.message += "ccc";
}
app.use(middleware1, middleware2, middleware3);
app.listen(8000, () => {
console.log("服务器启动成功~");
})
异步处理
koa 和 express 一样的都是递归执行中间件,那如果中间件中发送了异步请求呢?该怎么拿到异步数据。
我们会发现,普通的中间件都是同步的,它们拿不到异步请求的结果。无论是 koa 还是 express。
const express = require('express');
const axios = require('axios');
const app = express();
const middleware1 = (req, res, next) => {
req.message = "aaa";
next();
res.end(req.message); // 结果为:aaabbb
}
const middleware2 = (req, res, next) => {
req.message += "bbb";
next();
}
const middleware3 = async (req, res, next) => {
const result = await axios.get('http://localhost:9000'); // 注:服务器将返回 ccc
req.message += result.data;
}
app.use(middleware1, middleware2, middleware3);
app.listen(8000, () => {
console.log("express 服务器启动成功~");
})
express 获取异步请求的数据
不要将异步请求放在别的中间件中,就放在需要使用请求数据的中间件中。
const express = require('express');
const axios = require('axios');
const app = express();
const middleware1 = async (req, res, next) => {
req.message = "aaa";
next();
const result = await axios.get('http://localhost:9000'); // 注:服务器将返回 ccc
res.end(req.message + result.data); // 结果为:aaabbbccc
}
const middleware2 = (req, res, next) => {
req.message += "bbb";
next();
}
// const middleware3 = async (req, res, next) => {
// const result = await axios.get('http://localhost:9000'); // 注:服务器将返回 ccc
// req.message += result.data;
// }
app.use(middleware1, middleware2, middleware3);
app.listen(8000, () => {
console.log("服务器启动成功~");
})
koa 获取异步请求的数据
因为 koa 中每个 dispatch 函数返回的都是 promise,所以我们可以连续使用 await ,将中间件全链条进行 await。就不需要像 express 中改异步请求的位置了。
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://localhost:9000');
ctx.message += result.data;
}
app.use(middleware1);
app.use(middleware2);
app.use(middleware3);
app.listen(8000, () => {
console.log("koa 服务器启动成功~");
})