总结自 Coderwhy的nodejs课程。
官网 koa.bootcss.com/#applicatio…
Koa的介绍
- node.js的下一代web框架;
- Koa旨在为Web应用程序和API提供更小、更丰富和更强大的能力;
- 相对于express具有更强的异步处理能力;
- Koa的核心代码只有1600+行,是一个更加轻量级的框架,我们可以根据需要安装和使用中间件;
Koa初体验
const Koa = require('koa');
const app = new Koa();
app.use((ctx, next) => {
ctx.response.body = "Hello Koa";
});
app.listen(8000, () => {
console.log("koa初体验服务器启动成功~");
});
通过上面的例子,我们来介绍一下中间件中的两个参数。
- ctx: 上下文(Context)对象;
- koa并没有像express一样,将req和res分开,而是将它们作为ctx的属性
ctx.req为Node 的request对象.ctx.res为Node 的response对象.ctx.response为Koa 的response对象.ctx.request为Koa 的request对象.
- next: 终止该中间件使用,调用下一个中间件。
Koa中使用中间件
Koa中注册中间件只能通过use()来注册,而且只能传递一个中间件,不能匹配路径。
app.use((ctx, next) => {
ctx.response.body = "Hello Koa";
});
使用路由
由于koa中未给我们提供路由处理,如果想要做复杂的路由匹配,我们就需要安装第三方库来帮助我们处理路由。
安装koa-router库。
注册路由
// users.js
const Router = require('koa-router');
const userRouter = new Router({prefix: "/users"});
userRouter.get('/', (ctx, next) => {
ctx.response.body = "User Lists~";
});
userRouter.put('/', (ctx, next) => {
ctx.response.body = "put request~";
});
module.exports = userRouter;
使用路由
// index.js
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路由服务器启动成功~");
});
从上面的例子可以看出
- 当如路由文件,在app中将
router.routes()注册为中间件 router.allowedMethods(): 他是用来判断我们设置了那些请求方法。如果只设置get请求,那么我们请求post,patch,delete等方法将会报错。Method Not Allowed,状态码:405;
解析请求传递的参数
解析params参数
我们直接通过ctx.request.params就可以获取到。当未使用路由时,他不会被解析。因为他不知道怎么解析,未给出解析对应的键名。
const Koa = require('koa');
const app = new Koa();
const Router = require('koa-router');
const userRouter = new Router({ prefix: '/users' });
// http://127.0.0.1:8000/users/9?name=zh&age=20
userRouter.get('/:id', (ctx, next) => {
console.log(ctx.request.url);
console.log(ctx.request.params);
console.log(ctx.request.query);
/**
* /users/9?name=zh&age=20
* { id: '9' }
* [Object: null prototype] { name: 'zh', age: '20' }
*/
})
app.use(userRouter.routes());
app.listen(8000, () => {
console.log("参数处理服务器启动成功~");
});
解析query参数
我们直接通过ctx.request.query就可以获取到。
const Koa = require('koa');
const app = new Koa();
const Router = require('koa-router');
const userRouter = new Router({ prefix: '/users' });
// http://127.0.0.1:8000/users/9?name=zh&age=20
userRouter.get('/:id', (ctx, next) => {
console.log(ctx.request.url);
console.log(ctx.request.params);
console.log(ctx.request.query);
/**
* /users/9?name=zh&age=20
* { id: '9' }
* [Object: null prototype] { name: 'zh', age: '20' }
*/
})
app.use(userRouter.routes());
app.listen(8000, () => {
console.log("参数处理服务器启动成功~");
});
解析请求中的json格式数据
Koa没有像Express中那样提供内置的中间件,所以我们需要使用第三方库koa-bodyparser解析。将解析后的参数放在ctx.request.body上。
const bodyParser = require('koa-bodyparser');
app.use((ctx, next) => {
console.log("body===json", ctx.request.body);
ctx.response.body = "Hello World";
});
解析请求中的x-www-from-urlencoded格式数据
Koa没有像Express中那样提供内置的中间件,所以我们需要使用第三方库koa-bodyparser解析。将解析后的参数放在ctx.request.body上。
const bodyParser = require('koa-bodyparser');
app.use((ctx, next) => {
console.log("body===json", ctx.request.body);
ctx.response.body = "Hello World";
});
解析请求中的form-data格式数据
他们的用法就等同于expres中的multer使用
我们需要安装koa-multer库来帮助我们解析。这个库和Express中解析文件数据差不多。不要将它作为全局中间件来使用,上传文件和非文件的form-data格式的数据,可能会产生冲突。 并且它将数据挂载到的是nodejs中的request.body上的。通过ctx.req.body获取
解析非文件的表单数据
const Koa = require('koa');
const Router = require('koa-router');
const multer = require('koa-multer');
const app = new Koa();
const uploadRouter = new Router({prefix: '/upload'});
const upload = multer();
uploadRouter.post('/', upload.any(), (ctx, next) => {
console.log(ctx.req.body);
ctx.response.body = "解析成功~";
});
app.use(uploadRouter.routes());
app.listen(8000, () => {
console.log("koa初体验服务器启动成功~");
});
解析文件的表单数据
我们可以指定上传的文件名和后缀。也可以上系统自动分配文件名。
const Koa = require('koa');
const Router = require('koa-router');
const multer = require('koa-multer');
const app = new Koa();
const uploadRouter = new Router({prefix: '/upload'});
const uploadSingle = multer({
dest: './uploads/avatar'
});
const uploadArray = multer({
dest: './uploads/picture'
});
// 上传单个文件,而且他只能上传单文件。
uploadRouter.post('/avatar', uploadSingle.single('avatar'), (ctx, next) => {
console.log(ctx.req.file);
ctx.response.body = "上传头像成功~";
});
// 上传多个文件
uploadRouter.post('/picture', uploadArray.array('picture'), (ctx, next) => {
console.log(ctx.req.files);
ctx.response.body = "上传多文件成功~";
});
app.use(uploadRouter.routes());
app.listen(8000, () => {
console.log("koa初体验服务器启动成功~");
});
响应请求
其他方法请访问官网: koa.bootcss.com/#response
response.body: 用来响应请求的数据。- string :字符串数据
- Buffer :Buffer数据
- Stream :流数据
- Object || Array:对象或者数组
- null :不输出任何内容
- 如果response.status尚未设置,Koa会自动将状态设置为200或204。
response.type: 设置Content-Type字段response.set: 设置响应头字段。可以传入一个对象或者传入key, value。response.status: 设置状态码。
app常用方法和属性
- app.context。可以通过编辑
app.context为ctx添加其他属性。
app.context.db = db();
app.use(async ctx => {
console.log(ctx.db);
});
- app.keys。设置cookie的签名
// 这个值为数组
app.keys = ['im a newer secret'];
// 这些密钥可以倒换,并在使用 `{ signed: true }` 参数签名 Cookie 时使用。
ctx.cookies.set('name', 'tobi', { signed: true });
ctx常用方法和属性
ctx.cookies.set(name, value, [options]): 设置cookiectx.cookies.get(name, [options]): 获取cookiectx.get(): 获取请求头中的字段名ctx.body: ctx.response.body的别名。返回响应内容。ctx.status: ctx.response.status的别名。返回或者设置状态码。ctx.type: ctx.response.type的别名。返回或者设置返回值类型。ctx.set(): ctx.response.set的别名。设置响应头字段。可以设置多个字段或者单个字段
ctx.set('Cache-Control', 'no-cache');
ctx.set({
'Etag': '1234',
'Last-Modified': date
});
ctx.request对象中的简写
ctx.response对象中的简写
部署静态服务器
Koa中没有像Express中内置的中间件,需要安装第三方koa-static库。
const Koa = require('koa');
const koaStatic = require('koa-static');
const path = require("path")
const app = new Koa();
app.use(koaStatic(path.resolve(__dirname, "./static")));
app.listen(8000, () => {
console.log("koa初体验服务器启动成功~");
});
错误处理
官方可是说过有力地增强错误处理。
通过ctx.app.emit("error", 错误类型, ctx)传递错误。
通过app.on("error", (err, ctx) => {})监听错误,并做出处理。
自定义错误类型
// 用户名或者密码未输入
const USERNAME_OR_PASSWORD_NOT_INPUT = "username_or_password_not_input"
// 用户名重复
const USER_ALREADY_EXISTS = 'user_already_exists';
// 用户名错误
const USERNAME_NOT_EXISTS = 'username_not_exists';
// 密码错误
const PASSWORD_ERROR = 'password_error'
// 未携带授权token
const NOT_TAKE_AUTHORIZATION_TOKEN = "not_take_authorization_token"
// 未授权
const UNAUTHORIZATION = 'unauthorization'
// 用户没有权限
const NOT_PERMISSION = 'not_permission'
module.exports = {
USERNAME_OR_PASSWORD_NOT_INPUT,
USER_ALREADY_EXISTS,
USERNAME_NOT_EXISTS,
PASSWORD_ERROR,
NOT_TAKE_AUTHORIZATION_TOKEN,
UNAUTHORIZATION,
NOT_PERMISSION
}
处理错误的中间件函数
// errorHandle.js
const {
USERNAME_OR_PASSWORD_NOT_INPUT,
USER_ALREADY_EXISTS,
USERNAME_NOT_EXISTS,
PASSWORD_ERROR,
NOT_TAKE_AUTHORIZATION_TOKEN,
UNAUTHORIZATION,
NOT_PERMISSION
} = require("./errorType");
const errorHandle = (errorMessage, ctx) => {
let status, message;
switch (errorMessage) {
case USERNAME_OR_PASSWORD_NOT_INPUT:
status = 400; // Bad Request
message = "用户名或者密码不能为空~";
break;
case USER_ALREADY_EXISTS:
status = 409; // conflict
message = "用户名已经存在~";
break;
case USERNAME_NOT_EXISTS:
status = 400; // 参数错误
message = "用户名不存在~";
break;
case PASSWORD_ERROR:
status = 400; // 参数错误
message = "密码是错误的~";
break;
case NOT_TAKE_AUTHORIZATION_TOKEN:
status = 401; // 参数错误
message = "未携带token~";
break;
case UNAUTHORIZATION:
status = 401; // 参数错误
message = "无效的token~";
break;
case NOT_PERMISSION:
status = 401; // 参数错误
message = "您不具备操作的权限~";
break;
default:
status = 404;
message = "NOT FOUND";
}
ctx.status = status;
ctx.body = message;
}
module.exports = errorHandle
注册错误中间件
const errorHandle = require("./errorHandle");
app.on("error", errorHandle)
Koa的源码分析
我们调用 new Koa()其实就是在内部调用Application类的构造函数。其中有一个middleware的数组用于存储中间件。
我们通过app.listen()来开启服务,底层其实就是调用nodejs中的listen方法
注册中间件时,调用use方法将中间件加入到middleware数组中。
通过调用callback函数来返回响应
并且通过compose函数来处理中间件的调用
处理完中间件后,我们将中间件传入到handleRequest函数中,来做出响应。
Express和Koa的区别
从架构设计上来说:
- express是完整和强大的,其中帮助我们内置了非常多好用的功能;
- koa是简洁和自由的,它只包含最核心的功能,并不会对我们使用其他中间件进行任何的限制。甚至是在app中连最基本的get、post都没有给我们提供;我们需要通过自己或者路由来判断请求方式或者其他功能;
中间件执行的差异
express和koa框架他们的核心其实都是中间件:它们的中间件的执行机制是不同的,特别是针对某个中间件中包含异步操作时;
所以,接下来,我们再来研究一下express和koa中间件的执行顺序问题;
情景介绍
-
在middleware1中,在req.message中添加一个字符串 aaa;
-
在middleware2中,在req.message中添加一个 字符串bbb;
-
在middleware3中,在req.message中添加一个 字符串ccc;
-
当所有内容添加结束后,在middleware1中,通过res返回最终的结果; express的中间件没有在异步操作中拼接字符串,然后在第一个中间件中返回响应结果req.message。将会是aaabbbccc。如果有异步操作拼接字符串(假设ccc在异步操作中拼接的), 那么将会返回aaabbb。这是因为express调用中间件的时候,执行到最后一个中间件时,他不会等待异步操作执行完毕。就会返回到上一个中间件未执行完的代码。
Koa的中间件中执行同步拼接的时候,产生的结果和express相同。我们可以通过将中间件设置为async函数,来让next()后的代码异步操作,就可以正确返回aaabbbccc。