Express && Koa

Express

经过 express 咱们能够轻松的构建一个 web 服务器 例如如下代码就在 3000 端口建立了一个 web 服务器css

const express = require("express");

const app = express();

app.listen(3000, () => {
  console.log("start");
});
复制代码

在个人理解中 express 就是一个对一系列中间件调用的函数html

好比常见的处理 get 和 post 请求的方法都是中间件的调用node

当咱们 require express 的时候 本质上是导入了一个函数ios

能够查看源码 如下是入口文件 express.js 的截取代码web

exports = module.exports = createApplication;

/** * Create an express application. * * @return {Function} * @api public */

function createApplication() {
  var app = function (req, res, next) {
    app.handle(req, res, next);
  };

  mixin(app, EventEmitter.prototype, false);
  mixin(app, proto, false);

  // expose the prototype that will get set on requests
  app.request = Object.create(req, {
    app: { configurable: true, enumerable: true, writable: true, value: app },
  });

  // expose the prototype that will get set on responses
  app.response = Object.create(res, {
    app: { configurable: true, enumerable: true, writable: true, value: app },
  });

  app.init();
  return app;
}
复制代码

express.js 导出了一个函数 createApplication 也就是咱们 require 导入的函数express

在这个函数中初始化了一个 app 的函数 拥有 req / res / next 等参数 并经过 minin 这个函数混入了不少属性到 app 中json

具体的 API 可查阅 expressjs.com/en/4x/api.h…axios

中间件

传递给 express 的一个回调函数 中间件位于请求和响应之间 因此它能够api

• 执行任何代码服务器

• 更改请求和响应对象

• 结束请求响应周期 res.end

• 调用下一个中间件

咱们可使用 app.use 的方法在全局注册中间件 这样全部的路由都会匹配到这个中间件

也能够在具体的路由中使用中间件 例如以下

const middlewareA = ((req,res,next)=>{
  do something...
})

app.use(middlewareA)

app.get('/', middlewareA ,(req,res,next)=>{
  do something...
})
复制代码

托管静态文件

const express = require("express");
const path = require("path");

const app = express();

// 经过/static 来访问public文件夹
app.use("/static", express.static(path.join(__dirname, "public")));

app.listen(8080, () => {
  console.log("静态资源服务器部署成功");
});
复制代码

get

get 请求的参数都在 url 中 咱们能够经过 query 和 params 这两种形式来获取

const express = require("express");

const app = express();

app.get("/query", (req, res, next) => {
  res.json(req.query);
});

app.get("/param/:id", (req, res, next) => {
  res.json(req.params);
});

app.listen(3000, () => {
  console.log("start");
});
复制代码

post

post 请求的参数在 body 可是若是咱们直接打印 body 是看不到任何结果的

这是须要引入 body-parser 这个库

这个库和不一样版本的 express 集成状况以下

  • 3.x 内置

  • 4.x 分离

  • 4.16 内置函数

因此若是是 4.x 4.16 之前的版本 咱们须要手动安装这个第三方库

const express = require("express");

const app = express();

// 老版本写法 不推荐
// var bodyParser = require("body-parser");
// app.use(bodyParser.urlencoded({ extended: false }));
// app.use(bodyParser.json());

// 4.16之后的写法 推荐
// extended false 表示使用node的内置模块querystring来解析
// true 则表示使用第三方模块qs来解析
app.use(express.urlencoded({ extended: false }));
app.use(express.json());

app.post("/login", (req, res, next) => {
  console.log(req.body);
});

app.listen(8000, () => {
  console.log("start");
});
复制代码

文件上传

若是使用原生的方法实现文件上传 后台处理起来会很是麻烦 由于二进制流中不只有文件的信息 还有 header 的一些相关信息 咱们能够打印一些相关的数据来看一下

const express = require("express");

const app = express();

app.post("/upload", (req, res, next) => {
  console.log(req.headers);
  let data = "";
  req.on("data", (buffer) => {
    data += buffer;
  });
  req.on("end", () => {
    console.log(data.toString());
  });
});

app.listen(8080, () => {
  console.log("start~~~");
});
复制代码

而后咱们访问 localhost:8080/upload 并上传一个文件 就能够看到打印台输出了一段乱码的二进制流和请求头的 headers

headers 信息以下

{
  "user-agent": "PostmanRuntime/7.13.0",
  "accept": "*/*",
  "cache-control": "no-cache",
  "postman-token": "e48f538d-2988-4e39-8d50-80fdede0ed02",
  "host": "localhost:8080",
  "accept-encoding": "gzip, deflate",
  "content-type": "multipart/form-data; boundary=--------------------------372446874075094600561084",
  "content-length": "376074",
  "connection": "keep-alive"
}
复制代码

