Node高级进阶六-koa框架

1、认识Koa

  • 前面我们已经学习了express,另外一个非常流行的Node Web服务器框架就是Koa。
  • Koa官方的介绍:
  • koa:next generation web framework for node.js;
  • koa:node.js的下一代web框架;
  • 事实上,koa是express同一个团队开发的一个新的Web框架:
  • 目前团队的核心开发者TJ的主要精力也在维护Koa,express已经交给团队维护了;
  • Koa旨在为Web应用程序和API提供更小、更丰富和更强大的能力;
  • 相对于express具有更强的异步处理能力(后续我们再对比);
  • Koa的核心代码只有1600+行,是一个更加轻量级的框架;
  • 我们可以根据需要安装和使用中间件;
  • 事实上学习了express之后,学习koa的过程是很简单的;

2、Koa的基本使用

  • 我们来体验一下koa的Web服务器,创建一个接口。
  • koa也是通过注册中间件来完成请求操作的;
  • koa注册的中间件提供了两个参数:
  • ctx:上下文(Context)对象;
  • koa并没有像express一样,将req和res分开,而是将它们作为ctx的属性;
  • ctx代表一次请求的上下文对象;
  • ctx.request:获取请求对象;
  • ctx.response:获取响应对象;
  • next:本质上是一个dispatch,类似于之前的next;
# 初始化项目
npm init
# 安装koa
npm install 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服务器启动成功');
})

2.1、cxt参数的基本解析

const Koa = require('koa')

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服务器启动成功');
})

3、Koa中间件

  • koa通过创建的app对象,注册中间件只能通过use方法:
  • Koa并没有提供methods的方式来注册中间件;
  • 也没有提供path中间件来匹配路径;
  • 但是真实开发中我们如何将path和method分离呢?
  • 方式一:根据request自己来判断;
  • 方式二:使用第三方路由中间件;

3.1、区分path和method的方式一-根据request自己来判断

const Koa = require('koa')

// 创建app对象
const app = new Koa()

// 注册中间件(middleware)
// koa的中间件有两个参数:ctx、next
app.use((ctx, next) => {
    if (ctx.path === '/users') {
        if (ctx.method === 'GET') {
            ctx.body = 'get user data'
        } else if (ctx.method === 'POST') {
            ctx.body = 'post user data'
        }
    } else if (ctx.path === '/home') {
        ctx.body = 'home data'
    } else if (ctx.path === '/login') {
        ctx.body = 'login data'
    }
})

// 启动服务器
app.listen(6000, () => {
    console.log('koa服务器启动成功');
})

3.2、区分path和method的方式一-使用第三方路由中间件

  • koa官方并没有给我们提供路由的库,我们可以选择第三方库:koa-router
npm install @koa/router
  • 我们可以先封装一个 user.router.js 的文件:
  • 在app中将router.routes()注册为中间件:
  • 注意:allowedMethods用于判断某一个method是否支持:
  • 如果我们请求method为 get,那么是正常的请求,因为我们有实现get;
  • 如果我们请求method为 put、patch,那么就自动报错:Method Not Allowed,状态码:405;

router/userRouter.js:

const KoaRouter = require('@koa/router')

// 1、创建路由对象
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

app.js:

const Koa = require('koa')
const userRouter = require('./router/user.router')

// 创建app对象
const app = new Koa()

// 3、让路由中的中间件生效
app.use(userRouter.routes())
app.use(userRouter.allowedMethods())

// 启动服务器
app.listen(6000, () => {
    console.log('koa服务器启动成功');
})

4、参数解析

4.1、参数解析:params - query

  • 请求地址:http://localhost:6000/users/123
  • 获取params
// ...其他代码省略

const userRouter = new KoaRouter({ prefix: '/users' })

userRouter.get('/:id', (ctx, next) => {
    console.log(ctx.request.params);
    console.log(ctx.params);
    const id = ctx.params.id
    ctx.body = '获取某一个用户' + id
})
  • 请求地址:http://localhost:8000/login?username=why&password=123
  • 获取query
// ...其他代码省略

const userRouter = new KoaRouter({ prefix: '/users' })

userRouter.get('/', (ctx, next) => {
    console.log(ctx.request.query);
    console.log(ctx.query);
    ctx.body = 'users list data'
})

4.2、参数解析:json

  • 请求地址:http://localhost:6000/login

  • body是json格式:
    在这里插入图片描述

  • 获取json数据:

  • 安装依赖: npm install koa-bodyparser;
  • 使用 koa-bodyparser的中间件;
