当前位置: 首页 > news >正文

谈一谈前端构建工具的本地代理配置(Webpack与Vite)

在Web前端开发中,我们在本地写代码经常遇到的一件事情就是代理配置。代理配置说简单也简单,配置一次基本就一劳永逸,但有时候配置不对,无论如何也连不上后端,就成了非常头疼的一件事。在这本文中,我们讨论一下前端构建工具提供的代理配置。

为什么要使用代理

我们前端开发的页面,是要运行到浏览器上的(SSR和其它场景暂不考虑)。浏览器会请求HTML页面,请求一些JS/CSS/图片等静态资源(部分静态资源可能使用CDN,这里暂不考虑),还会请求服务端的数据接口。

浏览器限制了我们请求HTML和请求的数据接口必须在同一个域中,简言之就是必须同一个域名或者同一个IP+端口号。我们的页面在线上运行时,这些HTML和接口都在同一网站中请求,不存在跨域问题。即使有这个问题,也是服务端采用Nginx等代理服务转发接口,浏览器看到的也是同域而不是跨域。

但在本地开发前端页面时,情况和服务端并不一致。我们本地一般会启动一个服务,专门提供HTML页面。但是所以需要的数据在后端或者平台中,并不在我们本地,因此是跨域的。所以需要一个代理来将跨域转换为同域。

  • 浏览器访问本地HTML页面 -> 前端本地服务承载
  • 使用代理前:浏览器访问后端数据接口 -> 跨域
  • 使用代理后:浏览器访问本地数据接口 -> 本地代理转发给后端 -> 从后端拿到数据 -> 返回给前端(同域)

这里使用React和fetch举一个跨域的例子:

import { useEffect } from "react";
function App() {useEffect(() => {fetch('/api/test').then(res => console.log(1, res))fetch('https://www.baidu.com/s').then(res => console.log(2, res))}, [])return <div>23123</div>;
}

在这里插入图片描述

我们在代码中使用fetch请求了两个接口,结果如图。一个是同域的/api/test,能正常收到结果。一个是跨域的https://www.baidu.com/s,请求被浏览器拦住了,错误提示为 CORS policy。如果我们想本地请求跨域的接口,就需要代理来帮我们。(还有一些其它方式可以规避跨域,因为与这个主题无关,且有诸多限制,因此这里并不描述)

当然,使用代理还有一些其它原因,例如需要调试移动端,需要Mock数据,方便切换后端服务等等。

模拟后端服务

在描述具体的代理配置和实验之前,我们先在本地启动一个模拟的后端服务,方便我们实验代理请求。

const http = require('http');http.createServer((req, res) => {console.log(`request url: ${req.url}`);if(req.url === '/api/test') {res.writeHead(200, { 'Content-Type': 'application/json' })res.end('{"a":1}');} else {res.writeHead(404);res.end('Not found');}
}).listen(8000, () => {console.log('server start!');
});

我们在本地的8000端口启动了一个“后端服务”,当请求命中接口时,返回json。这个后端服务与前端独立,是跨域的。在下面的例子中,我们假设:

  • 前端本地服务: http://localhost:5000
  • 后端服务: http://localhost:8000

Vite中的代理配置

这里参考Vite文档中的说明,简单描述一下Vite中的代理配置。

简单代理

server: {proxy: { '/api': 'http://localhost:8000' }
}
// 或者
server: {proxy: { "/api": { target: "http://localhost:8000" } }
}

最简单的配置就如上面所示,将/api开头的请求都转发到配置的服务地址上。我们举几个例子:

  • http://localhost:5000/api 命中为 http://localhost:8000/api
  • http://localhost:5000/api/test 命中为 http://localhost:8000/api/test
  • http://localhost:5000/api1 命中为 http://localhost:8000/api1
  • http://localhost:5000/abc 未命中

changeOrigin

变更请求中的host为target所指定的url。

server: {proxy: { "/api": { target: "http://localhost:8000", changeOrigin: true } }
}

通过打印我们模拟服务中req.headers,我们能看到加与不加的区别:

  • 配置之前 host: ‘localhost:5000’
  • 配置之后 host: ‘localhost:8000’

在这里插入图片描述

为什么要改host?因为部分后端会要求host为指定地址时,接口验证才会通过。还有很多后端会要求其它的指定数据,例如headers和cookies等,我们后面再描述。

