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 | const express = require('express'); |
中间件
Express是一个路由和中间件的Web框架,它本身的功能非常少:
Express应用程序本质上是一系列中间件函数的调用;
中间件是什么?
- 中间件的本质是传递给express的一个回调函数;
- 这个回调函数接受三个参数:
- 请求对象(request对象);
- 响应对象(response对象);
- next函数(在express中定义的用于执行下一个中间件的函数);
中间件可以执行的任务:
- 执行任何代码
- 更改请求和响应对象
- 结束请求-响应周期(返回数据)
- 调用栈中的下一个中间件
如果当前中间件功能没有结束请求-响应周期,则必须调用next()将控制权传递给下一个中间件功能,否则,请求将被挂起。
中间件的匹配:从上往下匹配 匹配到了就执行 不过在中间件中要调用next()才能往下执行
如下是一个例子:
1 | const express = require('express'); |
详细api查看官方文档https://www.expressjs.com.cn/
中间件的应用
body参数解析
编写解析request body中间件
1
2
3
4
5
6
7
8
9
10
11
12
13
14app.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
2const upload = multer()
app.use(upload.any()) //这个any解析的form-data中非文件如果form-data是文件
首先要给multer传参数
1
2
3
4
5
6
7
8
9
10
11
12
13const 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
4app.post('/uploads',upload.array('file'),(req,res,next)=>{
console.log(req.files);
res.end("文件上传成功")
})注意 前面的upload.any如果写在全局 那么会对后续req.files造成影响 应当写到需要的中间体里面 比如
1
2
3
4app.post('/product',upload.any(),(req,res,next)=>{ //将upload.any()写到里面 如果写到全局会对res.files有影响
console.log(req.body);
res.end("商品")
})
第三方中间件
比如 morgan 可以将请求日志记录下来 需要单独安装
1 | const writerStream = fs.createWriteStream('./logs/access.log',{ |
其他中间件可以查看https://github.com/expressjs
客户端发送请求的方式
客户端传递到服务器参数的方法常见的是5种:
- 通过get请求中的URL的params
- 通过get请求中的URL的query
- 通过post请求中的body的json格式(中间件中使用过)
- 通过post请求中的body的x-www-form-urlencoded格式(中间件中使用过)
- 通过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 | const express = require('express'); |
静态资源服务器
Node可以作为静态资源服务器, express提供方法
1 | const express = require('express') |
express 的错误处理
当遇到错误信息时,需要返回错误信息和状态码 比如登录注册时要进行验证 如果是错误的或者存在的话就要返回error
比如
1 | const USERNAME_DOES_NOT_EXISTS = "USERNAME_DOES_NOT_EXISTS"; |
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 继续调用