Web框架 - Express

因为使用原生内置的http模块搭建服务器在进行很多处理的时候会很复杂 所以使用框架

比较流行的框架是 express 和 koa

Express整个框架的核心就是中间件,理解了中间件其他一切都非常简单!

Express的安装和体验

  • 安装脚手架npm install -g express-generator
  • 创建项目express express-demo
  • 安装依赖npm install
  • 启动项目node bin/www

或者用npm init -y的方式来从0搭建自己的express应用架构

体验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const express = require('express');
//express实际上是一个函数 createApplication
//返回一个函数 app
const app = express()

//监听默认路径
app.get('/',(req,res,next)=>{
res.end('hello express')
})
app.post('/',(req,res,next)=>{
res.end('hello post express')
})
app.post('/login',(req,res,next)=>{
res.end('welcome back ')
})

//开启监听
app.listen(8000,()=>{
console.log("体验服务器");
})

中间件

Express是一个路由和中间件的Web框架,它本身的功能非常少:

Express应用程序本质上是一系列中间件函数的调用;

中间件是什么?

  • 中间件的本质是传递给express的一个回调函数;
  • 这个回调函数接受三个参数:
    • 请求对象(request对象);
    • 响应对象(response对象);
    • next函数(在express中定义的用于执行下一个中间件的函数);

中间件可以执行的任务:

  • 执行任何代码
  • 更改请求和响应对象
  • 结束请求-响应周期(返回数据)
  • 调用栈中的下一个中间件

如果当前中间件功能没有结束请求-响应周期,则必须调用next()将控制权传递给下一个中间件功能,否则,请求将被挂起。

中间件的匹配:从上往下匹配 匹配到了就执行 不过在中间件中要调用next()才能往下执行

如下是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const express = require('express');

const app = express()

app.use((req,res,next)=>{
console.log("common middleware 010");
next()
})

app.get('/home',(req,res,next)=>{
console.log("home path and method middleware 01");
next()
},(req,res,next)=>{
console.log("home path and method middleware 02");
next()
},(req,res,next)=>{
console.log("home path and method middleware 03");
next()
},(req,res,next)=>{
console.log("home path and method middleware 04");
res.end("homepage")
})
app.listen(8000,()=>{
console.log("路径中间件服务器启动成功");
})

当发送了get请求会打印
common middleware 010
home path and method middleware 01
home path and method middleware 02
home path and method middleware 03
home path and method middleware 04

详细api查看官方文档https://www.expressjs.com.cn/

中间件的应用

body参数解析

  • 编写解析request body中间件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    app.use((req, res, next) => {
    if (req.headers["content-type"] === 'application/json') {
    req.on('data', (data) => {
    const info = JSON.parse(data.toString());
    req.body = info;
    })

    req.on('end', () => {
    next();
    })
    } else {
    next();
    }
    })
  • 实际上 可以使用两个express提供的中间件

    • 我们需要根据前端传过来的数据类型进行解析
    1
    2
    3
    4
    5
    6
    //body-parser  第三方库
    app.use(express.json())
    app.use(express.urlencoded({extended:true})) //对urlencoded的解析
    //extended Boolean类型
    //true: 对urlencoded解析时 使用的是第三方库 qs
    //false :对urlencode 解析时 使用的是Node内置模块 querystring
  • 如果前端传过来的是form-data 改如何解析呢?

    • 这里是需要使用一个第三方库multer

      • 如果form-data中是字段
      1
      2
      const upload = multer()
      app.use(upload.any()) //这个any解析的form-data中非文件
      • 如果form-data是文件

      • 首先要给multer传参数

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        const storage =multer.diskStorage({
        destination:(req,file,cb)=>{
        cb(null,'./uploads')
        },
        filename:(req,file,cb)=>{
        cb(null,Date.now()+path.extname(file.originalname)) //extname取出了原始名字的后缀名 拼接Date.now是可以让名字不重复
        }
        })

        const upload = multer({
        // dest:"./uploads/"
        storage
        })
      • 通过/uploads路径前台上传文件

        1
        2
        3
        4
        app.post('/uploads',upload.array('file'),(req,res,next)=>{
        console.log(req.files);
        res.end("文件上传成功")
        })
      • 注意 前面的upload.any如果写在全局 那么会对后续req.files造成影响 应当写到需要的中间体里面 比如

        1
        2
        3
        4
        app.post('/product',upload.any(),(req,res,next)=>{  //将upload.any()写到里面  如果写到全局会对res.files有影响
        console.log(req.body);
        res.end("商品")
        })

