1、定义:
是一个代理中间件,用于转发请求,将客户端发送的请求数据转发到目标服务器,再将响应输送到客户端。
2、核心概念
如何配置一个代理中间件
1、createProxyMiddleware([context,] config)
- context:匹配请求的路径
- options:中间件配置
2、option
- 属性
- 代理事件
1、常用属性:
- target :用于设定目标服务器的 host
- router: 根据客户端请求重新设定目标服务器的 host(这样,根据不同的请求,可以设定多个目标服务器)
- pathRewrite:将客户端请求路径转化为目标服务器地址。
- changeOrigin:是否将主机标头的来源更改为目标URL
- **secure:**是否验证SSL证书
2、监听代理事件
1、error
:发出错误事件。框架不对客户端和代理之间传递的消息以及代理和目标之间传递的消息进行任何错误处理,需要手动监听错误并进行处理。
2、proxyReq
:在发送数据之前发出此事件。它使您有机会更改proxyReq请求对象。
3、proxyReqWs
:在发送数据之前发出此事件。它使您有机会更改proxyReq请求对象。适用于“ websocket”连接
4、proxyRes
:如果对目标的请求得到响应,则发出此事件。
5、open
:创建代理Websocket并将其通过管道传输到目标Websocket后,将发出此事件。
6、close
:关闭代理websocket后,将发出此事件。
(proxySocket
不推荐使用):不推荐使用open
。
3、应用一个代理中间件的步骤
1、创建并配置一个代理中间件
2、在项目中注册中间件
1、应用举例:
目的:
proxyRes:这里将目标主机返回的response data包装成
{
code:200
data:proxyRes.data
}
写进res对象,返回给client
代码:
import {NestFactory} from '@nestjs/core';
import {AppModule} from './app.module';
import {createProxyMiddleware} from "http-proxy-middleware";
import {IncomingMessage} from "http";
async function sleep(interval) {
return new Promise(resolve => {
setTimeout(resolve, interval);
})
}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use("/api",
createProxyMiddleware({
target: 'http://localhost:3001',
changeOrigin:true,
pathRewrite: {
'/api/hello': '/hello'
},
secure: false,
selfHandleResponse: true,
onProxyReq: (proxyReq, req, res) => {
if (req.body) {
const bodyData = JSON.stringify(req.body)
// incase if content-type is application/x-www-form-urlencoded -> we need to change to application/json
proxyReq.setHeader("Content-Type", "application/json")
proxyReq.setHeader("Content-Length", Buffer.byteLength(bodyData))
// stream the content
proxyReq.write(bodyData)
}
// 禁用缓存
// proxyReq.setHeader('Cache-Control', 'no-cache');
// res.send("req")
//
// console.log(res.getHeaders())
},
onProxyRes: async (proxyRes, req, res) => {
let bodyRes
bodyRes = await getBody(proxyRes)
console.log("1 res from proxied server:", bodyRes);
res.json({
code: 200,
data: bodyRes,
}
)
},
onError: (err, req, res) => {
res.writeHead(500, {
'Content-Type': 'text/plain',
});
res.end('Something went wrong. And we are reporting a custom error message.');
}
})
);
await app.listen(3002);
}
function getBody(proxyRes: IncomingMessage) {
return new Promise((resolve, reject) => {
let body = []
proxyRes.on('data', function (chunk) {
body.push(chunk)
})
let bodyStr = ""
proxyRes.on('end', function () {
bodyStr = Buffer.concat(body).toString()
console.log('getBody ======', bodyStr)
resolve(bodyStr)
})
})
}
bootstrap();
2、注意:
1、如果要给客户端手动返回响应值,
在这里要注意,我们需要设置
因为:
1、如果设置了selfHandleResponse=true的话,代理服务器会发射一个“end“事件,当客户端响应对象res监听到这个事件后,可以自己修改响应
2、如果不设置selfHandleResponse=true的话,代理响应对象proxyRes会将响应流直接pipe(写)进客户端res响应对象,直接响应到客户端
当我们想要修改响应时
会报如下错误
提示我们,不能在响应发送给客户端之后再来修改响应头,因为不设置selfHandleResponse=true,代理响应对象proxyRes会将响应流直接pipe(写)进客户端res响应对象,直接响应到客户端
2、如果在proxyReq阶段,直接返回res
客户端会立即收到响应,
在req.send之后,也可以在onProxyReq中打印出响应对象
如果有多个中间件,只会执行完当前的代理中间件,然后直接响应客户端,后面的中间件不会再执行
不会立即中断当前中间件,后面的代理过程还是会执行(应用场景:客户端删除十万条商品信息,可以立即返回客户端“正在删除中,请稍等”,然后代理去执行删除),拿到代理对象的返回并打印返回值“hello world"
res.json({
code: 200,
data: bodyRes,
}
)
但是当执行res.json给客户端发送响应时报错,不能在响应已经发送给client后再次设置信息(因为proxyReq已经给客户端响应)
4、实现原理
1、获取中间注册的配置信息,生成一个代理服务器
2、根据 options.pathRewrite 生成路径转化器 pathRewriter
3、为代理服务器绑定监听事件
4、创建转发 http, https, websocket 请求的代理中间件。
中间件,请求阶段为什么可以拿到res对象
express源码:
1、创建application
创建app时会创建req和res对象
request对象提供create方法根据底层的http对象来创建一个request对象
reaponse对象提供create方法根据底层的http对象来创建一个response对象
创建一个中间件时,会将app中创建的request和response对象注册进来
5、总结
1、http-proxy-middleware 是一个代理中间件,用于转发请求到目标服务器
2、可以监听代理对象的请求和响应,以增强请求和响应
3、实现原理:本质上是一个中间件,添加了代理对象的监听事件,以至于我们能够灵活处理代理对象