const Koa = require('koa')
const bodyParser = require('koa-bodyparser')

// 创建app对象
const app = new Koa()

// 使用中间件
app.use(bodyParser())

app.use((ctx, next) => {
    // 注意事项: 不能从ctx.body中获取数据
    console.log(ctx.request.body)
    ctx.body = '哈哈哈哈哈'
})

// 启动服务器
app.listen(6000, () => {
    console.log('koa服务器启动成功');
})

4.3、参数解析:x-www-form-urlencoded

  • 请求地址:http://localhost:6000/login
  • body是x-www-form-urlencoded格式:

在这里插入图片描述

  • 获取json数据:(和上面4.2是一致的)
  • 安装依赖: npm install koa-bodyparser;
  • 使用 koa-bodyparser的中间件;
const Koa = require('koa')
const bodyParser = require('koa-bodyparser')

// 创建app对象
const app = new Koa()

// 使用中间件
app.use(bodyParser())

app.use((ctx, next) => {
    // 注意事项: 不能从ctx.body中获取数据
    console.log(ctx.request.body)
    ctx.body = '哈哈哈哈哈'
})

// 启动服务器
app.listen(6000, () => {
    console.log('koa服务器启动成功');
})

4.4、参数解析:form-data

  • 请求地址:http://localhost:6000/login
  • body是form-data格式
  • 解析body中的数据,我们需要使用multer
  • 安装依赖:npm install koa-multer;
  • 或者使用 @koa/multer
npm i @koa/multer multer
  • 使用 multer中间件;
// ...其他代码省略
const KoaRouter = require('@koa/router')
const multer = require('@koa/multer')

// 1、创建路由对象
const userRouter = new KoaRouter({ prefix: '/users' })

// 使用第三方中间件
const formParser = multer()

// post/form-data
userRouter.post('/formdata', formParser.any(), (ctx, next) => {
    console.log(ctx.request.body);
    ctx.body = '用户的formdata信息'
})

5、Multer上传文件

const Koa = require('koa')
const KoaRouter = require('@koa/router')
const multer = require('@koa/multer')

// 创建app对象
const app = new Koa()

const upload = multer({
    storage: multer.diskStorage({
        destination(req, file, cb) {
            // 注意:当前目录下需已存在uploads文件夹,否则会报错
            cb(null, './uploads')
        },
        filename(req, file, cb) {
            cb(null, file.originalname)
        }
    })
})

// 注册路由对象
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服务器启动成功');
})

6、Koa部署静态资源

  • koa并没有内置部署相关的功能,所以我们需要使用第三方库:
npm install koa-static
  • 部署的过程类似于express:
const Koa = require('koa')
const static = require('koa-static')

// 创建app对象
const app = new Koa()

app.use(static('./uploads'))

// 启动服务器
app.listen(9000, () => {
    console.log('koa服务器启动成功');
})

备注:如果遇到通过浏览器访问localhost:6000不通的情况,原因如下:使用用chrom浏览器访问6000端口

7、数据的响应

  • 输出结果:body将响应主体设置为以下之一:
  • string :字符串数据
  • Buffer :Buffer数据
  • Stream :流数据
  • Object|| Array:对象或者数组
  • null :不输出任何内容
  • 如果response.status尚未设置,Koa会自动将状态设置为200或204。
  • 请求状态:status
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('hello world')

    // 3、body的类型是Stream
    // const readStresm = fs.createReadStream('./uploads/1.jpg')
    // ctx.type = 'image/jpeg'
    // ctx.body = readStresm

    // 4、body的类型是数组、对象(array/object) => 使用最多
    // ctx.status = 201 // 或者ctx.response.status = 201
    // ctx.body = {
    //     code: 0,
    //     data: [
    //         {
    //             id: 1, name: 'dd',
    //             id: 2, name: 'aa'
    //         }
    //     ]
    // }

    // 5、body的值是null,自动设置http status code为204
    ctx.body = null

})

app.use(userRouter.routes())
app.use(userRouter.allowedMethods())

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

8、Koa的错误处理方案

app.js:

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 {
        // 发送错误事件error
        ctx.app.emit('error', -1003, ctx)
    }
    
})

app.use(userRouter.routes())
app.use(userRouter.allowedMethods())
// 导出app
module.exports = app

utils/handle-error.js:

