
本文介绍在 go 语言中实现 http 代理时,如何将解析后的 *http.request 对象完整、安全地写入下游连接(如 net.conn),重点讲解底层 req.write() 方法的使用、常见陷阱及最佳实践。
本文介绍在 go 语言中实现 http 代理时,如何将解析后的 *http.request 对象完整、安全地写入下游连接(如 net.conn),重点讲解底层 req.write() 方法的使用、常见陷阱及最佳实践。
在构建 HTTP 代理(如反向代理或中间人代理)时,一个典型流程是:从客户端连接读取请求 → 解析为 *http.Request → 修改(可选)→ 转发至远端服务器。你提到的 http.ReadRequest() 已完成第一步,但后续“如何把 req 写出去”才是关键——不能手动拼接字符串,也不应依赖中间件框架(如 Negroni)来“传递”请求对象,因为 Negroni 的 next(rw, r) 是在服务端 Handler 链内传递控制权,而非网络层转发。
真正面向 net.Conn 的转发,应使用 http.Request.Write() 方法:
// 假设已建立到远端服务器的连接
connToRemote, err := net.Dial("tcp", "example.com:80")
if err != nil {
log.Fatal(err)
}
// req 是通过 http.ReadRequest() 解析得到的 *http.Request
err = req.Write(connToRemote)
if err != nil {
log.Printf("Failed to write request: %v", err)
return
}
⚠️ 注意事项:
- req.Write() 会自动写入完整的 HTTP/1.1 请求行、所有 Header 和 Body(若 req.Body != nil 且未被读取过)。确保 req.Body 仍可读(例如未被 http.ReadRequest 提前消费);
- 若需修改请求(如重写 Host、添加 X-Forwarded-For),应在调用 Write() 前操作 req.URL, req.Host, req.Header 等字段;
- req.Write() 不处理 HTTP/2 或 TLS;若目标为 HTTPS,需使用 tls.Dial 并注意 SNI 设置;
- 对于大文件上传等流式 Body,建议设置 req.ContentLength 并避免 req.Body 为不可 rewind 的类型(如 bytes.Reader 安全,io.PipeReader 则需谨慎)。
✅ 推荐增强写法(带超时与错误处理):
connToRemote, err := net.DialTimeout("tcp", "backend:80", 5*time.Second)
if err != nil {
http.Error(w, "Upstream unreachable", http.StatusBadGateway)
return
}
defer connToRemote.Close()
// 设置写超时
connToRemote.SetWriteDeadline(time.Now().Add(30 * time.Second))
if err := req.Write(connToRemote); err != nil {
log.Printf("Write failed: %v", err)
http.Error(w, "Failed to forward request", http.StatusBadGateway)
return
}
总结:http.Request.Write(io.Writer) 是 Go 标准库为代理场景提供的核心方法,它精准对应 HTTP 协议规范,比手动序列化更可靠、更高效。Negroni 等中间件仅适用于服务端 Handler 链内逻辑编排,不解决网络层转发问题。掌握 Write() 的使用,是构建健壮 Go 代理服务的基础能力。











