不能用 Fiber 自己拼 http.Client 转发请求,因 Body 会丢、Header 会漏、连接不复用、流式响应卡死;应使用 httputil.NewSingleHostReverseProxy 实现可靠转发,并正确处理 CONNECT 隧道、路径重写、头过滤与连接池配置。

别用 Fiber 自己拼 http.Client 转发请求——Body 会丢、Header 会漏、连接不复用、流式响应(SSE/大文件)直接卡死。Fiber 是路由和中间件框架,不是反向代理实现;真正扛转发的,得靠 Go 标准库的 httputil.NewSingleHostReverseProxy。
为什么不能在 Fiber 中直接用 c.Body() + http.DefaultClient.Do()
常见翻车现场:你在中间件里调了一次 io.ReadAll(c.Request().Body) 做鉴权或日志,再传给 http.Client 时,req.Body 已是空的——后端收不到 POST 数据,但状态码还是 200。
-
c.Body()和c.Request().Body是同一底层 reader,读一次就 EOF,不可重放 -
http.DefaultClient.Transport.MaxIdleConnsPerHost默认是 2,压测几秒就触发dial tcp: too many open files - 没手动过滤 hop-by-hop 头(如
Connection、Keep-Alive),后端可能返回 400 或 502 - 没重写
req.URL.Host和req.Host,HTTPS 后端因 Host 不匹配返回 403
如何把 Fiber 当“前置网关”,把转发交给 httputil.ReverseProxy
正确分工:Fiber 负责路径匹配、鉴权、限流、打标;httputil.NewSingleHostReverseProxy 负责可靠转发。两者组合才稳。
- 每个后端服务配独立的
*httputil.ReverseProxy实例,避免连接池混用 - 用
http.StripPrefix剥离路由前缀(比如/api/v1),再交由 proxy 处理,否则后端收到的是带前缀的路径 - 显式设置
Director函数:重写req.URL.Scheme、req.URL.Host、req.Host,同步更新req.URL.RawPath(含中文路径必须处理) - 在
Director里手动加X-Forwarded-For:req.Header.Set("X-Forwarded-For", c.IP())
CONNECT 方法必须单独 hijack,不能走 HTTP 流程
浏览器访问 HTTPS 站点前先发 CONNECT google.com:443,这不是 HTTP 请求,而是 TCP 隧道协商指令。任何试图用 proxy.ServeHTTP 或 http.ServeHTTP 处理它的做法都会失败——你会看到浏览器卡死、超时,或后端返回 405。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 在 Fiber 的 handler 最顶层判断:
if c.Method() == "CONNECT" { ... } - 立即返回
"HTTP/1.1 200 Connection Established\r\n\r\n",然后调c.Context().Hijack()获取原始net.Conn - 用
net.DialTimeout("tcp", c.Get("Host"), 10*time.Second)连后端,不是http.Get - 启动两个
io.Copy协程双向转发字节流,并调conn.SetDeadline(time.Time{})关闭超时,否则拷贝中途会被中断
Fiber 中间件里调 proxy.ServeHTTP 的关键细节
别在中间件里自己构造 http.Request 再丢给 client;要让 Fiber 的 *fiber.Ctx 和标准库的 http.ResponseWriter / *http.Request 对接上。
- 用
c.Context().Response().Writer()获取http.ResponseWriter(注意类型断言) - 用
c.Context().Request().URI().String()构造新*url.URL,再转成*http.Request,但更推荐直接复用c.Context().Request().Header和 body 流 - 转发前若已读过 body(如鉴权解析 JSON),必须重建:
req.Body = io.NopCloser(bytes.NewReader(data)),并设req.ContentLength = int64(len(data)) - Transport 必须独立配置:超时、MaxIdleConns、MaxIdleConnsPerHost、TLS 设置——不要共用
http.DefaultTransport
最易被忽略的点:CONNECT 隧道建立后,任意一端连接关闭时,另一端必须显式关闭,否则连接泄漏;Director 函数里改了 req.URL.Path 却忘了同步 req.URL.RawPath,含中文路径直接 400;还有,Fiber 的 c.Body() 返回的是原始字节,不是解析后的结构体——想取 JSON 字段,得自己 json.Unmarshal 或用 c.BodyParser(&v),且 v 必须是指针。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










