Go ReverseProxy 代理示例

发布时间: 更新时间: 总字数:1893 阅读时间:4m 作者: IP上海 分享 网址

验证当 golang httputil.ReverseProxy 代理的后端服务(Backend Server)没有启动或无法连接时,发往代理的请求是否会被代理服务缓存,等待后端恢复后再转发?结论是:不会。

核心问题分析

httputil.ReverseProxy 本质上是一个 流式 的反向代理。它的设计目标是高效地将客户端的请求向后端服务器,并将后端服务器的响应回客户端。它本身 不包含 任何请求缓存、排队或重试的逻辑。

当一个请求到达 ReverseProxy 时,它会立即尝试与后端服务器建立连接并转发该请求。如果此时后端服务不存在或无法建立连接(例如,端口未监听,网络不通),TCP 连接会失败。ReverseProxy 会将这个连接失败的错误视为一次失败的请求,并立即向客户端返回一个错误响应,通常是 502 Bad Gateway

这个请求的处理流程在此时就已经结束了。ReverseProxy 不会保存这个失败的请求。当后端服务恢复后,ReverseProxy 也不会记起之前失败的请求并重新发送它们。只有新的、后续的客户端请求才会被成功转发到已恢复的后端服务。

实验验证

为了证明这一点,我们将搭建一个简单的场景:

  1. 一个反向代理服务 (proxy_server.go):监听在 localhost:8080,它会把所有请求转发到 localhost:9090
  2. 一个后端服务 (backend_server.go):监听在 localhost:9090,但我们稍后会手动启动和停止它。
  3. 使用 curl 作为客户端来发送请求。

第 1 步:编写反向代理服务 (proxy_server.go)

这个服务使用了 httputil.NewSingleHostReverseProxy 来创建一个基本的反向代理。

// proxy_server.go
package main

import (
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
)

func main() {
	// 定义后端服务的地址
	backendURL, err := url.Parse("http://localhost:9090")
	if err != nil {
		log.Fatalf("无法解析后端 URL: %v", err)
	}

	// 创建一个新的反向代理
	proxy := httputil.NewSingleHostReverseProxy(backendURL)

	// 自定义 Director 来打印请求日志,以便观察
	originalDirector := proxy.Director
	proxy.Director = func(req *http.Request) {
		log.Printf("接收到请求,将转发到后端: %s%s", req.Host, req.URL.Path)
		originalDirector(req) // 执行默认的 Director 逻辑
	}

	// 代理本身就是一个 http.Handler
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		proxy.ServeHTTP(w, r)
	})

	log.Println("反向代理服务器正在启动,监听地址 http://localhost:8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatalf("代理服务器启动失败: %v", err)
	}
}

第 2 步:编写后端 Web 服务 (backend_server.go)

这是一个非常简单的 HTTP 服务器,只是为了验证代理是否能成功转发。

// backend_server.go
package main

import (
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		log.Println("后端服务接收到请求")
		w.Header().Set("Content-Type", "text/plain")
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("你好,这里是后端服务!请求处理成功。"))
	})

	log.Println("后端 Web 服务器正在启动,监听地址 http://localhost:9090")
	if err := http.ListenAndServe(":9090", nil); err != nil {
		log.Fatalf("后端服务器启动失败: %v", err)
	}
}

第 3 步:使用 curl 构造请求并进行验证

现在我们开始实验。请打开两个终端窗口。

场景一:正常情况(后端服务正在运行)

  1. 在终端 A 中,启动后端服务:

    go run backend_server.go
    

    你应该会看到输出:后端 Web 服务器正在启动,监听地址 http://localhost:9090

  2. 在终端 B 中,启动我们的代理服务:

    go run proxy_server.go
    

    你应该会看到输出:反向代理服务器正在启动,监听地址 http://localhost:8080

  3. 在第三个终端 C 中,使用 curl 向代理发送请求:

    curl http://localhost:8080/
    
    # 或大文件测试
    curl -XPOST -T big-file.iso http://localhost:8080
    
    • curl 的输出 (客户端视角):
      你好,这里是后端服务!请求处理成功。
      
    • 代理服务的日志 (终端 B):
      接收到请求,将转发到后端: localhost:8080/
      
    • 后端服务的日志 (终端 A):
      后端服务接收到请求
      

    结论: 一切正常,代理成功将请求转发给了后端。

场景二:关键验证(后端服务未建立)

  1. 在终端 A 中,按 Ctrl + C 停止后端服务。现在 localhost:9090 没有任何服务在监听。

  2. 代理服务(终端 B)保持运行。

  3. 在终端 C 中,再次使用 curl 向代理发送请求。这次我们加上 -v 选项来查看详细的 HTTP 交互过程。

    curl -v http://localhost:8080/
    
    • curl 的输出 (客户端视角): 你会看到类似下面的内容,关键是 HTTP/1.1 502 Bad Gateway
      * Trying 127.0.0.1:8080...
      * Connected to localhost (127.0.0.1) port 8080 (#0)
      > GET / HTTP/1.1
      > Host: localhost:8080
      > User-Agent: curl/7.81.0
      > Accept: */*
      >
      * Request completely sent off
      < HTTP/1.1 502 Bad Gateway
      < Content-Length: 0
      <
      * Connection #0 to host localhost left intact
      
    • 代理服务的日志 (终端 B):
      接收到请求,将转发到后端: localhost:8080/
      http: proxy error: dial tcp 127.0.0.1:9090: connect: connection refused
      

    分析:

    • 代理服务确实收到了请求(日志显示 接收到请求...)。
    • 然后它立即尝试连接 localhost:9090,但连接被拒绝 (connection refused)。
    • 这个错误被代理捕获 (http: proxy error: ...)。
    • 代理 立即 向客户端 curl 返回了 502 Bad Gateway 错误,并将底层错误信息作为响应体发送了回去。
    • 请求处理流程到此结束。请求没有被任何地方缓存或保留。

场景三:后端服务恢复

  1. 在终端 A 中,重新启动后端服务:

    go run backend_server.go
    
  2. 代理服务(终端 B)仍然在运行。

  3. 在终端 C 中,我们发送一个 新的 请求:

    curl http://localhost:8080/a-new-request
    
    • curl 的输出 (客户端视角):
      你好,这里是后端服务!请求处理成功。
      
    • 代理服务的日志 (终端 B):
      接收到请求,将转发到后端: localhost:8080/a-new-request
      
    • 后端服务的日志 (终端 A):
      后端服务接收到请求
      

    分析:

    • 当后端服务恢复后,新的请求可以被正常处理。
    • 但是,上一个失败的请求 (/) 并没有被自动重发。它已经失败并永远消失了。
本文总阅读量 次 本站总访问量 次 本站总访客数
Home Archives Categories Tags Statistics