596 node之koa:路由, 请求解析,响应方式/内容,错误处理,静态服务器,处理同步、异步数据

时间:2021-02-01 12:52:21   收藏:0   阅读:0

一. Koa初体验

1.1. 认识Koa

除了express,另外一个非常流行的Node Web服务器框架就是Koa。

Koa官方的介绍:

事实上,koa是express同一个团队开发的一个新的Web框架:


1.2. koa初体验

因为学习过了express,它们的基本开发模式是比较相似的。

我们来体验一下koa的Web服务器:

const Koa = require(‘koa‘);
const app = new Koa();

app.use((ctx, next) => {
  console.log("middleware 01");
  next();
})

app.use((ctx, next) => {
  console.log("middleware 02");
  ctx.response.body = "Hello World";
})

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

koa注册的中间件提供了两个参数:

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

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

方式一:根据request自己判断

一:根据request自己

整个代码的逻辑是非常复杂和混乱的,真实开发中我们会使用路由。


01_koa初体验.js

// Koa导出的是一个类
const Koa = require(‘koa‘);

const app = new Koa();

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

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

1.3. 路由的使用

koa官方并没有给我们提供路由的库,我们可以选择第三方库:koa-router

1.3.1. 安装koa-router

因为是第三方的库,所以我们需要单独下项目中安装:

npm install koa-router

1.3.2. koa-router基本使用

我们可以先封装一个 user.router.js 的文件:

app.use((ctx, next) => {
  if (ctx.request.path === ‘/users‘) {
    if (ctx.request.method === ‘POST‘) {
      ctx.response.body = "Create User Success~";
    } else {
      ctx.response.body = "Users List~";
    }
  } else {
    ctx.response.body = "Other Request Response";
  }
})

在app中将router.routes()注册为中间件:

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

注意:allowedMethods用于判断某一个method是否支持:


02_koa注册中间件.js

const Koa = require(‘koa‘);

const app = new Koa();

// use注册中间件
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~";
  }
});

// 没有提供下面的注册方式
// methods方式: app.get()/.post
// path方式: app.use(‘/home‘, (ctx, next) => {})
// 连续注册: app.use((ctx, next) => {
// }, (ctx, next) => {
// })

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

1.3.3. router的前缀

通常一个路由对象是对一组相似路径的封装,那么路径的前缀都是一直的,所以我们可以直接在创建Router时,添加前缀:

const userRouter = new Router({prefix: ‘/users‘});

userRouter.get(‘/‘, (ctx, next) => {
  ctx.response.body = "user list~";
});

userRouter.post(‘/‘, (ctx, next) => {
  ctx.response.body = "create user info~";
});

module.exports = userRouter;

03_koa中路由的使用.js

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

const app = new Koa();

app.use((ctx, next) => {
  // ctx.response.body = "Hello World";
  next(); // 如果这个中间件没有调用next,就会直接结束 【不会执行下面的2个中间件。】
});

// userRouter.routes():返回一个函数,然后注册到app.use中
app.use(userRouter.routes());
// 用于判断是否支持某一个method 【而不是返回 not found】
app.use(userRouter.allowedMethods());

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

user.js

const Router = require(‘koa-router‘);

// 前缀
const router = new Router({ prefix: "/users" });

// 写了前缀后,这里注册接口的时候,路径就不用再写成"/users"了
router.get(‘/‘, (ctx, next) => {
  ctx.response.body = "User Lists~";
});

router.put(‘/‘, (ctx, next) => {
  ctx.response.body = "put request~";
});

module.exports = router;

1.4. 请求解析

客户端传递到服务器参数的方法常见的是5种:


1.4.1. 方式一:params

请求地址:http://localhost:8000/users/123

获取params:

const userRouter = new Router({prefix: "/users"})

userRouter.get("/:id", (ctx, next) => {
  console.log(ctx.params.id);
  ctx.body = "Hello World";
})

04_参数解析-query-params.js

const Koa = require(‘koa‘);

const app = new Koa();
const Router = require(‘koa-router‘);

const userRouter = new Router({ prefix: ‘/users‘ });

// 假设请求地址是http://localhost:8888/user/haha?aa=11&bb=22,那么user后面的/haha不能丢,否则匹配不上get请求
userRouter.get(‘/:id‘, (ctx, next) => {
  console.log(ctx.request.params);
  console.log(ctx.request.query);
})

