ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Slim源码解析(二):SNI多域路由、WebSocket透传与singleflight证书缓存的反代实现

Slim源码解析(二):SNI多域路由、WebSocket透传与singleflight证书缓存的反代实现 Slim源码解析二SNI多域路由、WebSocket透传与singleflight证书缓存的反代实现【免费下载链接】slimGive your localhost a local or public URL项目地址: https://gitcode.com/gh_mirrors/slim30/slimSlim 是一个轻量级 CLI 反向代理工具它能给 localhost 上的本地服务一键签发干净的 HTTPS 域名如myapp.test并支持把本地端口暴露为公网 URL。这篇文章深入拆解 Slim 反代内核的三大核心设计基于 SNI 的多域路由、零特殊处理的 WebSocket 透传以及用singleflight 防止证书生成风暴的缓存方案帮你读懂一个生产级本地反向代理的完整实现思路。一、整体架构一个反代服务多个本地域名Slim 的反代核心全部集中在 internal/proxy/ 目录。Server启动时会监听两个端口HTTP 端口只做一件事——302 跳转到 HTTPS见 server.goHTTPS 端口真正的反向代理入口通过tls.Config.GetCertificate回调按 SNI 动态选证书路由数据来自 internal/config/config.go 解析的.slim.yaml配置每个 domain 绑定一个默认端口还可以挂多条 path 路由services: - domain: myapp port: 3000 routes: - path: /api port: 8080配置支持热加载ReloadConfig重新读取 YAML 后调用applyConfig在锁保护下整体替换路由表和证书缓存server.go#L209-L261读请求走 RLock实现无锁粒度的平滑热更。二、SNI 多域路由一次握手选中正确的域名与证书浏览器发起 TLS 握手时会在 ClientHello 中带上 SNI目标域名。Slim 把这个值当作路由键在 getCertificate 中完成三步判断SNI 为空时兜底回退到配置里的第一个域名defaultConfiguredDomain避免纯 IP/端口访问直接失败白名单校验用knownDomainsmap 确认该域名已配置未配置的域名握手直接失败防止拿 Slim 的 CA 证书代理任意域名查缓存 → singleflight 兜底命中缓存直接返回未命中才走 singleflight 生成。singleflight 如何避免“证书风暴”假设 100 个并发请求同时首次访问myapp.test若不加控制会有 100 个 goroutine 各自生成 ECDSA 密钥对并写盘。Slim 用 golang.org/x/sync/singleflight 把同一域名的并发请求合并成一次val, err, _ : s.certGroup.Do(name, func() (any, error) { if tlsCert : s.cachedCertificate(name); tlsCert ! nil { return tlsCert, nil // 双重检查避免重复生成 } // 生成证书 → 加载 → 写入 certCache })注意这里的双重检查锁定进入 singleflight 闭包后先再查一次缓存——因为可能有前驱请求刚把证书写入缓存直接复用即可无需重复 IO。本地 CA 签发全程无外部依赖证书生成在 internal/cert/ 完成每台机器首次运行会生成自签 CA之后每个域名的 leaf 证书GenerateLeafCert都用 P-256 ECDSA 密钥签发有效期约 2 年同时把127.0.0.1和::1写进 IP SAN。EnsureLeafCert 会在剩余有效期不足 30 天时自动续签用户完全无感。三、域名路由器path 前缀匹配的边界细节拿到域名后domainRouter.match 负责在该域名的 path 路由里挑选上游端口。两个易踩坑的细节前缀按长度降序预排序server.go#L240-L242/api和/api/v2同时存在时更长、更精确的前缀优先命中边界校验防误匹配匹配/api前缀时要求请求路径等于前缀本身或前缀后紧跟/否则/apiv2会被/api错误拦截。入口 handler buildHandler 的处理顺序是normalizeHost归一化 Host去端口、去尾点、转小写→ 查路由表 → CORS 头注入预检请求直接 204 返回→ 选 handler → 用statusRecorder包住响应以记录状态码和耗时。四、上游反代一个 Transport 池化所有 localhost 连接每个「域名 端口」组合对应一个httputil.ReverseProxy实例构建逻辑见 newDomainProxyRewrite固定把请求打到localhost:port但保留原始Host头上游服务看到的域名不变ErrorHandler在上游不可达时渲染内嵌的 upstream_down.html 友好 502 页面而不是裸错误。所有 ReverseProxy共享同一个 TransportnewUpstreamTransportMaxIdleConns512、IdleConnTimeout2h——本地回环连接成本极低长连接复用可以显著降低首字节延迟。TLS 服务器同样设置IdleTimeout: 2h这正是长连接与 WebSocket 能稳定存活的关键。WebSocket 透传不写一行特殊代码为什么 Slim 的 WebSocket 路由slim start myapp --route /ws9000开箱即用因为httputil.ReverseProxy原生支持 Upgrade只要响应链路上的ResponseWriter实现了Flusher101 切换协议帧就能原样穿过。Slim 的 statusRecorder 特意补上了两个方法Flush()透传底层的流式刷新保证 SSE、分块传输不被缓冲Hijack()把裸连接接管能力透传给上游 writer覆盖需要直接操作 TCP 的协议场景。日志记录只包裹ServeHTTP前后不碰 body 流因此 WebSocket 升级后的双向帧流与反代日志互不干扰。五、公网隧道WebSocket 之上的二进制帧协议slim share的公网 URL 走另一条链路internal/tunnel/ 的客户端与远端隧道服务器建立 WebSocket 长连接。设计上同样值得借鉴心跳保活每 20 秒 Ping 一次client.go#L202-L215弱网下快速感知断连指数退避重连断线后从 1s 起翻倍、封顶 30s注册失败则终止避免空转client.go#L117-L171帧协议每个 HTTP 请求/响应序列化为二进制帧、带requestID多路复用protocol/protocol.go响应写入用互斥锁串行化保证帧边界完整TTL 到期服务器以自定义关闭码 4000 通知隧道过期客户端优雅退出。本地侧健康检查见 internal/proxy/health.goCheckUpstreams用信号量并发上限 16并行探测各上游端口WaitForUpstream以 200ms 轮询等待服务起来让slim start的体验始终顺滑。六、值得抄进你工具箱的三个模式模式Slim 的做法适用场景singleflight 双重检查合并同 key 的昂贵初始化证书签发、缓存预热、资源加载整体替换 RWMutex热加载时构建新 map 再原子切换配置热更、规则引擎双监听 errCh 聚合HTTP/HTTPS 两个 Serve 并发跑首个真实错误胜出双协议网关的优雅退出总结Slim 用不到千行代码实现了一个教科书级的本地反向代理SNI 回调串联白名单校验与 singleflight 证书缓存path 路由靠预排序前缀树精确匹配WebSocket 则靠 Flusher/Hijack 透传零成本落地。如果你想给自己的项目加一个本地多域反代或公网隧道能力internal/proxy/ 与 internal/tunnel/ 是最直接的可参考实现。【免费下载链接】slimGive your localhost a local or public URL项目地址: https://gitcode.com/gh_mirrors/slim30/slim创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表