rewrite

rewrite参数接收一个函数,可以在传给后端前,重写接口地址。

server: {proxy: {"/api": {target: "http://localhost:8000",rewrite: (path) => path.replace(/\/proxy/, ""),},},
},

使用上面的例子,可以将接口地址中的/proxy/去掉,而没有包含的则不改动。

  • http://localhost:5000/api/proxy/test 命中为http://localhost:8000/api/test
  • http://localhost:5000/api/test 命中为 http://localhost:8000/api/test
  • http://localhost:5000/abc 未命中

bypass

bypass也接收一个函数,不同的是它可以接收request对象,和代理配置等,方便我们直接修改请求对象。例如我们可以在bypass中直接修改headers:

server: {proxy: {"/api": {target: "http://localhost:8000",bypass: (req, res, options) => {req.headers.host = options.target;},},},
},
  • 配置之前 host: ‘localhost:5174’
  • 配置之后 host: ‘localhost:8000’

bypass还可以返回另一个url,代替原本的请求;返回false会为请求产生 404 错误;返回null或undefined则继续代理请求(如上例)。

// 将/api/b请求代替为/index.html
bypass: (req, res, options) => {if(req.url == '/api/b') return '/index.html';
},
// 返回404
bypass: (req, res, options) => {if(req.url == '/api/b') return false;
},

Webpack中的代理配置

这里我们换用Vue CLI创建了工程,看看Webpack中提供的代理配置。

简单代理

devServer: {proxy: 'http://localhost:8000'
}

服务器将任何未知请求 (没有匹配到静态文件的请求) 代理到指定的后端服务。

配置选项