// 引用app
const app = require('../app')
// 监听错误:code、ctx为发送error事件时传递的参数
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;
        default:
            message = '未知错误'
            break;
    }

    const body = {
        code: errCode,
        message
    }

    ctx.body = body
})

main.js:

const app = require('./app')
require('./utils/handle-error')


// 启动服务器
app.listen(6000, () => {
    console.log('koa服务器启动成功');
})

9、和express对比

  • 在学习了两个框架之后,我们应该已经可以发现koa和express的区别:

9.1、架构上的区别

  • 从架构设计上来说:
  • express是完整和强大的,其中帮助我们内置了非常多好用的功能;
  • koa是简洁和自由的,它只包含最核心的功能,并不会对我们使用其他中间件进行任何的限制。
  • 甚至是在app中连最基本的get、post都没有给我们提供;
  • 我们需要通过自己或者路由来判断请求方式或者其他功能;
  • 因为express和koa框架他们的核心其实都是中间件:
  • 但事实上,它们的中间件的执行机制是不同的,特别是针对某个中间件中包含异步操作时;
  • 所以,接下来,我们再来研究一下express和koa中间件的执行顺序问题;

9.2、中间件的区别

9.2.1、koa执行同步代码

先执行中间件next()前的代码,遇到next(),会去执行下一个中间件,直到执行完所有中间件next()前的代码。然后从下往上,依次执行中间件next()后面的代码。

  • 看最后ctx.msg的值是什么?
const Koa = require('koa')

// 创建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()
    ctx.msg += '02'
})

app.use((ctx, next) => {
    console.log('koa middleware03');
    ctx.msg += 'ccc'
})

// 启动服务器
app.listen(6000, () => {
    console.log('koa服务器启动成功');
})

9.2.2、koa执行异步代码

默认是不会等待异步中间件执行完成的,可以在next()前面加上await的方式等待异步中间件执行完成。

  • 看最后ctx.msg的值是什么?
const Koa = require('koa')

// 创建app对象
const app = new Koa()

app.use(async (ctx, next) => {
    console.log('koa middleware01');
    ctx.msg = 'aaa'
    await next()

    // 返回结果
    ctx.body = ctx.msg
})

app.use(async (ctx, next) => {
    console.log('koa middleware02');
    ctx.msg += 'bbb'
    await next()
    ctx.msg += '02'
})

app.use(async (ctx, next) => {
    console.log('koa middleware03');
    // 模拟网络请求
    const res = await new Promise((resolve) => {
        setTimeout(() => {
            resolve('ccc')
        }, 2000)
    })
    ctx.msg += res
})

// 启动服务器
app.listen(6000, () => {
    console.log('koa服务器启动成功');
})

9.2.3、express执行同步代码

顺序跟koa执行同步代码一样

const express = require('express')

const app = express()

app.use((req, res, next) => {
    console.log('normal middleware 01');
    req.msg = 'aaa'
    next();
    // 返回结果
    res.json(req.msg)
})

app.use((req, res, next) => {
    console.log('normal middleware 02');
    req.msg += 'bbb'
    next();
    req.msg += '02'
})

app.use((req, res, next) => {
    console.log('normal middleware 03');
    req.msg += 'ccc'
})


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

9.2.4、express执行异步代码

不能像koa那样在next()前面加上await的方式等待下个中间件的异步执行结果,要给客户端返回数据的话只能在最后执行的中间件里面返回。
原因:express框架本身就不支持这样做,express的next()方法返回值是void,koa的next()返回值是promise,所以koa框架才支持通过在next()前加上await的方式等待异步代码的执行结果。

const express = require('express')

const app = express()

app.use(async (req, res, next) => {
    console.log('normal middleware 01');
    req.msg = 'aaa'
    await next();
})

app.use(async (req, res, next) => {
    console.log('normal middleware 02');
    req.msg += 'bbb'
    await next();
    console.log('normal middleware 02 end');
    req.msg += '02'
})

app.use(async (req, res, next) => {
    console.log('normal middleware 03');
    // 模拟网络请求
    const resData = await new Promise((resolve) => {
        setTimeout(() => {
            resolve('ccc')
        }, 2000)
    })
    req.msg += resData

    console.log('normal middleware 03 end');

    // 返回结果
    res.json(req.msg)
})


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

9.2.5、引出Koa的洋葱模型

  • 两层理解含义:
  • 中间件处理代码的过程;
  • response返回body执行顺序;

在这里插入图片描述

  • express在执行同步代码的时候也是符合洋葱模型的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值