
本文介绍如何将远程 HTTP GET 请求的响应(包括状态码、头信息和响应体)完整、准确地透传至 Go 的 http.ResponseWriter,实现轻量级反向代理效果,避免直接写入原始 dump 数据导致的渲染异常。
本文介绍如何将远程 http get 请求的响应(包括状态码、头信息和响应体)完整、准确地透传至 go 的 `http.responsewriter`,实现轻量级反向代理效果,避免直接写入原始 dump 数据导致的渲染异常。
直接使用 httputil.DumpResponse 将响应序列化为字节流并调用 w.Write() 会导致浏览器接收到的是纯文本格式的 HTTP 报文(含状态行、头字段和原始 body),而非可渲染的响应——这正是你看到 HTML 源码而非 Google 首页的原因。真正的代理需结构化透传:逐项复制响应头、设置状态码,再流式转发响应体。
以下是推荐的实现方式(含错误处理与资源清理):
package main
import (
"io"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
// 发起上游 GET 请求
resp, err := http.Get("https://google.com")
if err != nil {
http.Error(w, "Failed to fetch upstream: "+err.Error(), http.StatusBadGateway)
return
}
defer resp.Body.Close() // 关键:防止连接泄漏
// 1. 复制响应头(注意:Header 是 map[string][]string)
for name, values := range resp.Header {
w.Header()[name] = values
}
// 2. 设置状态码(必须在 WriteHeader 后、写 body 前调用)
w.WriteHeader(resp.StatusCode)
// 3. 流式拷贝响应体(高效且内存友好)
_, err = io.Copy(w, resp.Body)
if err != nil {
// io.Copy 失败通常意味着客户端断连,可选择忽略或记录日志
return
}
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
⚠️ 重要注意事项:
-
禁止省略
defer resp.Body.Close():否则 HTTP 连接无法复用,极易触发too many open files错误; -
不要手动修改
Content-Length:io.Copy会自动处理,且多数现代服务(如 Google)使用Transfer-Encoding: chunked,显式设Content-Length反而引发错误; -
跨域与安全头:上游响应中的
Set-Cookie、Strict-Transport-Security等敏感头可能被浏览器拒绝(尤其当代理域名与目标不一致时),生产环境需评估是否过滤或重写; -
生产级替代方案:若需健壮代理(支持重试、超时、负载均衡、缓冲控制等),请直接使用标准库的
net/http/httputil.NewSingleHostReverseProxy,例如:
proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: "https", Host: "google.com"})
http.Handle("/", proxy)
该方式已内置头处理、连接池管理及错误恢复机制,是构建真实代理服务的首选。










