mirror of
https://github.com/RuoJi6/ARTEX.git
synced 2026-10-28 01:03:50 +08:00
fix: Repeater 一次性传输取消阶段超时并回收空闲连接,错误时不重复显示(Codex gpt-6.1-sol)(#42)
This commit is contained in:
1 parent
91d2fe456c
commit
32d732bdfa
3 files changed
+79
-1
No files matched your search
@@ -167,6 +167,9 @@ func (s *Server) sendRepeater(w http.ResponseWriter, r *http.Request) {
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
// Each send owns its transport. Registered before Body.Close so idle
|
||||||
|
// connections are released after the response body, including on errors.
|
||||||
|
defer client.CloseIdleConnections()
|
||||||
|
|
||||||
started := time.Now()
|
started := time.Now()
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
@@ -235,6 +238,9 @@ func (s *Server) repeaterTransport(useRecordingProxy, insecure bool) http.RoundT
|
|||||||
// Keep response bytes faithful and make body_bytes match what was received;
|
// Keep response bytes faithful and make body_bytes match what was received;
|
||||||
// transparent gzip decompression would otherwise obscure both values.
|
// transparent gzip decompression would otherwise obscure both values.
|
||||||
transport.DisableCompression = true
|
transport.DisableCompression = true
|
||||||
|
// Use the request's timeout_ms budget instead of inherited phase deadlines.
|
||||||
|
transport.TLSHandshakeTimeout = 0
|
||||||
|
transport.ExpectContinueTimeout = 0
|
||||||
if !proxied {
|
if !proxied {
|
||||||
// Clear a custom TLS dialer before installing the guarded dialer; when
|
// Clear a custom TLS dialer before installing the guarded dialer; when
|
||||||
// DialTLSContext is set, net/http bypasses DialContext entirely.
|
// DialTLSContext is set, net/http bypasses DialContext entirely.
|
||||||
|
|||||||
@@ -161,6 +161,78 @@ func TestRepeaterDirectHTTP2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRepeaterTLSHandshakeUsesRequestTimeout(t *testing.T) {
|
||||||
|
t.Setenv(repeaterAllowLocalEnv, "1")
|
||||||
|
// Scale down the inherited timeout so a slow handshake can be tested
|
||||||
|
// without waiting for the default transport's ten-second deadline.
|
||||||
|
base := http.DefaultTransport
|
||||||
|
short := base.(*http.Transport).Clone()
|
||||||
|
short.TLSHandshakeTimeout = 10 * time.Millisecond
|
||||||
|
http.DefaultTransport = short
|
||||||
|
defer func() { http.DefaultTransport = base }()
|
||||||
|
defer short.CloseIdleConnections()
|
||||||
|
|
||||||
|
srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, _ = io.WriteString(w, "handshake completed")
|
||||||
|
}))
|
||||||
|
srv.TLS = &tls.Config{GetConfigForClient: func(*tls.ClientHelloInfo) (*tls.Config, error) {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
return nil, nil
|
||||||
|
}}
|
||||||
|
srv.StartTLS()
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
code, out, raw := repeaterTestSend(t, &Server{}, map[string]any{
|
||||||
|
"url": srv.URL, "use_recording_proxy": false, "insecure_skip_verify": true, "timeout_ms": 1000,
|
||||||
|
})
|
||||||
|
if code != http.StatusOK || out.Error != "" || out.Status != http.StatusOK || out.Body != "handshake completed" {
|
||||||
|
t.Fatalf("handshake within request budget failed: status=%d raw=%s", code, raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRepeaterClosesIdleConnections(t *testing.T) {
|
||||||
|
t.Setenv(repeaterAllowLocalEnv, "1")
|
||||||
|
for _, mode := range []string{"direct", "http2", "global proxy", "recording proxy fallback"} {
|
||||||
|
t.Run(mode, func(t *testing.T) {
|
||||||
|
closed := make(chan struct{}, 1)
|
||||||
|
srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, _ = io.WriteString(w, "response")
|
||||||
|
}))
|
||||||
|
srv.Config.ConnState = func(_ net.Conn, state http.ConnState) {
|
||||||
|
if state == http.StateClosed {
|
||||||
|
closed <- struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mode == "http2" {
|
||||||
|
srv.EnableHTTP2 = true
|
||||||
|
srv.StartTLS()
|
||||||
|
} else {
|
||||||
|
srv.Start()
|
||||||
|
}
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
s := &Server{}
|
||||||
|
payload := map[string]any{"url": srv.URL, "use_recording_proxy": false, "insecure_skip_verify": true}
|
||||||
|
if mode == "global proxy" || mode == "recording proxy fallback" {
|
||||||
|
s.m = &Manager{globalProxy: srv.URL}
|
||||||
|
payload["url"] = "http://target.example/"
|
||||||
|
if mode == "recording proxy fallback" {
|
||||||
|
delete(payload, "use_recording_proxy")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code, out, raw := repeaterTestSend(t, s, payload)
|
||||||
|
if code != http.StatusOK || out.Error != "" || out.Status != http.StatusOK || out.Body != "response" {
|
||||||
|
t.Fatalf("status=%d raw=%s", code, raw)
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-closed:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("connection still open after sendRepeater returned")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRepeaterRoundTripHeadersAndBody(t *testing.T) {
|
func TestRepeaterRoundTripHeadersAndBody(t *testing.T) {
|
||||||
t.Setenv(repeaterAllowLocalEnv, "1")
|
t.Setenv(repeaterAllowLocalEnv, "1")
|
||||||
var got http.Request
|
var got http.Request
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export function formatHeaders(headers: Record<string, string[]> | Record<string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function buildRawResponse(response: RepeaterSendResponse): string {
|
export function buildRawResponse(response: RepeaterSendResponse): string {
|
||||||
if (!response.status && response.error) return `错误: ${response.error}`;
|
if (!response.status) return "";
|
||||||
const statusLine = `${response.proto || "HTTP/1.1"} ${response.status_text || response.status}`.trim();
|
const statusLine = `${response.proto || "HTTP/1.1"} ${response.status_text || response.status}`.trim();
|
||||||
const headers = formatHeaders(response.headers);
|
const headers = formatHeaders(response.headers);
|
||||||
return `${statusLine}\n${headers}\n\n${response.body}`;
|
return `${statusLine}\n${headers}\n\n${response.body}`;
|
||||||
|
|||||||
Reference in new issue
Block a user