📖阅读本文,你将:
- 初步理解什么是"反向代理"。
- 理解为什么2022年,前端离不开"反向代理"。
- 跟随作者一步步摸清楚如何开发一个简单而实用的轻量代理工具。
- 学会如何把本地代码发布到
npm
全局命令中。 - 收货一份少得可怜的源码。
一、什么是反向代理?
度娘说:“反向代理位于用户与目标服务器之间,对用户而言,反向代理相当于目标服务。用户直接访问反向代理服务器就可以获得目标服务器的资源。”
很好理解吧?
还不理解的话,我用更通俗的语法解释下:
你朝反向代理发请求,它将请求转发给其他服务,并将结果返回给你。
如上图所示,你找代理服务器请求 业务接口/本地资源/对象存储资源
,代理服务器分别向不同的资源发出请求,获得资源后,再返还给你。
对于2022年的前端开发而言,反向代理已经是工作中不可或缺的一部分了。
为什么呢?
二、前端为啥需要代理层?
2.1 合理规避“同源策略”
同源策略:浏览器的护城河。
浏览器的 “同源策略” 声明:默认情况下,“跨域” 的请求返回会被拦截下来。
啥是跨域?
当一个请求 url
的协议、域名、端口三者之间任意一个与当前页面 url
不同即为跨域。
例如,你在 https://baidu.com
页面上请求 https://google.com/user/abc
就属于跨域请求。(域名不同)
至于为啥浏览器要有这样一条 “同源策略”,那又是一个可以单独开篇的话题了,本文不进行赘述。
总之,因为 “同源策略” 的存在,前端在开发时就会面临很多的问题,比如:“如何跨域去调用服务端的接口?”
CORS
是一种解决方案。
CORS (Cross-Origin Resource Sharing),中文名为 “跨域资源共享”,通过服务端在“响应头”、“OPTIONS” 方法上的配合,可以让浏览器允许页面请求跨域资源。
但这其实也会在一定程度上增加安全风险,并需要服务端同学的配合。
“反向代理” 则是一种成本更低,也被更为泛用技术手段。
如上图所示,注意以下几个关键点:
- 当前页面在
http://localhost:8080
域。 - 浏览器向
http://localhost:8080
发起请求时,完全符合“同源策略”。 - 反向代理将接收到的请求转发给真实服务器,获得了请求的数据。
- 反向代理将得到的数据返回给浏览器页面。
- 浏览器获得到了它请求的数据。
在整个过程中,既符合浏览器的 “同源策略”, 又不需要服务端介入增加额外的配置,从而不会因为这些增加的配置导致生产环境增加危险系数。
反向代理,让前端研发处理跨域时,随心所欲。
2.2 SPA应用:有它更灵活
SPA ( Single Page Application ),中文名为 “单页应用”,是目前市场上前端工程最普遍、最常见的存在形态。
自从 React
、 Angular
、 Vue
等一批前端框架开始占据市场,SPA
便成为了几乎所有前端都无法绕开的一个关键名词。
SPA
通常情况下,和 webpack
、vite
等构建工具是相生相伴的好搭档,得益于社区的兴隆,webpack
提供了 devServer
这一特性,能让前端开发者无感地用上了 “反向代理” 的特性。
每当你在控制台输入:
npm run dev
# or
npm run start
的时候,其实前端框架里的 webpack
已经悄悄给你启动了一个 反向代理,你可以在配置文件里轻松地让符合某些规则的请求 被代理 到服务器上。
它不仅提供反向代理,也和 webpack
本身的 hmr
(热更新) 绑定在一起,每当资源更新后,你很快就能在页面上看到最新代码所呈现的效果。
这当然是极佳的开发体验,但它也 带来了一个小小的问题:
切换服务的成本被提高了。
假想一个场景:
当你的服务连着 [环境A] 进行开发时,突然发现这个环境的服务宕机了,于是你熟练地切换到了 [环境B],因为你修改了代理配置,你的
webpack
需要重启,对于一些巨石项目或低配电脑,这可能需要5分钟或者更久。
终于,服务起好了,你在 [环境B] 快乐的开发着,此时被告知 [环境A] 修复了,为了联调,你得切回去。好家伙,又是一个
5分钟
……
开发过大型 SPA
项目的小伙伴,想必对这种场景并不陌生。
此时,我们就难免会有一种疑问:
反向代理一定要和
开发服务
绑定吗?我切个环境,关您 开发服务
啥事?您重启个什么劲呢?
在这个疑问的驱动下,一个想法出现了:
为什么我们不增加一层 轻量代理层 呢?它只从事代理相关的工作,让 代理 和 构建 彻底解耦呢?
因为专一,所以轻量,从而 切换贼快。
2.3 微前端:用它补全资源
随着 SPA
的日渐发展,相关配套逐渐成熟。
但是在某些巨型企业级应用日益庞大臃肿之后,开始暴露出一些让人头疼的问题。
- 代码结构日渐复杂,难以维护和管理
- 开发工具启动、热更新效率捉急
- 模块与模块之间依赖变高,复用成本增加
这类应用,被业内人士起了一个形象的名字——“巨石应用”。
于是有了 “微前端” 的概念,比如 micro.js
和 qiankun.js
,都提供了相应的解决方案。
微前端固然是 大型企业级应用 开发的利器,形成了一种全新的前端开发方式,提供了前端微应用 "制品级" 复用的能力,但它同样引入了新的问题:
开发期可能需要起一堆应用🤣。(如图)
当然,不必惊慌,方法总比困难多。
当你需要 “容器” 或 “其他微应用” 的资源时,完全不必局限在本地端口中获取,也可以在某个开发环境上直接拉取。
此时,一个独立轻量的 “代理层” 可能就会显得 恰如其分。
虽然也可以在每个微应用内设置代理,但是很显然,一个轻量的代理层会让整个结构变得更加便捷,灵活。
比如,此时我想在本地新起一个微应用,想让它进入我的联调范围,相比于重启某个微应的开销,重启代理层的开销就会显得更小。
如上所述:
一个 “轻量代理层” 的存在,对于前端开发而言,真的可以带来非常多的便捷,那么,我们是否可以手写订制一个好用的 “轻量代理层” 呢?
当然可以,现在就写!
跟着春哥一起,手把手带你实现一个 “代理层神器”!
三、手把手!简单实用的自制工具
前端制作开发工具,首选的技术当然是毫无疑问的 nodejs
,语言上的熟悉,可以降低非常多不必要的麻烦。
在 nodejs
生态里,被运用最广的 web
开发框架无疑正是 express
,这也是我们接下来要选择使用的框架。
3.1 认识 express
express
:
"基于 Node.js
,快速、开放、极简的 Web
开发框架。"
可以说,它是 nodejs
生态里,最具影响力的明星框架之一。
通过 express
,你可以快速构建一个 web
应用,这个所谓的 web
应用,可以是一个网站、可以是一个接口提供方、可以是一个定时发送邮件的工具,当然,它也可以是我们想要实现的 “轻量代理层”。
简单来说:
你使用
express
可以创建一个应用,该应用可以监听端口,提供 http、https、websocket 等多种形态的网络服务。
关于 express
的一切,如果你有兴趣,可以访问其官网学习:
www.expressjs.com.cn/starter/ins…
在 express
使用过程中,我们需要理解许多的概念,包括:什么是应用、什么是路由、什么是中间件。
虽然这些在文档中都有官方解释,但为了你能快速理解,我们先写一个简单例子:
mkdir 'lightweight-proxy' # windows 用户请把 mkdir 换成 md
cd lightweight-proxy
yarn init
git init
yarn add express
完成以上步骤,就成功创建了一个项目,并安装了对 express
的依赖。
在项目根目录新建 index.js。
// index.js
const express = require('express')
const app = express() // app 就是一个应用
app.use(function (req, res, next) { // 此方法就是一个最基本的中间件,它在每次请求之前打印时间
console.log('Time:', Date.now())
next()
})
app.get('/hello', (req, res) => { // "GET /hello" 就是一个路由
res.send('Hello World!')
})
app.listen(3000) // 当app监听一个端口时,它就可以被访问了
在浏览器里访问 http://localhost:3000/hello
,你可以立刻看到如下效果:
[浏览器端]
并且,控制台上也会出现当次访问的时间戳打印:
很好,你已经学会了 express
最简单的用法,你又掌握了一项技能,接下来,我们就需要用到一款来自社区的 “中间件” 了。
3.2 利用 http-proxy-middleware
中间件
express
的生态无比繁荣,在 github
上,你可以找到各种场景需要用到的 demo
或者中间件。
http-proxy-middleware
正是这样一个中间件。
它的npm官网:www.npmjs.com/package/htt…
官网中的自我介绍:
让 node.js 代理变得容易;作为中间件,它支持
connect
, express
、browser-sync
等诸多框架。
很显然,http-proxy-middleware
是为 “请求代理” 而生的插件,它不仅仅支持 express
,还支持更多场景,但本文我们只需要知道它在 epxress
中如何使用即可。
在刚才的工程基础上,先删掉根目录下的 index.js
按以下步骤进行操作:
- 执行
yarn add http-proxy-middleware
- 在
package.json
的scripts
下新建一个名为dev
的脚本,内容为:node src/index.mjs
"scripts": {
"dev": "node src/index.mjs"
}
- 新建 src/index.mjs,并初始化内容:
import express from 'express'
import { createProxyMiddleware } from 'http-proxy-middleware';
const app = express();
// --------- 看这里 ↓ -------------------------
app.use('/', createProxyMiddleware({
target: 'https://***.cn/',
changeOrigin: true
}));
// --------- 看这里 ↑ -------------------------
app.listen(3000);
以上代码的关键就在注释中着重强调的区域,为路由 '/' 下的所有请求指定使用一个中间件,代理到 'https://***.cn/'
地址上去。
因此,当你在浏览器中输入 http://localhost:3000
时,你会惊奇地发现,你打开了掘金的主页:(虽然,因为掘金官网 “防盗链” 机制的存在,你无法刷出文章列表。)
ok,你已经学会了如何使用 http-proxy-middleware
在 express
应用创建一个简单的代理!
是不是很容易?
接下来,我们要结合 实际开发场景 让它接管我们的 请求代理!
3.3 在 SPA
应用中实际使用
我先假设,你的项目中有一个
vue.js
or react.js
的项目,它占用了 9527
端口。
通常情况下,SPA
应用中都会采用这样的方法区分 “xhr
请求” 和 “前端资源请求”。
- 设置一个常量字符串,比如
/prod-api
- 在你的所有
xhr
请求中,配置前缀为/prod-api
,比如你想请求/user/list
,那就将其统一定义为/prod-api/user/list
- 在
webpack devServer
中,识别到以/prod-api
开头,就认定为xhr
请求,代理到服务端;否则就认定为前端资源请求。 - 在所有
/prod-api
请求代理到服务端之前,通过rewrite
方法去除/prod-api
部分,向服务器发送http://www.dev-server.com/user/list
请求。(此处可根据服务端情况配置灵活调整)
只要你想明白了以上过程,你也就能清晰地撰写 “轻量代理层” 中间的逻辑了。
- 以
/prod-api
开头的请求,将其代理到服务器上。 - 其他请求,代理到
http://localhost:9527
上。
修改 src/index.mjs
中的代码为:
import express from 'express'
import { createProxyMiddleware } from 'http-proxy-middleware';
const app = express();
// --------- 看这里 ↓ -------------------------
// 凡是以 '/prod-api' 开头的都认定为 xhr 请求
app.use('/prod-api', createProxyMiddleware({
target: 'https://dev.my-server.com/', // 这一行是你的服务端地址
changeOrigin: true,
pathRewrite: {
'^/prod-api': '' // 在向服务端发起请求时,去掉标识xhr的前缀
}
}));
// 其他请求则认定为前端资源请求,如:html/js/css 等
app.use('/', createProxyMiddleware({
target: 'http://localhost:9527/',
changeOrigin: true
}));
// --------- 看这里 ↑ -------------------------
app.listen(3000);
以上配置已经可以顺利运行我手中的几个项目了,你要不要赶紧上手制作一个,看看你手头的项目是否可以顺利运行起来?
当我们想切换接口服务器时,只需要修改 => 停止 => 重启。
切换顺滑如丝,再也不用等待漫长的构建过程了!
3.3 让其在本机全局命令生效
当我们完成上面的步骤后,实际上 “轻量代理层” 就已经可以在日常开发中用起来了,但每次都要找到 lightweight-proxy
的文件夹,运行命令行,属实麻烦,有啥更好的办法吗?
当然!我们把它注册到全局命令中去!
只需要两步:
- 在
package.json
文件中定义bin
属性。 - 执行
npm link
3.3.1 增加 bin
属性:
package.json
"bin": {
"cc-proxy": "./src/index.mjs"
}
其中 cc-proxy
是我随便乱取的,你也可以根据自己的喜好,将其改为 lol-proxy
、i-love-study
等各种奇形怪状的名称,都行🤣。
3.3.2 执行 npm link
在项目的根目录下执行如下指令:
npm link
# 不知道为啥,yarn link无法产生正确的指令,所以必须使用npm link
执行完以上两步之后,尝试在任何其他位置打开一个全新的 cmd
或者 powershell
命令行(对于 windows
用户); mac
用户应该也需要打开一个新命令行。
然后执行以下命令:
cc-proxy
嘿!它生效了!
现在,我们的电脑上正式多了一个专属的 “轻量代理层”!
四、还能做什么?
当然,本文到这里,已经实现了我们初步的目标,那么,我们还可以在这个基础上做些什么呢?
让我们畅想一下:
- 支持 https
- 将工具打包成
npm
包,发布到私有npm
源上让同事也能使用? - 提供配置化的能力,在不同的项目通过配置实现不同的效果?
- more...
五、源码
细心的春哥,已经把少的可怜的源码上传到了 github
,地址:
github.com/zhangshichu…
六、结束语
我是春哥
。
大龄前端打工仔,依然在努力学习。
我的目标是给大家分享最实用、最有用的知识点,希望大家都可以早早下班,并可以飞速完成工作,淡定摸鱼🐟。
你可以在公众号里找到我:前端要摸鱼
。