1 koa的基本使用
2 koa的参数解析
3 koa响应和错误
4 koa静态服务器
5 koa的源码解析
6 和express对比
koa的基本使用过程
const Koa = require('koa') // 创建app对象 const app = new Koa() // 注册中间件(middleware) // koa的中间件有两个参数: ctx/next app.use((ctx, next) => { console.log('匹配到koa的中间件') // 向客户端发送消息 ctx.body = '哈哈哈哈哈' }) // 启动服务器 app.listen(6000, () => { console.log('koa服务器启动成功~') })
koa中ctx参数的解析
ctx包含了请求对象和响应对象
注意区分ctx.req和ctx.request;
区分ctx.res和ctx.response.
还包括其他的属性,比如ctx.query和ctx.params等等。
next()和express的用法差不多
const Koa = require('koa') // 创建app const app = new Koa() // 中间件 app.use((ctx, next) => { // 1.请求对象 console.log(ctx.request) // 请求对象: Koa封装的请求对象 console.log(ctx.req) // 请求对象: Node封装的请求对象 // 2.响应对象 console.log(ctx.response) // 响应对象: Koa封装的响应对象 console.log(ctx.res) // 响应对象: Node封装的响应对象 // 3.其他属性 console.log(ctx.query) // console.log(ctx.params) next() }) app.use((ctx, next) => { console.log('second middleware~') }) // 启动服务器 app.listen(6000, () => { console.log('koa服务器启动成功~') })
koa区分路径和方式
在koa里面没有像express里面的app.get()和app.post()这样子的写法,只有app.use()。
并且也没有像express里面的app.get("/home"(){})和app.post("/home"(){})这样子的写法,koa需要手动判断路径,ctx.path可以拿到客户端访问服务器时候的路径是什么。
通过ctx.method可以拿到客户端访问服务器时候采用的是什么方式。
这种情况多了是肯定不方便也很繁琐,解决办法是使用路由。看下面一节的代码。
const Koa = require('koa') // 创建app const app = new Koa() // 中间件: path/method使用路由 app.use((ctx, next) => { if (ctx.path === '/users') { if (ctx.method === 'GET') { ctx.body = 'user data list' } else if (ctx.method === 'POST') { ctx.body = 'create user success~' } } else if (ctx.path === '/home') { ctx.body = 'home data list~' } else if (ctx.path === '/login') { ctx.body = '登录成功, 欢迎回来~' } }) // 启动服务器 app.listen(6000, () => { console.log('koa服务器启动成功~') })
koa路由的使用方法
下载对应的koa的路由
npm install @koa/router
下面这段代码是没有封装路由的写法:
const Koa = require('koa') const KoaRouter = require('@koa/router') // 创建服务器app const app = new Koa() // 路由的使用 // // 1.创建路由对象 /users对应的是后续路由的主要路径,即localhost:6000/users // const userRouter = new KoaRouter({ prefix: '/users' }) // // 2.在路由中注册中间件: path/method // userRouter.get('/', (ctx, next) => { // ctx.body = 'users list data~' // }) // userRouter.get('/:id', (ctx, next) => { // const id = ctx.params.id // ctx.body = '获取某一个用户' + id // }) // userRouter.post('/', (ctx, next) => { // ctx.body = '创建用户成功~' // }) // userRouter.delete('/:id', (ctx, next) => { // const id = ctx.params.id // ctx.body = '删除某一个用户' + id // }) // userRouter.patch('/:id', (ctx, next) => { // const id = ctx.params.id // ctx.body = '修改某一个用户' + id // }) // 3.让路由中的中间件生效 app.use(userRouter.routes()) // 这个是用来提示客户端除了上面有的请求方式,其他的都是没有封装的方法,不能使用 app.use(userRouter.allowedMethods()) // 启动服务器 app.listen(6000, () => { console.log('koa服务器启动成功~') })
下面的写法是针对封装了路由的:
userRouter的代码:
const KoaRouter = require('@koa/router')
// 1.创建路由对象 /users对应的是后续路由的主要路径,即localhost:6000/users
const userRouter = new KoaRouter({ prefix: '/users' })
// 2.在路由中注册中间件: path/method
userRouter.get('/', (ctx, next) => {
ctx.body = 'users list data~'
})
userRouter.get('/:id', (ctx, next) => {
const id = ctx.params.id
ctx.body = '获取某一个用户' + id
})
userRouter.post('/', (ctx, next) => {
ctx.body = '创建用户成功~'
})
userRouter.delete('/:id', (ctx, next) => {
const id = ctx.params.id
ctx.body = '删除某一个用户' + id
})
module.exports = userRouter
const Koa = require('koa') const userRouter = require('./router/userRouter') // 创建服务器app const app = new Koa() // 3.让路由中的中间件生效 app.use(userRouter.routes()) app.use(userRouter.allowedMethods()) // 启动服务器 app.listen(6000, () => { console.log('koa服务器启动成功~') })
koa中参数解析方式
解析get的两种数据是内置就能解决的,但是队与post的json格式的数据还得下载第三方的库:
npm install koa-bodyparser
这个库能解析json和urlencoded的消息体数据,但是不能成立formdata的数据 ,这个时候用到另外一个第三方库:
解析formdata的路径的路由才有必要使用这个解析,所以解析的方式是写在对应路由的中间件中
npm install @koa-multer multer
userRouter.post('/formdata', formParser.any(), (ctx, next) => {
console.log(ctx.request.body)
ctx.body = '用户的formdata信息'
})
const Koa = require('koa') const KoaRouter = require('@koa/router') const bodyParser = require('koa-bodyparser') const multer = require('@koa/multer') // 创建app对象 const app = new Koa() // 使用第三方中间件解析body数据 app.use(bodyParser()) const formParser = multer() // 注册路由对象 /users对应的是后续路由的主要路径,即localhost:6000/users const userRouter = new KoaRouter({ prefix: '/users' }) /** * 1.get: params方式, 例子:/:id * 2.get: query方式, 例子: ?name=why&age=18 * 3.post: json方式, 例子: { "name": "why", "age": 18 } * 4.post: x-www-form-urlencoded * 5.post: form-data */ // 1.get/params userRouter.get('/:id', (ctx, next) => { const id = ctx.params.id ctx.body = 'user list data~:' + id }) // 2.get/query userRouter.get('/', (ctx, next) => { const query = ctx.query console.log(query) ctx.body = '用户的query信息' + JSON.stringify(query) }) // 3.post/json(使用最多) userRouter.post('/json', (ctx, next) => { // 注意事项: 不能从ctx.body中获取数据 //第三方库解析的json内容就是放到ctx.request.body里面, //ctx.req.body在这里是underfine console.log(ctx.request.body, ctx.req.body) // ctx.body用于向客户端返回数据 ctx.body = '用户的json信息' }) // 4.post/urlencoded userRouter.post('/urlencoded', (ctx, next) => { //这个第三方库解析的urlencoded内容就是放到ctx.request.body里面, console.log(ctx.request.body) ctx.body = '用户的urlencoded信息' }) app.use(userRouter.routes()) app.use(userRouter.allowedMethods()) // 5.post/form-data userRouter.post('/formdata', formParser.any(), (ctx, next) => { console.log(ctx.request.body) ctx.body = '用户的formdata信息' }) // 启动服务器 app.listen(6000, () => { console.log('koa服务器启动成功~') })
koa中文件上传方式
const Koa = require('koa') const KoaRouter = require('@koa/router') const multer = require('@koa/multer') // 创建app对象 const app = new Koa() // const upload = multer({ // dest: './uploads' // }) // 给上传文件解析到 ./uploads文件夹里面并且添加文件后缀 const upload = multer({ storage: multer.diskStorage({ destination(req, file, cb) { cb(null, './uploads') }, filename(req, file, cb) { cb(null, Date.now() + "_" + file.originalname) } }) }) // 注册路由对象 /upload对应的是后续路由的主要路径,即localhost:6000/uploads const uploadRouter = new KoaRouter({ prefix: '/upload' }) // 当文件的上传 uploadRouter.post('/avatar', upload.single('avatar'), (ctx, next) => { console.log(ctx.request.file) ctx.body = '文件上传成功~' }) //多文件上传 uploadRouter.post('/photos', upload.array('photos'), (ctx, next) => { console.log(ctx.request.files) ctx.body = '文件上传成功~' }) app.use(uploadRouter.routes()) app.use(uploadRouter.allowedMethods()) // 启动服务器 app.listen(6000, () => { console.log('koa服务器启动成功~') })
koa中部署静态资源
需要下载对应的第三方包:
npm install koa-static
使用之后可以给客户端提供图片地址,比如:http://localhost:8000/uploads/XXXX.jpg
const Koa = require('koa') const static = require('koa-static') const app = new Koa() // app.use((ctx, next) => { // ctx.body = "哈哈哈哈" // }) app.use(static('./uploads')) app.use(static('./build')) app.listen(8000, () => { console.log('koa服务器启动成功~') })
koa响应结果的方式
const fs = require('fs') const Koa = require('koa') const KoaRouter = require('@koa/router') // 创建app对象 const app = new Koa() // 注册路由对象 const userRouter = new KoaRouter({ prefix: '/users' }) userRouter.get('/', (ctx, next) => { // 1.body的类型是string // ctx.body = 'user list data~' // 2.body的类型是Buffer // ctx.body = Buffer.from('你好啊, 李银河~') // 3.body的类型是Stream // const readStream = fs.createReadStream('./uploads/1668331072032_kobe02.png') // ctx.type = 'image/jpeg' // ctx.body = readStream // 4.body的类型是数据(array/object) => 使用最多 ctx.status = 201 ctx.body = { code: 0, data: [ { id: 111, name: 'iphone', price: 100 }, { id: 112, name: 'xiaomi', price: 990 }, ] } // 5.body的值是null, 自动设置http status code为204 // ctx.body = null }) app.use(userRouter.routes()) app.use(userRouter.allowedMethods()) // 启动服务器 app.listen(6000, () => { console.log('koa服务器启动成功~') })
koa的错误处理方案
const Koa = require('koa') const KoaRouter = require('@koa/router') // 创建app对象 const app = new Koa() // 注册路由对象 const userRouter = new KoaRouter({ prefix: '/users' }) userRouter.get('/', (ctx, next) => { const isAuth = false if (isAuth) { ctx.body = 'user list data~' } else { // ctx.body = { // code: -1003, // message: '未授权的token, 请检测你的token' // } // EventEmitter ctx.app.emit('error', -1003, ctx) } }) app.use(userRouter.routes()) app.use(userRouter.allowedMethods()) // 独立的文件: error-handle.js app.on('error', (code, ctx) => { const errCode = code let message = '' switch (errCode) { case -1001: message = '账号或者密码错误~' break case -1002: message = '请求参数不正确~' break case -1003: message = '未授权, 请检查你的token信息' break } const body = { code: errCode, message } ctx.body = body }) // 启动服务器 app.listen(6000, () => { console.log('koa服务器启动成功~') })
koa和express对比
这里提前先说一点:在中间件中遇到next()就会马上跳到下一个中间件中执行中间件的内容;这个时候前一个中间件的next()后面如果还有一段代码的话就暂时先不执行了,等后续中间件没有next()了会以洋葱模型的方式回来继续执行每一个中间件的next()后面的一段代码。入下图显示的中间件。但是express 的异步情况不满足洋葱模型。
express中间件-执行同步
代码的执行顺序是
1、console.log('koa middleware01') ctx.msg = 'aaa';2、aaa中的next() ; 3、console.log('koa middleware02') ctx.msg += 'bbb';4、bbb中的next()
4、console.log('koa middleware03') ctx.msg += 'ccc' ; ;5、回到ccc看看有没有执行的代码; 6、回到aaa中的剩余代码ctx.body = ctx.msg ;。
最后向客户端发送的是aaabbbccc。
res.json(aaabbbccc)
const express = require('express') // 创建app对象 const app = express() // 编写中间件 app.use((req, res, next) => { console.log('express middleware01') req.msg = 'aaa' next() // 返回值结果 res.json(req.msg) }) app.use((req, res, next) => { console.log('express middleware02') req.msg += 'bbb' next() }) app.use((req, res, next) => { console.log('express middleware03') req.msg += 'ccc' }) // 启动服务器 app.listen(9000, () => { console.log('express服务器启动成功~') })
express中间件-执行异步
这里的执行顺序是1、console.log('koa middleware01') ctx.msg = 'aaa';2、aaa中的next() ; 3、console.log('koa middleware02') ctx.msg += 'bbb';4、回到aaa中的剩余代码ctx.body = ctx.msg ;。
这里 res.json(req.msg)最后向客户端发送的是aaabbb
const express = require('express') const axios = require('axios') // 创建app对象 const app = express() // 编写中间件 app.use((req, res, next) => { console.log('express middleware01') req.msg = 'aaa' next() // 返回值结果 res.json(req.msg) }) app.use((req, res, next) => { console.log('express middleware02') req.msg += 'bbb' next() }) // 执行异步代码 app.use(async (req, res, next) => { console.log('express middleware03') const resData = await axios.get('http://123.207.32.32:8000/home/multidata') req.msg += resData.data.data.banner.list[0].title // 只能在这里返回结果 }) // 启动服务器 app.listen(9000, () => { console.log('express服务器启动成功~') })
express像koa一样把所有中间件变成异步的也不能做到像koa一样执行顺序可以回到第一步。
下面代码的res.json(req.msg)结果还是aaabbb
const express = require('express') const axios = require('axios') // 创建app对象 const app = express() // 编写中间件 app.use(async (req, res, next) => { console.log('express middleware01') req.msg = 'aaa' await next() // 返回值结果 res.json(req.msg) }) app.use(async (req, res, next) => { console.log('express middleware02') req.msg += 'bbb' await next() }) // 执行异步代码 app.use(async (req, res, next) => { console.log('express middleware03') const resData = await axios.get('http://123.207.32.32:8000/home/multidata') req.msg += resData.data.data.banner.list[0].title // 只能在这里返回结果 }) // 启动服务器 app.listen(9000, () => { console.log('express服务器启动成功~') })
使用expree解决异步请求的办法就是在最后一个中间件res.json(req.msg)再输出客户端数据。顺序和同步一样
const express = require('express') const axios = require('axios') // 创建app对象 const app = express() // 编写中间件 app.use(async (req, res, next) => { console.log('express middleware01') req.msg = 'aaa' await next() }) app.use(async (req, res, next) => { console.log('express middleware02') req.msg += 'bbb' await next() }) // 执行异步代码 app.use(async (req, res, next) => { console.log('express middleware03') const resData = await axios.get('http://123.207.32.32:8000/home/multidata') req.msg += resData.data.data.banner.list[0].title // 只能在这里返回结果 res.json(req.msg) }) // 启动服务器 app.listen(9000, () => { console.log('express服务器启动成功~') })
koa中间件-执行同步
这里执行的结果是ctx.body = aaabbbccc返回给客户端数据是aaabbbccc。
执行顺序是先执行
1、console.log('koa middleware01') ctx.msg = 'aaa';2、aaa中的next() ; 3、console.log('koa middleware02') ctx.msg += 'bbb';4、bbb中的next()
4、console.log('koa middleware03') ctx.msg += 'ccc' ; ;5、回到ccc看看有没有执行的代码; 6、回到aaa中的剩余代码ctx.body = ctx.msg ;。
这种执行顺序又叫洋葱模型。
const Koa = require('koa') const KoaRouter = require('@koa/router') // 创建app对象 const app = new Koa() // 注册中间件 app.use((ctx, next) => { console.log('koa middleware01') ctx.msg = 'aaa' next() // 返回结果 ctx.body = ctx.msg }) app.use((ctx, next) => { console.log('koa middleware02') ctx.msg += 'bbb' next() }) app.use((ctx, next) => { console.log('koa middleware03') ctx.msg += 'ccc' }) // 启动服务器 app.listen(6000, () => { console.log('koa服务器启动成功~') })
koa中间件-执行异步
比如执行的中间件中有异步请求的代码,axios的请求。
下面这段代码有异步的中间件时,执行顺序是:1、console.log('koa middleware01') ctx.msg = 'aaa';2、aaa中的next();3、console.log('koa middleware02') ctx.msg += 'bbb' ;4、 aaa中的剩余代码ctx.body = ctx.msg ; 。 这里 最后向客户端发送的是aaabbb
const Koa = require('koa') const axios = require('axios') // 创建app对象 const app = new Koa() // 注册中间件 // 1.koa的中间件1 app.use( (ctx, next) => { console.log('koa middleware01') ctx.msg = 'aaa' next() // 返回结果 ctx.body = ctx.msg }) // 2.koa的中间件2 app.use( (ctx, next) => { console.log('koa middleware02') ctx.msg += 'bbb' // 如果执行的下一个中间件是一个异步函数, 那么next默认不会等到中间件的结果, 就会执行下一步操作 // 如果我们希望等待下一个异步函数的执行结果, 那么需要在next函数前面加await next() console.log('----') }) // 3.koa的中间件3 app.use(async (ctx, next) => { console.log('koa middleware03') // 网络请求 const res = await axios.get('http://123.207.32.32:8000/home/multidata') ctx.msg += res.data.data.banner.list[0].title }) // 启动服务器 app.listen(6000, () => { console.log('koa服务器启动成功~') })
所以,如果想要使得有异步中间件的情况下还是能够像同步那样都执行完中间件之后在返回结果的办法就是全部中间件转成异步的。最后向客户端发送的是aaabbbXXXX,顺序和同步一样
const Koa = require('koa') const axios = require('axios') // 创建app对象 const app = new Koa() // 注册中间件 // 1.koa的中间件1 app.use(async (ctx, next) => { console.log('koa middleware01') ctx.msg = 'aaa' await next() // 返回结果 ctx.body = ctx.msg }) // 2.koa的中间件2 app.use(async (ctx, next) => { console.log('koa middleware02') ctx.msg += 'bbb' // 如果执行的下一个中间件是一个异步函数, 那么next默认不会等到中间件的结果, 就会执行下一步操作 // 如果我们希望等待下一个异步函数的执行结果, 那么需要在next函数前面加await await next() console.log('----') }) // 3.koa的中间件3 app.use(async (ctx, next) => { console.log('koa middleware03') // 网络请求 const res = await axios.get('http://123.207.32.32:8000/home/multidata') ctx.msg += res.data.data.banner.list[0].title }) // 启动服务器 app.listen(6000, () => { console.log('koa服务器启动成功~') })