Director函数必须重写req.URL.Path和req.Host,否则原始带前缀路径(如/api/v1/users)直接转发导致后端404或跳转到localhost;需用strings.TrimPrefix截断路径并显式设置req.URL.Scheme、req.URL.Host及req.Host。

Director 必须重写 URL.Path 和 URL.Host,否则请求原样转发
不重写 Director 函数,就等于把带前缀的原始路径(如 /api/v1/users)直接发给后端,而下游服务只认 /users ——结果全是 404 或重定向跳转到 http://localhost。这不是配置漏了,是代理逻辑根本没生效。
-
req.URL.Path必须用strings.TrimPrefix(req.URL.Path, "/api/v1")截断,别用正则替换或strings.Replace,否则/api/v1/user-profile可能被误切为/user-profile(漏掉中间横线) -
req.URL.Scheme和req.URL.Host必须显式赋值,不能依赖upstreamURL.String(),它可能丢掉 Scheme 或 Host 端口 -
req.Host也得同步设成上游地址(如"user-srv:8080"),否则下游做租户识别或 CORS 校验会失败
模糊匹配不是靠正则,而是靠路由层的前缀/精确匹配策略
Go 生态里没有“URI 模糊匹配”这种标准概念,所谓“模糊”,其实是不同路由库对路径匹配的宽松程度差异。你真正要选的是:用前缀匹配(http.ServeMux)、精确匹配(gorilla/mux),还是带通配和中间件链的(chi)。
-
http.ServeMux是前缀匹配,注册/api就会匹配/api、/api/users、/api/v1/health,但不匹配/apix;缺点是末尾斜杠处理诡异,容易触发 301 -
gorilla/mux默认精确匹配,/users/{id}不会匹配/users/123/posts,加正则约束(如{id:[0-9]+})才能控制粒度 -
chi支持混合模式:r.Get("/users/*", handler)是前缀,r.Get("/users/{id}", handler)是精确,且中间件可中断流程(c.AbortWithStatus(401)),更适合网关边界控制
重写时 X-Real-IP 和 hop-by-hop 头必须手动清理
即使路径和 Host 都改对了,如果透传了 Connection、Keep-Alive 这类 hop-by-hop 头,Nginx 或下游服务可能直接拒收请求,返回 502 或静默丢包。
-
req.Header.Set("X-Real-IP", getClientIP(req)):从X-Forwarded-For提取最左非私有 IP,别直接用req.RemoteAddr(容器里全是10.0.0.x) -
req.Header.Del("Connection")、req.Header.Del("Proxy-Authenticate")、req.Header.Del("Keep-Alive")这三项必须删,标准库不自动处理 - 若需保留原始 Host 给下游决策,用
req.Header.Set("X-Forwarded-Host", req.Host),而不是改req.Host后又想“恢复”
Transport 连接池不调优,重写再准也没法上线
Director 写得再严谨,如果 http.Transport 还用默认配置,DNS 缓存永久不刷新、空闲连接卡死、大文件上传卡在 30 秒超时——所有请求都会在转发层堆积,最终 OOM 或大面积 504。
立即学习“go语言免费学习笔记(深入)”;
-
MaxIdleConns设为1000,避免频繁建连;IdleConnTimeout设为90s,防止长连接占满 fd -
DialContext要设Timeout(建议5s)和KeepAlive(建议30s),否则内网延迟抖动就会拖垮整条链路 -
TLSHandshakeTimeout单独设(如5s),避免 TLS 握手慢导致后续请求全堵住
Director 函数里做阻塞操作(比如同步查 Redis 鉴权),这会让整个代理协程卡住——它不是“某次请求慢”,而是所有并发请求都排队等这个函数返回。


















