BackIcon使用自建 PoC 进行 API 代理

2024年7月24日

Openai logomark

Hamster1963

国内未备案的服务器在使用上一直很棘手,不是在域名备案上,就是在暴露自建服务的繁琐上。

在没有域名备案的情况下,可以通过 IP 加端口的方式来暴露服务,或使用第三方的代理或隧道来转发服务。

1. IP + 端口

在这种方式下,调用方通过 IP:Port 的方式直接请求源站服务。

Untitled

对于 IP 加端口的方式,除去安全性的问题,对于 HTTPS 的页面来说,还需要为 IP 配置可用的 SSL 证书,而目前市面上的 IP 证书申请起来比较费时费力。

目前免费的 IP 证书中,仅有 ZeroSSL 可签发的三个免费证书,而后续的续费都需要不低的费用。

alt:ZeroSSL 最基础的套餐费用

ZeroSSL 最基础的套餐费用

同时对于 IP 证书来说,在可用性上也会略打折扣,可能会被某些浏览器或插件判定为不可信的站点,影响到服务的可用性。

因此不使用域名,直接通过 IP:Port 的方式进行服务暴露是不太实用的。

2. 直接域名绑定

在没有备案的情况下,无论是 HTTP 还是 HTTPS 下,通过域名访问服务器都会被云服务商检测并拦截。

Untitled

因此直接将域名解析到国内云服务器的方式,在未备案的服务器上不可用。

而对于反向代理,目前云服务商也加大了检测力度,因此简单的 Nginx 反向代理也会被拦截。

3. 第三方代理

因此如果在未备案的情况下,想通过域名的方式进行接入,就需要一个“中转”。

中转的方式很多,比如:

  1. Vercel Rewrites
  2. Next.js Route Handlers

alt:利用 Vercel 和 Next.js 来进行请求转发

利用 Vercel 和 Next.js 来进行请求转发

  1. Cloudflare Tunnel

对于这些服务,原理都与反向代理大致相同,先向数据包发往一个代理节点,代理节点与源服务器交换数据后进行返回。

Frame 4.png

但这些服务在国内都存在一定的问题。

对于 Vercel,最大的缺点在于费用,无论是 Vercel Rewrites 还是 Next.js 的 Route Handlers,使用的次数都会进行计费,虽然 Hobby 计划中包含着一定的使用量,但是长远来说,一旦使用超过限制,只能通过付费或者升级计划的方式来恢复服务。

而最近 Vercel 不断改动的计费规则,也会使得这个方案的可行性日渐降低。

alt:Vercel 仪表盘中的计费用量面板

Vercel 仪表盘中的计费用量面板

而对于 Tunnel,赛博佛祖 Cloudflare 一如既往地让 Tunnel 可以免费使用。

但是,虽然在文档中描述 Tunnel 也采用了 Cloudflare 的边缘网络加速技术,但目前从实际测试与论坛调查来看,大陆方向几乎全部的请求都由美西的节点进行代理,这使得延迟成为了很大的问题。

同时 cloudflare 的连通性在大陆方向也日渐降低,加上代理节点集中在美西,转发效率实在不太高。

自建 PoC

在经过多次尝试降低 tunnel 的延迟和其他的中转方案后,突然想到,可以使用家宽的方式来进行服务的转发。

Frame 5.png

目前博客首页的播放组件背后就采用了这套自建的转发方案,实测就算在延迟 Double 的情况下,仍有不错的延迟表现。

(主要原因是国内延迟本来就比较低)

Untitled

在构建这套简易的服务时,我希望将短链系统和代理结合在一起,服务通过短链找到原始请求 API ,并进行代理访问,返回最终的请求结果。

在架构上可以看到,最核心的服务需要部署在一台需要 有公网 IP 且无需备案 的机器上。

接下来就从

  1. 短链系统
  2. 代理请求
  3. PoC 前端界面

简单从前后端介绍一下项目的组成。

短链系统

短链系统在大型系统中属于是基础服务一般的存在,因此在互联网上其实已经有许多关于如何构建短链系统的最佳实践。

Design a URL Shortener

在常见的短链系统中,客户端通过一个短链访问服务器,而服务器返还一个重定向的原始链接。

URL重定向:给定一个短的URL => 重定向到原来的URL

而其中的短链生成我们通常通过一个哈希函数,将长链接映射到一串短字符串中。

关于一致性 hash 设计,也有许多的最佳实践。

Design Consistent Hashing

而在本系统中,我们使用一个简单的随机字符串函数进行短链 hash 的生成。

ray-so-export.png

这将会生成一个长度为 8 位的字符串作为短链中的 Hash。

完整的短链结构如下:

Frame 6.png

与常见的短链系统相同,从短链获取真实链接的延迟取决于存储的方式。

在 PoC 中,最终的数据存储在 MySQL 中,而从数据库获取键值对的结果往往也需要毫秒级别的时间,因此采用了内存缓存的方式将短链键值对存储。

在系统启动/短链操作这两个节点对缓存进行操作,在系统启动时,获取全部已启用短链存入缓存中,在对短链有增删的操作时也对缓存做对应的修改。

其中为了极致的速度,没有采用外部的例如 Redis 的缓存中间件,而是直接采用 gcache 作为缓存中间件,同时为了避免内存占用过大的问题,启动定时监测服务来确保内存占用在合理区间内。

Untitled

同时,针对短链在缓存中未命中的问题,采用gcache.GetOrSetFuncLock来确保短链可用性的最大化。

完整的短链请求架构图如下:

Frame 7.png

代理请求

与常见的短链系统不同,PoC 通过请求中的短链获取真实链接后,需要进行代理访问后再将结果返回。

避免被云服务商检测为使用了反向代理,因此需要直接使用 httpClient 来进行请求的访问。

核心代码如下:

Untitled

可见我们通过重写请求头来进行一些例如认证参数的传递,通过组装参数来进行其他方式的请求。

同时,为了更好监测每个短链的访问数据与情况,通过管道与 defer 结合的方式将请求日志传入到日志消费者进行后续的入库与分析处理。

Frame 9.png

在完成请求后,通过捕获代理请求将返回数据返回到客户端中,便完成了整个的短链请求流程。

PoC 前端页面

前端页面主要参考了 clerk 与 medusa 的 UI 风格,以功能性为核心进行构建。

Clerk

Untitled

Medusa UI

Untitled

整体 PoC 站点如下:

Untitled

站点使用 Next.js 进行构建,在权限控制方面,直接采用 session 来进行用户的认证与信息获取。

其中在数据获取部分,采用 SWR 配合 refreshInterval 来保持页面数据的不断更新。

ray-so-export (2).png

后言

构建这套“简陋单一”的系统的过程十分的开心,对于各种反向代理和网络协议的骚操作又有了许多的了解,同时在构建的过程中,参考了目前许多的后端与前端项目,十分感谢众多开源项目带来的灵感。

最后向 Next.js 的文档编写者致谢,感谢精美的配图带来的良好的阅读体验,因此也在此篇文章中尝试使用 Figma 照猫画虎地画了一些示意图,如有画的不清晰的地方,还望多见谅。