// HTTP 穿透测试:验证"隧道模式全用 tcp"成立——真实 HTTP 服务(含 keep-alive、 // 大 body)经纯 TCP 隧道完整可用,无需 zonat 的 http 反向代理模式。 package e2e import ( "bytes" "crypto/rand" "encoding/json" "io" "net/http" "net/http/httptest" "strconv" "testing" "time" "git.zeroonesoft.cn/golib/zonat/agent" "git.zeroonesoft.cn/golib/zonat/internal/node" ) func startHTTPServer(t *testing.T) *httptest.Server { t.Helper() mux := http.NewServeMux() mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"hello": "zonat", "via": "tcp-tunnel"}) }) mux.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "POST only", http.StatusMethodNotAllowed) return } body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Write(body) }) srv := httptest.NewServer(mux) t.Cleanup(srv.Close) return srv } // registerHTTP 把 httptest.Server 登记为一条隧道,返回公网端口 func registerHTTP(t *testing.T, n *node.Node, a *agent.Agent, srv *httptest.Server, id string) int { t.Helper() port, err := a.RegisterTunnel(buildTunnel(t, n, a, id, srv.Listener.Addr().String(), 0)) if err != nil { t.Fatal(err) } return port } func TestHTTPGetPostThroughTunnel(t *testing.T) { srv := startHTTPServer(t) n, a := startEnv(t) port := registerHTTP(t, n, a, srv, "http-tunnel") base := "http://127.0.0.1:" + strconv.Itoa(port) // GET resp, err := http.Get(base + "/hello") if err != nil { t.Fatal(err) } var body map[string]string if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { t.Fatal(err) } resp.Body.Close() if resp.StatusCode != http.StatusOK || body["hello"] != "zonat" { t.Fatalf("GET /hello 异常: status=%d body=%v", resp.StatusCode, body) } // POST 大 body(1MB,跨 smux 多帧) payload := make([]byte, 1<<20) if _, err := rand.Read(payload); err != nil { t.Fatal(err) } resp2, err := http.Post(base+"/echo", "application/octet-stream", bytes.NewReader(payload)) if err != nil { t.Fatal(err) } got, err := io.ReadAll(resp2.Body) resp2.Body.Close() if err != nil { t.Fatal(err) } if resp2.StatusCode != http.StatusOK || !bytes.Equal(got, payload) { t.Fatalf("POST /echo 异常: status=%d len(got)=%d", resp2.StatusCode, len(got)) } } func TestHTTPKeepAliveThroughTunnel(t *testing.T) { srv := startHTTPServer(t) n, a := startEnv(t) port := registerHTTP(t, n, a, srv, "http-ka") // 同一 client 复用 TCP 连接(keep-alive),10 次请求走同一条隧道流 client := &http.Client{Timeout: 10 * time.Second} for i := 0; i < 10; i++ { resp, err := client.Get("http://127.0.0.1:" + strconv.Itoa(port) + "/hello") if err != nil { t.Fatal(err) } resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("第 %d 次请求异常: %d", i+1, resp.StatusCode) } } }