// app.use((ctx, next) => {
//   console.log(ctx.request.url);
//   console.log(ctx.request.query);
//   console.log(ctx.request.params); // undefined,没有params属性
//   ctx.response.body = "Hello World";
// });

app.use(userRouter.routes());

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

1.4.2. 方式二:query

请求地址:http://localhost:8000/login?username=why&password=123

获取query:

const userRouter = new Router({prefix: "/users"})

userRouter.get("/:id", (ctx, next) => {
  console.log(ctx.params.id);
  ctx.body = "Hello World";
})

1.4.3. 方式三:json

请求地址:http://localhost:8000/login

body是json格式:

{
    "username": "coderwhy",
    "password": "123"
}

获取json数据:

app.use(bodyParser());

app.use((ctx, next) => {
  console.log(ctx.request.body);
  ctx.body = "Hello World";
})

1.4.4. 方式四:x-www-form-urlencoded

请求地址:http://localhost:8000/login

body是x-www-form-urlencoded格式:

技术图片x-www-form-urlencoded

获取json数据:(和json是一致的)

app.use(bodyParser());

app.use((ctx, next) => {
  console.log(ctx.request.body);
  ctx.body = "Hello World";
})

1.4.5. 方式五:form-data

请求地址:http://localhost:8000/login

body是form-data格式:

技术图片form-data

解析body中的数据,我们需要使用multer

const upload = multer({
});

app.use(upload.any());

app.use((ctx, next) => {
  console.log(ctx.req.body);
  ctx.body = "Hello World";
});

我们知道multer还可以实现文件的上传:

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
});

const fileRouter = new Router();

fileRouter.post("/upload", upload.single(‘avatar‘), (ctx, next) => {
  console.log(ctx.req.file);
})

app.use(fileRouter.routes());

~

注意,postman中,body选择form-data,key栏选择file类型,值写avatar,value选则图片

![1612057099122](F:\前端\why\node\kejian\day01_24\笔记\Node.js系列十一 - koa开发web服务器.assets\1612057099122.png)

~


05_参数解析-json-urlencoded-formdata.js

const Koa = require(‘koa‘);
const bodyParser = require(‘koa-bodyparser‘); // 解析json
const multer = require(‘koa-multer‘); // 解析form-data
const Router = require(‘koa-router‘);

const app = new Koa();
const upload = multer();

app.use(bodyParser());
app.use(upload.any());

app.use((ctx, next) => {
  console.log(ctx.request.body); // 空对象{},没数据
  console.log(ctx.req.body); // 不上空对象,有数据,ject: null prototype] { name: ‘华为‘, price: ‘8888886666‘ }
  ctx.response.body = "Hello World";
});

app.listen(8001, () => {
  console.log("koa初体验服务器启动成功~");
});