proxy: {"/proxy": {target: "http://localhost:8000",changeOrigin: true,// pathRewrite: { '^/proxy': '' },pathRewrite: (path) => path.replace(/^\/proxy/, ""),bypass:  (req, res, options) => {req.headers.host = options.target;}},
},

这是在Webpack中的配置例子,可以看到和Vite的配置非常像,我们对比一下:

Vite配置Webpack配置
targettarget
changeOriginchangeOrigin
rewritepathRewrite
bypassbypass

这些对应的配置在Webpack和Vite中不仅效果一样,连配置方式,字段名都是基本都一样的,因此这里不再赘述了,Webpack的使用方法对比Vite即可。

Vite和Webpack配置总结

使用代理之后,在浏览器中,前端访问还是原来的非跨域的接口,但实际请求后端的url可能早就被改的面目全非了。例如上面的场景中,浏览器中看到的请求是http://localhost:5000/api/proxy/test,但实际对后端发起的请求为http://localhost:8000/api/test

生产模式

那么上述的代理配置,在生产模式,即我们构建后的成果物中会不会生效呢?答案是不会的。因为构建的成果物是纯静态资源(html,js等),没有服务器配置,因此是无法解决跨域问题的。在服务器中,一般由后端服务或者有统一的代理服务器解决跨域问题。

正好vite有预览模式(preview),我们本地打包一下构建成果物,然后本地预览一下试试。注意需要先在vite中设置preview.proxy为false,否则预览模式也会自动采用开发模式的代理。

结果虽然本地后端服务依旧是开启状态,在浏览器中看到的请求依然是http://localhost:5000/api/proxy/test没有变,但是接口不通了,后端服务收不到请求。

共同的底层http-proxy

通过上一节的具体配置,我们发现两个构建工具的配置形式非常像。这是因为它们使用了共同的底层依赖http-proxy。

查看Vite的文档与Vite代理配置的源码,发现Vite实际就是将http-proxy进行了简单的封装。查看Webpack的文档,发现代理配置是由http-proxy-middleware这个包提供的,而它的底层依赖,实际上也是http-proxy。因此,这两个构建工具的代理配置才这么像。

那为什么两个工具的配置还有不同的地方呢?这是因为构建工具在封装http-proxy时,多提供的额外功能。因此两者才会有一些小区别。

http-proxy简述

http-proxy是一个Node.js下的HTTP代理请求库,Vite和Webpack等构建工具就是用它作为底层代理请求的方法。我们不通过构建工具,直接使用一下http-proxy。这里我们启动一个小Node.js服务器作为前端开发服务器(在角色上类似于前端构建工具启动的本地开发服务)。

const http = require('http');
const httpProxy = require('http-proxy');const htmlData = `
<html><script>fetch('/proxy/api/test').then(res => console.log(1, res.json()))fetch('/proxy/api/b').then(res => console.log(2, res.json()))fetch('/api/b').then(res => console.log(2, res.json()))</script>
</html>
`;const reg = /^\/proxy/;
const rewrite = (path) => path.replace(reg, "");
const proxy = httpProxy.createProxyServer({});proxy.on('proxyReq', (proxyReq, req, res, option) => {proxyReq.path = rewrite(proxyReq.path);
})http.createServer((req, res) => {console.log(`request url: ${req.url}`);if(req.url === '/') {res.writeHead(200, { 'Content-Type': 'text/html' });res.end(htmlData);} else if(reg.test(req.url)) {proxy.web(req, res, { target: 'http://localhost:8000' }, (e) => {});} else {res.writeHead(404, { 'Content-Type': 'text/plain' });res.end('Not found');}
}).listen(5000, () => {console.log('server start!');
});

可以看到,我们自己启动了一个服务,提供HTML,里面包含fetch请求。当fetch请求命中我们的API规则时,将req和res交给http-proxy的代理接管,由它来负责请求真正的后端,拿到接口返回后,再透传回来。代理提供了一些配置和事件,可以做到拦截请求,更改请求内容或者返回数据等等。上面展示的例子就类似于我们在前面使用Webpack和Vite的配置的代理规则。注意上面即使是接口请求失败,也会帮我们转发失败结果。

通过例子可以看到,http-proxy代理本身是没有“独立服务”的(除非我们手动启动独立服务),依附于本地开发用的http服务。它的作用很简单,仅仅是帮我们请求后端接口而已。最后我们用一个图表示代理关系:

在这里插入图片描述

参考

  • Vite文档 server.proxy
    https://cn.vitejs.dev/config/server-options.html#server-proxy
  • webpack文档 devServer.proxy
    https://webpack.docschina.org/configuration/dev-server/#devserverproxy
  • Github http-proxy-middleware
    https://github.com/chimurai/http-proxy-middleware
  • Github http-proxy
    https://github.com/http-party/node-http-proxy
  • MDN fetch
    https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch
  • Vue CLI devServer.proxy
    https://cli.vuejs.org/zh/config/?#devserver-proxy
  • Vite代理配置 源码
    https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/proxy.ts

相关文章:

谈一谈前端构建工具的本地代理配置(Webpack与Vite)

在Web前端开发中&#xff0c;我们在本地写代码经常遇到的一件事情就是代理配置。代理配置说简单也简单&#xff0c;配置一次基本就一劳永逸&#xff0c;但有时候配置不对&#xff0c;无论如何也连不上后端&#xff0c;就成了非常头疼的一件事。在这本文中&#xff0c;我们讨论一…...

CentOS7非root用户离线安装Docker及常见问题总结、各种操作系统docker桌面程序下载地址

环境说明 1、安装用户有sudo权限 2、本文讲docker组件安装&#xff0c;不是桌面程序安装 3、本文讲离线安装&#xff0c;不是在线安装 4、目标机器是内网机器&#xff0c;与外部网络不连通 下载 1、下载离线安装包&#xff0c;并上传到$HOME/basic-tool 目录 下载地址&am…...

Alibaba Spring Cloud 十三 Nacos,Gateway,Nginx 部署架构与负载均衡方案

在微服务体系中&#xff0c;Nacos 主要承担“服务注册与发现、配置中心”的职能&#xff0c;Gateway&#xff08;如 Spring Cloud Gateway&#xff09;通常负责“路由转发、过滤、安全鉴权、灰度流量控制”等功能&#xff0c;而 Nginx 则常被用作“边缘反向代理”或“统一流量入…...

+-*/运算符优先级计算模板

acwing3302 知识点一&#xff1a;有关unordered_map的优先级 头文件<unordered_map>,然后进行符号优先级定义 定义方式unordered_map<char,int>pr{ {,1},{-,1},{*,2},{/,2}};其余没定义的默认为0 知识点二&#xff1a;头文件<cctype>中的isdigit()是判断…...

GPT 结束语设计 以nanogpt为例

GPT 结束语设计 以nanogpt为例 目录 GPT 结束语设计 以nanogpt为例 1、简述 2、分词设计 3、结束语断点 1、简述 在手搓gpt的时候&#xff0c;可能会遇到一些性能问题&#xff0c;即关于是否需要全部输出或者怎么节约资源。 在输出语句被max_new_tokens 限制&#xff0c…...

FastDFS的安装及使用

分布式存储发展历程 前段时间 618 活动火热进行&#xff0c;正是购物的好时机。当我们访问这些电 商网站的时候&#xff0c;每一个商品都会有各式各样的图片展示介绍&#xff0c;这些图 片一张两张可以随便丢在服务器的某个文件夹中&#xff0c;可是电商网站如此 大体量的…...

C++ lambda表达式

目录 1.lambda表达式 1.1什么是Lambda表达式&#xff1f; 1.2Lambda表达式的语法 1.3捕捉列表 1.4函数对象与lambda表达式 1.lambda表达式 1.1什么是Lambda表达式&#xff1f; Lambda表达式是C11标准引入的一种匿名函数&#xff0c;它允许你在需要函数的地方直接编写代码…...

react页面定时器调用一组多个接口,如果接口请求返回令牌失效,清除定时器不再触发这一组请求

为了实现一个React页面使用定时器调用一组多个接口&#xff0c;并在任意一个接口请求返回令牌失效时清除定时器且不再触发这一组请求&#xff0c;可以遵循以下步骤&#xff1a; 1. 定义API调用函数&#xff1a;创建一个函数来处理一组API调用。每个API调用都应该检查响应状态以…...

Python的泛型(Generic)与协变(Covariant)

今天咱们聊聊Python类型标注中的泛型(Generic),与协变(Covariant)。 不了解类型标注的小伙伴,可以先看一看我的上一篇文章 “Python类型检查” Python 类型检查-CSDN博客 例子 这次我开个宠物商店。看下面代码。 class Animal:passclass Dog(Animal):passclass Cat(A…...

Python Typing: 实战应用指南

文章目录 1. 什么是 Python Typing&#xff1f;2. 实战案例&#xff1a;构建一个用户管理系统2.1 项目描述2.2 代码实现 3. 类型检查工具&#xff1a;MyPy4. 常见的 typing 用法5. 总结 在 Python 中&#xff0c;静态类型检查越来越受到开发者的重视。typing 模块提供了一种方式…...

OpenEuler学习笔记(六):OpenEuler与其他Linux服务器的区别是什么?

OpenEuler是一款基于Linux内核的开源服务器操作系统&#xff0c;与其他Linux服务器操作系统&#xff08;如CentOS、Ubuntu Server等&#xff09;存在多方面的区别&#xff0c;主要体现在以下几个方面&#xff1a; 一、社区与支持 OpenEuler&#xff1a;由华为发起并开源&…...

如何使用CRM数据分析和洞察来支持业务决策和市场营销?

如何使用CRM数据分析和洞察来支持业务决策和市场营销&#xff1f; 大家好&#xff01;今天咱们聊聊一个特别重要的话题——如何利用客户关系管理&#xff08;CRM&#xff09;系统中的数据进行分析与洞察能够帮助我们做出更好的业务决策以及提升市场营销效果。其实啊&#xff0…...

MyBatis和JPA区别详解

文章目录 MyBatis和JPA区别详解一、引言二、设计理念与使用方式1、MyBatis&#xff1a;半自动化的ORM框架1.1、代码示例 2、JPA&#xff1a;全自动的ORM框架2.1、代码示例 三、性能优化与适用场景1、MyBatis&#xff1a;灵活的SQL控制1.1、适用场景 2、JPA&#xff1a;开发效率…...

SVN客户端使用手册

目录 一、简介 二、SVN的安装与卸载 1. 安装&#xff08;公司内部一般会提供安装包和汉化包&#xff0c;直接到公司内部网盘下载即可&#xff0c;如果找不到可以看下面的教程&#xff09; 2. 查看SVN版本 ​编辑 3. SVN卸载 三、SVN的基本操作 1. 检出 2. 清除认证数据 3. 提交…...

VsCode安装文档

一、下载 进入VS Code官网&#xff1a;Visual Studio Code - Code Editing. Redefined&#xff0c;点击 DownLoad for Windows下载windows版本 当然也可以点击旁边的箭头&#xff0c;下载Windows版本 或 Mac OS 版本 备注&#xff1a; Stable&#xff1a;稳定版Insiders&#…...

豆包MarsCode 蛇年编程大作战 | 高效开发“蛇年运势预测系统”

&#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 豆包MarsCode 蛇年编程大作战 | &#x1f40d; 蛇年运势预测 在线体验地址&#xff1a;蛇年…...

【动态规划】--- 斐波那契数模型

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 算法Journey &#x1f3e0; 第N个泰波那契数模型 &#x1f4cc; 题目解析 第N个泰波那契数 题目要求的是泰波那契数&#xff0c;并非斐波那契数。 &…...

生信软件管家——conda vs pip

pip vs conda&#xff1a; 安装过python包的人自然两种管理软件都用过&#xff0c; Pip install和Conda install在Python环境中用于安装第三方库和软件包&#xff0c;但它们在多个方面存在显著的区别 总的来说&#xff1a; pip是包管理软件&#xff0c;conda既是包管理软件&…...

代码随想录——串

文章目录 反转字符串反转字符串Ⅱ路径加密反转字符串中的单词动态口令字符串匹配重复的子字符串 反转字符串 344. 反转字符串 //前后对应交换 //0<->sSize-1 //1<->sSize-2 //... //i<->sSize-1-i,i0,1,...,(sSize-1)/2 void reverseString(char* s, int s…...

詳細講一下RN(React Native)中的列表組件FlatList和SectionList

1. FlatList 基礎使用 import React from react; import { View, Text, FlatList, StyleSheet } from react-native;export const SimpleListDemo: React.FC () > {// 1. 準備數據const data [{ id: 1, title: 項目 1 },{ id: 2, title: 項目 2 },{ id: 3, title: 項目 3…...

【Python】 -- 趣味代码 - 小恐龙游戏

文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)

0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述&#xff0c;后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作&#xff0c;其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...

CentOS下的分布式内存计算Spark环境部署

一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架&#xff0c;相比 MapReduce 具有以下核心优势&#xff1a; 内存计算&#xff1a;数据可常驻内存&#xff0c;迭代计算性能提升 10-100 倍&#xff08;文档段落&#xff1a;3-79…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI

前一阵子在百度 AI 开发者大会上&#xff0c;看到基于小智 AI DIY 玩具的演示&#xff0c;感觉有点意思&#xff0c;想着自己也来试试。 如果只是想烧录现成的固件&#xff0c;乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外&#xff0c;还提供了基于网页版的 ESP LA…...

Java 加密常用的各种算法及其选择

在数字化时代&#xff0c;数据安全至关重要&#xff0c;Java 作为广泛应用的编程语言&#xff0c;提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景&#xff0c;有助于开发者在不同的业务需求中做出正确的选择。​ 一、对称加密算法…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

Mobile ALOHA全身模仿学习

一、题目 Mobile ALOHA&#xff1a;通过低成本全身远程操作学习双手移动操作 传统模仿学习&#xff08;Imitation Learning&#xff09;缺点&#xff1a;聚焦与桌面操作&#xff0c;缺乏通用任务所需的移动性和灵活性 本论文优点&#xff1a;&#xff08;1&#xff09;在ALOHA…...

LLMs 系列实操科普(1)

写在前面&#xff1a; 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容&#xff0c;原视频时长 ~130 分钟&#xff0c;以实操演示主流的一些 LLMs 的使用&#xff0c;由于涉及到实操&#xff0c;实际上并不适合以文字整理&#xff0c;但还是决定尽量整理一份笔…...

华为OD机试-最短木板长度-二分法(A卷,100分)

此题是一个最大化最小值的典型例题&#xff0c; 因为搜索范围是有界的&#xff0c;上界最大木板长度补充的全部木料长度&#xff0c;下界最小木板长度&#xff1b; 即left0,right10^6; 我们可以设置一个候选值x(mid)&#xff0c;将木板的长度全部都补充到x&#xff0c;如果成功…...

uniapp 小程序 学习(一)

利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 &#xff1a;开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置&#xff0c;将微信开发者工具放入到Hbuilder中&#xff0c; 打开后出现 如下 bug 解…...