出发点:
- 实在无法忍受的代码调试速度
- 当项目变的很大,webpack启动项目就变的很慢
- angular代理配置不支持正则路径,对于一些将参数写在路径中的接口,无法代理
具体场景:
- 同一套代码需要部署多个环境(机器),某个环境出现bug,就需要修改代理,重新启动项目
- 前端可能依赖多个微服务,与后端联调时,必须将依赖某个微服务的接口全部或者部分转发到该后端本地
一、定义代理配置
定义类型
export interface ProxyConfig {
target: TargetConfig;
headers: Record<string, string>;
pathRewrite: Record<string, string>;
}
export interface TargetConfig {
envHost: string;
}
export interface ParamsDictionary {
[key: string]: string;
}
export interface ParsedQs {
[key: string]: undefined | string | string[] | ParsedQs | ParsedQs[];
}
export type HttpsRequest = Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>;
export type HttpsResponse = Response<any, Record<string, any>>;
实现中间件
import { HttpsRequest, HttpsResponse, ProxyConfig } from './type';
import * as https from 'https';
import { RequestOptions } from 'https';
import * as http from 'node:http';
// 实现请求转发的类
export class HttpClient {
proxyConfig: ProxyConfig;
constructor(proxyConfig: ProxyConfig) {
this.proxyConfig = proxyConfig;
}
// 将请求转发至目标服务器,返回目标服务器响应体
async request(request: HttpsRequest, response: HttpsResponse): Promise<http.IncomingMessage> {
return new Promise(resolve => {
const targetUrl = this.getTargetUrl(request);
const options = this.getOptions(request);
console.log('\x1B[32m[日志] \x1B[0m' + targetUrl);
const httpsRequest = https.request(targetUrl, options, (targetResponse) => {
// 将目标服务器的响应流式传输给客户端
response.writeHead(targetResponse.statusCode ?? 200, targetResponse.headers);
resolve(targetResponse);
});
// 添加post请求参数
httpsRequest.write(JSON.stringify(request.body));
// 发送请求,将客户端的请求流传输给目标服务器
request.pipe(httpsRequest);
})
}
// 拼接目标url
getTargetUrl(request: HttpsRequest): string | URL {
const {target, pathRewrite} = this.proxyConfig;
const envHost = target.envHost;
const originPath = Object.keys(pathRewrite)[0];
const rewritePath = pathRewrite[originPath];
return envHost + this.rewritePath(request.url, originPath, rewritePath);
}
rewritePath(url: string, originPath: string, rewritePath: string): string {
return url.replace(new RegExp(originPath), rewritePath);
}
// 设置自定义headers
getOptions(request: HttpsRequest): RequestOptions {
request.headers.host = '';
return {
method: request.method,
headers: { ...request.headers, ...this.proxyConfig?.headers},
}
}
}
import { HttpsRequest, HttpsResponse, ProxyConfig } from './type';
import { HttpClient } from './http_client';
// 中间件,用于监听接口回调,
export function httpProxy(proxy: ProxyConfig) {
// 实例化转发请求类
const api = new HttpClient(proxy);
return async function (request: HttpsRequest, response: HttpsResponse, next: Function) {
response.locals.targetResponse = await api.request(request, response);
next();
}
}
// 将目标服务器的响应返回给客户端
export function end(request: HttpsRequest, response: HttpsResponse, next: Function) {
response.locals.targetResponse.pipe(response);
next();
}
二、搭建本地服务器,并接听客户端请求
import express from 'express';
import { proxy as proxyList } from '../proxy.config.js';
import * as https from 'node:https';
import fs from 'fs';
import { end, httpProxy } from './middlewares/http-proxy/http-proxy';
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const app = express();
app.use(express.json());
app.use(express.urlencoded({extended: false}));
const proxyPathList = Object.keys(proxyList);
proxyPathList.forEach(path => {
const proxy = proxyList[path];
app.all(new RegExp(path), httpProxy(proxy));
})
app.use(end);
app.listen(8002);
// https方案
// const options = {
// key: fs.readFileSync('ssl/server.key'),
// cert: fs.readFileSync('ssl/server.cert')
// }
// https.createServer(options, app).listen(443);
在app.all(new RegExp(path), httpProxy(proxy))和app.use(end)之间,可以添加一些其他操作,比如服务器登录:NodeJs + CDP 实现代理服务器登录功能在前端业务服务和鉴权服务分离的场景中,可以选择将登录操作托管给代理服务器 - 掘金