若是仔细对比 你就会发现二进制流中包含了 header 中的 content-type 中的 boundary 还有文件的 mime 类型等 若是不加处理的直接使用 fs 模块将这个二进制流写入到文件中 那么最终文件确定没法被正确解析

若是引入 multer 模块 那么文件上传功能就会变得很简单了

例如 咱们须要用户上传头像 代码以下

const express = require("express");
const multer = require("multer");
const path = require("path");

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    // 第一个参数为err的相关信息 node的回调函数都是错误优先的回调
    // 第二个参数为文件上传的目的地
    cb(null, "uploads");
  },
  filename: function (req, file, cb) {
    // 一样的 第一个参数为错误信息 第二个参数为文件名
    cb(
      null,
      file.fieldname + "-" + Date.now() + path.extname(file.originalname)
    );
  },
});

const upload = multer({ storage: storage });

const app = express();

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

app.listen(8080, () => {
  console.log("start~~~");
});
复制代码

上述方法演示的是单文件上传 req.file 中存储了文件的相关信息 以下

{
  "fieldname": "avatar",
  "originalname": "CHOU16.jpg",
  "encoding": "7bit",
  "mimetype": "image/jpeg",
  "destination": "uploads",
  "filename": "avatar-1616384964609.jpg",
  "path": "uploads\\avatar-1616384964609.jpg",
  "size": 375864
}
复制代码

若是须要支持多文件上传 例如还须要上传用户背景图 代码可改写成以下

app.post(
  "/fields",
  upload.fields([
    { name: "avatar", macCount: 1 },
    { name: "bg", maxCount: 2 },
  ]),
  (req, res, next) => {
    console.log(req.files);
  }
);

// {
// avatar: [
// {
// fieldname: 'avatar',
// originalname: 'CHOU1.jpg',
// encoding: '7bit',
// mimetype: 'image/jpeg',
// destination: 'uploads',
// filename: 'CHOU1.jpg',
// path: 'uploads/CHOU1.jpg',
// size: 646567
// }
// ],
// bg: [
// {
// fieldname: 'bg',
// originalname: 'CHOU2.jpg',
// encoding: '7bit',
// mimetype: 'image/jpeg',
// destination: 'uploads',
// filename: 'CHOU2.jpg',
// path: 'uploads/CHOU2.jpg',
// size: 398519
// }
// ]
// }
复制代码

若是是须要多张背景图上传 同一个 field 则能够用 array 的写法 第二个参数为最大可上传的数量

若是超过了最大数量 multer 会返回 MulterError: Unexpected field

app.post("/array", upload.array("bg", 3), (req, res, next) => {
  console.log(req.files);
});

// [
// {
// fieldname: 'bg',
// originalname: 'CHOU1.jpg',
// encoding: '7bit',
// mimetype: 'image/jpeg',
// destination: 'uploads',
// filename: 'CHOU1.jpg',
// path: 'uploads/CHOU1.jpg',
// size: 646567
// },
// {
// fieldname: 'bg',
// originalname: 'CHOU2.jpg',
// encoding: '7bit',
// mimetype: 'image/jpeg',
// destination: 'uploads',
// filename: 'CHOU2.jpg',
// path: 'uploads/CHOU2.jpg',
// size: 398519
// }
// ]
复制代码

router

若是将全部的请求处理都放在 index.js 中处理 那么 index.js 的代码就会变的很臃肿 这个时候咱们可使用路由来拆分咱们的代码

例如项目中有一个用户模块 实现增删改查的功能

咱们就能够新建一个 user.js 文件

const express = require("express");

const router = express.Router();

router.get("/add", (req, res, next) => {
  res.end("added");
});

router.post("/delete", (req, res, next) => {
  res.end("deleted");
});

router.post("/update", (req, res, next) => {
  res.end("updated");
});

router.post("/select", (req, res, next) => {
  res.end("selected");
});

module.exports = router;
复制代码

而后在 index.js 中导入这个路由

const express = require("express");
const userRouter = require("./user");

const app = express();

app.use("/user", userRouter);

app.listen(3000, () => {
  console.log("start");
});
复制代码

DEMO

express 中的中间件都是同步代码 即一个中间件执行完毕才会去执行另外一个中间件中的代码

例如咱们有以下功能要实现 在 ABC 三个中间件中 分别追加给 message 追加内容 而后在 A 中间件中输出结果

