angular+webpack修改代理配置麻烦?不如自己写一个

94 阅读2分钟

出发点:

  1. 实在无法忍受的代码调试速度
  2. 当项目变的很大,webpack启动项目就变的很慢
  3. angular代理配置不支持正则路径,对于一些将参数写在路径中的接口,无法代理

具体场景:

  1. 同一套代码需要部署多个环境(机器),某个环境出现bug,就需要修改代理,重新启动项目
  2. 前端可能依赖多个微服务,与后端联调时,必须将依赖某个微服务的接口全部或者部分转发到该后端本地

一、定义代理配置

定义类型

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 实现代理服务器登录功能在前端业务服务和鉴权服务分离的场景中,可以选择将登录操作托管给代理服务器 - 掘金