06_文件上传的处理.js

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 storage = multer.diskStorage({
//   destination,
//   filename,
// })

const upload = multer({
  // 指定上传的路径
  dest: ‘./uploads/‘
});

// 注意,postman中,body选择form-data,key栏选择file类型,值写upload.single(‘avatar‘)中的avatar,value选则图片
uploadRouter.post(‘/avatar‘, upload.single(‘avatar‘), (ctx, next) => {
  console.log(ctx.req.file);
  ctx.response.body = "上传头像成功~";
});

app.use(uploadRouter.routes());

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

1.5. 响应方式/内容

输出结果:body

将响应主体设置为以下之一:

如果response.status尚未设置,Koa会自动将状态设置为200204

比较常见的输出方式:

ctx.response.body = "Hello World";
ctx.body = {
  name: "why",
  age: 18,
  height: 1.88
};
ctx.body = ["abc", "cba", "nba"];

疑惑:ctx.response.bodyctx.body之间的区别:

技术图片image-20201104155927483

请求状态:status

请求状态我们可以直接给ctx设置,或者给ctx.response设置也是一样的效果:

ctx.status = 201;
ctx.response.status = 204;

07_koa的响应内容.js

const Koa = require(‘koa‘);
const app = new Koa();

app.use((ctx, next) => {
  // ctx.request.query 等同于
  // ctx.query

  // 设置内容
  // ctx.response.body
  // ctx.response.body = "Hello world~"
  // ctx.response.body = {
  //   name: "哈哈哈",
  //   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~";
});

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

1.6. 错误处理

const Koa = require(‘koa‘);
const app = new Koa();

app.use((ctx, next) => {
  const isLogin = false;
  if (!isLogin) {
    // 【触发error事件】
    ctx.app.emit(‘error‘, new Error("您还没有登录~"), ctx);
  }
});

// 【监听error事件】
// 【注意参数的顺序,不要写成 "ctx, err"】
app.on(‘error‘, (err, ctx) => {
  ctx.status = 401;
  ctx.body = err.message;
})

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

1.7. 静态服务器

koa并没有内置部署相关的功能,所以我们需要使用第三方库:

npm install koa-static

部署的过程类似于express:

08_部署静态资源.js

const Koa = require(‘koa‘);
const staticAssets = require(‘koa-static‘);

const app = new Koa();

app.use(staticAssets(‘./build‘));

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

1.8. 源码解析

这里不再截图


二. 和express对比

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

从架构设计上来说:

因为express和koa框架他们的核心其实都是中间件:

我通过一个需求来演示所有的过程:


2.1. 同步执行顺序

假如我们获取的所有数据,是可以同步获取的;

我们先通过express实现这个过程:

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 = req.message + ‘bbb‘;
  next();
}

const middleware3 = (req, res, next) => {
  req.message = req.message + ‘ccc‘;
}

app.use(middleware1, middleware2, middleware3);

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

最终的结果是:aaabbbccc,没问题;

我们再通过koa实现这个过程:

const Koa = require(‘koa‘);

const app = new Koa();

const middleware1 = (ctx, next) => {
  ctx.message = "aaa";
  next();
  console.log("aaaa");
  ctx.body = ctx.message;
}

const middleware2 = (ctx, next) => {
  ctx.message = ctx.message + ‘bbb‘;
  console.log("bbbb");
  next();
}

const middleware3 = (ctx, next) => {
  ctx.message = ctx.message + ‘ccc‘;
}

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

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

最终的结果也是:aaabbbccc,也没问题;


2.2. 异步执行顺序

但是,如果我们最后的ccc中的结果,是需要异步操作才能获取到的,是否会产生问题呢?

2.2.1. express中遇到异步操作

express有异步操作(没有在next前,加async、await):

const middleware1 = (req, res, next) => {
  req.message = "aaa";
  next();
  res.end(req.message);
}

const middleware2 = (req, res, next) => {
  req.message = req.message + ‘bbb‘;
  next();
}

const middleware3 = async (req, res, next) => {
  const result = await axios.get(‘http://123.207.32.32:9001/lyric?id=167876‘);
  req.message = req.message + result.data.lrc.lyric;
  console.log(req.message);
}

最终的结果aaabbb,是不正确。

express有异步操作(有在next前,加async、await):

const middleware1 = async (req, res, next) => {
  req.message = "aaa";
  await next();
  res.end(req.message);
}

const middleware2 = async (req, res, next) => {
  req.message = req.message + ‘bbb‘;
  await next();
}

const middleware3 = async (req, res, next) => {
  const result = await axios.get(‘http://123.207.32.32:9001/lyric?id=167876‘);
  req.message = req.message + result.data.lrc.lyric;
  console.log(req.message);
}

最终的结果也是aaabbb,也是不正确。

为什么呢?

技术图片image-20201106175205300


01_express实现-同步数据.js

const express = require(‘express‘);
const app = express();

// async、await对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);

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

02_express实现-异步数据.js

const express = require(‘express‘);
const axios = require(‘axios‘);

const app = express();

// async、await对express无效
const middleware1 = async (req, res, next) => {
  req.message = "aaa";
  await next();
  res.end(req.message);
}

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

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

app.use(middleware1, middleware2, middleware3);

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

2.2.2. koa中遇到异步操作

koa有异步操作(没有在next前,加async、await):

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

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

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

最终的结果也是aaabbb,也是不正确。

koa有异步操作(有在next前,加async、await):

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

const middleware2 = async (ctx, next) => {
  ctx.message = 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 = ctx.message + result.data.lrc.lyric;
}

最终的结果是aaabbb+歌词信息,是正确。


03_koa实现-同步数据.js

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);

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

04_koa实现-异步数据.js

const Koa = require(‘koa‘);
const axios = require(‘axios‘);

const app = new Koa();

const middleware1 = async (ctx, next) => {
  ctx.message = "aaa";
  await next();
  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("服务器启动成功~");
})
评论(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2  联系我们:gaon5@hotmail.com
迷上了代码!