const express = require("express");

const app = express();

const middlewareA = (req, res, next) => {
  req.message = "";
  req.message += "A";
  next();
  res.end(req.message);
};

const middlewareB = (req, res, next) => {
  req.message += "B";
  next();
};

const middlewareC = async (req, res, next) => {
  req.message += "C";
  next();
};

app.use(middlewareA);
app.use(middlewareB);
app.use(middlewareC);

app.listen(8000, () => {
  console.log(8000);
});
复制代码

next 函数会在中间件栈中找到下一个中间件并执行 因此 middlewareA 中的 res.end 会在全部中间件都执行完毕后才执行 输入 ABC

那么 若是咱们在第三个中间件中用定时器来模拟异步操做 最终的结果会怎么样呢

将 middlewareC 改形成以下

const middlewareC = (req, res, next) => {
  setTimeout(() => {
    req.message += "C";
  }, 0);
  next();
};
复制代码

经过访问 8000 端口 咱们能够看到此次最终的输出变成了 AB 因而可知

中间件中的代码都是同步调用的

而这 也是 express 面对异步场景下的一种无力 而 koa 则不同

Koa

koa 支持 async 和 await 的用法 这就意味着在 koa 中能够抛去 express 中回调函数的写法 用一种更优雅的方式来解决异步场景

基本使用

与 express 不一样的是 koa 导出的不是函数 而是一个名为 Application 的对象

因此在使用上咱们只须要 new 一个实例便可 其余用法和 Express 基本类似

const Koa = require("koa");

const app = new Koa();

app.listen(8080, () => {
  console.log("Koa");
});
复制代码

路由

这里咱们借助第三方模块 koa-router 由于 koa 自己很纯净 基本全部功能都要经过第三方插件来实现

新建一个 user.js 的路由模块 Koa 将 express 中的 request 和 response 都合成到了上下文对象 context 中 简写为 ctx

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

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

userRouter.get("/home", (ctx, next) => {
  ctx.body = "welcome~~";
});

userRouter.post("/login", (ctx, next) => {
  ctx.body = "login";
});

module.exports = userRouter;
复制代码

而后在 index 中引入 user.js

const Koa = require("koa");

const userRouter = require("./router");

const app = new Koa();

app.use(userRouter.routes());

app.listen(8080, () => {
  console.log("Koa");
});
复制代码

处理请求

koa 中须要引入 koa-bodyparser 来解析 json 和 urlencoded

const Koa = require("koa");
const Router = require("koa-router");
const bodyParser = require("koa-bodyparser");

const app = new Koa();
const router = new Router();

app.use(bodyParser());
app.use(router.routes());

// 解析query
router.get("/query", (ctx, next) => {
  console.log(ctx.request.query);
});

// 解析params
router.get("/params/:id", (ctx, next) => {
  console.log(ctx.request.params);
});

// 解析urlencoded
router.post("/urlencoded", (ctx, next) => {
  console.log(ctx.request.body);
});

// 解析json
router.post("/json", (ctx, next) => {
  console.log(ctx.request.body);
});

app.listen(8080, () => {
  console.log("start");
});
复制代码

注意 koa-bodyparser 中间件须要最早被使用

异步处理

从新回到 express 中的 demo 若是想在 koa 中处理异步操做就变的很是简单了

const Koa = require("koa");
const axios = require("axios");
const app = new Koa();

const middlewareA = async (ctx, next) => {
  ctx.msg = "";
  ctx.msg += "A";
  await next();
  ctx.body = ctx.msg;
};

const middlewareB = async (ctx, next) => {
  ctx.msg += "B";
  await next();
};

const middlewareC = async (ctx) => {
  const res = await axios.get("https://koa.bootcss.com/");
  ctx.msg += res.data;
};

app.use(middlewareA);
app.use(middlewareB);
app.use(middlewareC);

app.listen(8080, () => {
  console.log("Koa");
});
复制代码

洋葱模型

洋葱模型其实不是什么高大尚的概念 经过下图咱们不难发现

全部中间件都会被 request 访问两次 就像剥洋葱同样 这就是洋葱模型

express 对比 koa

  • express 是完整和强大的,其中帮助咱们内置了很是多好用的功能;

  • koa 是简洁和自由的,它只包含最核心的功能,并不会对咱们使用其余中间件进行任何的限制。 甚至是在 app 中连最基本的 get、post 都没有给咱们提供;咱们须要经过本身或者路由来判断请求方式或者其余功能

  • koa 中间件支持 async await

相关文章
相关标签/搜索