第三方中间件

比如 morgan 可以将请求日志记录下来 需要单独安装

1
2
3
4
5
const writerStream = fs.createWriteStream('./logs/access.log',{
flags:"a+"
})
app.use(morgan("combined",{stream:writerStream}))

其他中间件可以查看https://github.com/expressjs

客户端发送请求的方式

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

  1. 通过get请求中的URL的params
  2. 通过get请求中的URL的query
  3. 通过post请求中的body的json格式(中间件中使用过)
  4. 通过post请求中的body的x-www-form-urlencoded格式(中间件中使用过)
  5. 通过post请求中的form-data格式(中间件中使用过)
  • 通过get请求中的URL的params

  • 通过get请求中的URL的query

响应数据方式

end方法

  • 类似于http中的response.end方法,用法是一致的

json方法

  • json方法中可以传入很多的类型:object、array、string、boolean、number、null等,它们会被转换成json格式返回

status方法

  • 用于设置状态码:

更多响应方式:https://www.expressjs.com.cn/4x/api.html#res

Express的路由

如果我们将所有的代码逻辑都写在app中,那么app会变得越来越复杂

  • 一方面完整的Web服务器包含非常多的处理逻辑;
  • 另一方面有些处理逻辑其实是一个整体,我们应该将它们放在一起:比如对users相关的处理
    • 比如
      • 获取用户列表;
      • 获取某一个用户信息;
      • 创建一个新的用户;
      • 删除一个用户;
      • 更新一个用户;
  • 所以我们可以用express.Router来创建一个路由处理程序
    • 一个Router实例拥有完整的中间件和路由系统;
    • 因此,它也被称为 迷你应用程序(mini-app);

使用路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const express = require('express');

const router = express.Router()

router.get('/',(req,res,next)=>{
res.json(["hyp","why","hyo"])
})
router.get('/:id',(req,res,next)=>{
res.json(`${req.params.id}用户的信息`)
})
router.post('/',(req,res,next)=>{
res.json("create user success~")
})

module.exports = router

静态资源服务器

Node可以作为静态资源服务器, express提供方法

1
2
3
const express = require('express')
const app = express()
app.use(express.static('./build')) //传入静态资源的路径

express 的错误处理

当遇到错误信息时,需要返回错误信息和状态码 比如登录注册时要进行验证 如果是错误的或者存在的话就要返回error

比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const USERNAME_DOES_NOT_EXISTS = "USERNAME_DOES_NOT_EXISTS";

app.post('/login', (req, res, next) => {
// 加入在数据中查询用户名时, 发现不存在
const isLogin = false;
if (isLogin) {
res.json("user login success~");
} else {
// res.type(400);
// res.json("username does not exists~")
next(new Error(USERNAME_DOES_NOT_EXISTS)); //new一个Error 并向后执行
}
})

app.use((err,req,res,next)=>{ /有四个参数时就会标识这是一种错误的处理
let status = 400;
let message = "";
console.log(err.message);

switch(err.message) {
case USERNAME_DOES_NOT_EXISTS:
message = "username does not exists~";
break;
case USERNAME_ALREADY_EXISTS:
message = "USERNAME_ALREADY_EXISTS~"
break;
default:
message = "NOT FOUND~"
}
res.status(status);
res.json({
errCode: status,
errMessage: message
})
})

express的源码学习

1.调用express() 到底创建的是什么

首先在导入express的时候

exports = module.exports = createApplication 所以说实际上是调用了createApplication这个函数 当我们调用express() 的时候 实际上express反回了一个app app.handle

2.app.listen() 启动服务器如何可以结合原生来启动服务器

在源码中使用了混入的方法 mixin(app,proto,false) 执行http.createServer

3.app.use(中间件时) 内部发生了什么

app.use->router.use layer中存放着callback 然后将layer放入到了一个数组stack里面

4.用户发送了请求 中间件是如何被回调的

app.handle(req,res,next) -> router.handle ->proto.handle ->调用next() ->layer.handle_request(req.res,next)

5.调用next 为什么执行下一个中间件

function next index++ 从stack中找layer layer=stack[idx++]查找符合规则的中间件-> layer.handle_request 继续调用