From da518d0d8842b9f951d3c33108a595ded4a08e9c Mon Sep 17 00:00:00 2001 From: Eljees <3.14hell@gmail.com> Date: Fri, 30 Oct 2026 23:41:37 +0300 Subject: [PATCH 1/4] HTTP inbound: Forward short 100 Continue responses promptly Avoid waiting beyond the status prefix before forwarding an interim response. A client waiting for Continue cannot send its body while the origin waits for that body. Add a regression that withholds the final response until the interim response is forwarded. Assisted-by: Codex Signed-off-by: Eljees <3.14hell@gmail.com> --- proxy/http/server.go | 4 ++-- proxy/http/server_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/proxy/http/server.go b/proxy/http/server.go index b89c67c0ed43..5d2b436214c9 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -312,8 +312,8 @@ func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, wri // Sometimes, server might send 1xx response to client // it should not be processed by http proxy handler, just forward it to client func readResponseAndHandle100Continue(r *bufio.Reader, req *http.Request, writer io.Writer) (*http.Response, error) { - // have a little look of response - peekBytes, err := r.Peek(56) + // A short 100 Continue response must reach the client before it sends the body. + peekBytes, err := r.Peek(12) if err == nil || err == bufio.ErrBufferFull { str := string(peekBytes) ResponseLine := strings.Split(str, "\r\n")[0] diff --git a/proxy/http/server_test.go b/proxy/http/server_test.go index 2578d5315e97..1ae91d00704a 100644 --- a/proxy/http/server_test.go +++ b/proxy/http/server_test.go @@ -47,3 +47,40 @@ func TestReadResponseAndHandle100ContinueForwardsAndParsesFinalResponse(t *testi t.Fatalf("expected 1xx response to be forwarded, got %q", forwarded.String()) } } + +type responseReaderFunc func([]byte) (int, error) + +func (f responseReaderFunc) Read(p []byte) (int, error) { return f(p) } + +func TestReadResponseAndHandle100ContinueForwardsBeforeFinalResponse(t *testing.T) { + const interim = "HTTP/1.1 100 Continue\r\n\r\n" + var forwarded bytes.Buffer + final := strings.NewReader("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello") + reader := io.MultiReader(strings.NewReader(interim), responseReaderFunc(func(p []byte) (int, error) { + // An origin waiting for the request body cannot send its final response + // until Continue has reached the client. + if forwarded.String() != interim { + return 0, io.ErrNoProgress + } + return final.Read(p) + })) + req, err := http.NewRequest("POST", "http://example.com/", nil) + if err != nil { + t.Fatal(err) + } + resp, err := readResponseAndHandle100Continue(bufio.NewReader(reader), req, &forwarded) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + t.Fatalf("final response status = %d, want %d", resp.StatusCode, http.StatusOK) + } + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } + if string(body) != "hello" { + t.Fatalf("response body = %q, want hello", body) + } +} From d442a5153fc69abc7d6a5aeaaa2551e2521254c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Sat, 31 Oct 2026 05:12:37 +0800 Subject: [PATCH 2/4] Remove obsolete test for 100 Continue response handling Removed the TestReadResponseAndHandle100ContinueForwardsBeforeFinalResponse test and related responseReaderFunc type. --- proxy/http/server_test.go | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/proxy/http/server_test.go b/proxy/http/server_test.go index 1ae91d00704a..2578d5315e97 100644 --- a/proxy/http/server_test.go +++ b/proxy/http/server_test.go @@ -47,40 +47,3 @@ func TestReadResponseAndHandle100ContinueForwardsAndParsesFinalResponse(t *testi t.Fatalf("expected 1xx response to be forwarded, got %q", forwarded.String()) } } - -type responseReaderFunc func([]byte) (int, error) - -func (f responseReaderFunc) Read(p []byte) (int, error) { return f(p) } - -func TestReadResponseAndHandle100ContinueForwardsBeforeFinalResponse(t *testing.T) { - const interim = "HTTP/1.1 100 Continue\r\n\r\n" - var forwarded bytes.Buffer - final := strings.NewReader("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello") - reader := io.MultiReader(strings.NewReader(interim), responseReaderFunc(func(p []byte) (int, error) { - // An origin waiting for the request body cannot send its final response - // until Continue has reached the client. - if forwarded.String() != interim { - return 0, io.ErrNoProgress - } - return final.Read(p) - })) - req, err := http.NewRequest("POST", "http://example.com/", nil) - if err != nil { - t.Fatal(err) - } - resp, err := readResponseAndHandle100Continue(bufio.NewReader(reader), req, &forwarded) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("final response status = %d, want %d", resp.StatusCode, http.StatusOK) - } - body, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - if string(body) != "hello" { - t.Fatalf("response body = %q, want hello", body) - } -} From 6ea2203de620d74784838b1b42f8bb1611bcb37c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Sat, 31 Oct 2026 05:13:11 +0800 Subject: [PATCH 3/4] Adjust peek size in readResponseAndHandle100Continue Updated the peek size for reading response in readResponseAndHandle100Continue function. --- proxy/http/server.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/proxy/http/server.go b/proxy/http/server.go index 5d2b436214c9..b3b960b61dd4 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -312,8 +312,9 @@ func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, wri // Sometimes, server might send 1xx response to client // it should not be processed by http proxy handler, just forward it to client func readResponseAndHandle100Continue(r *bufio.Reader, req *http.Request, writer io.Writer) (*http.Response, error) { - // A short 100 Continue response must reach the client before it sends the body. - peekBytes, err := r.Peek(12) + // have a little look of response + r.Peek(1) + peekBytes, err := r.Peek(min(56, r.Buffered())) if err == nil || err == bufio.ErrBufferFull { str := string(peekBytes) ResponseLine := strings.Split(str, "\r\n")[0] From be938667200f454c132908e2695ffd6b22e2a5f3 Mon Sep 17 00:00:00 2001 From: Eljees <3.14hell@gmail.com> Date: Sat, 31 Oct 2026 11:25:42 +0300 Subject: [PATCH 4/4] HTTP inbound: Read the status prefix before classifying a response A fragmented first read can contain less than the status prefix. Wait for that prefix before the adaptive buffered peek, so a 100 Continue response is forwarded before parsing the final response. Exercise fragmentation in the existing response test. Assisted-by: Codex Signed-off-by: Eljees <3.14hell@gmail.com> --- proxy/http/server.go | 4 ++-- proxy/http/server_test.go | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/proxy/http/server.go b/proxy/http/server.go index b3b960b61dd4..349ba7dde647 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -312,8 +312,8 @@ func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, wri // Sometimes, server might send 1xx response to client // it should not be processed by http proxy handler, just forward it to client func readResponseAndHandle100Continue(r *bufio.Reader, req *http.Request, writer io.Writer) (*http.Response, error) { - // have a little look of response - r.Peek(1) + // Wait for the HTTP/1.x status prefix before inspecting the buffered response. + r.Peek(12) peekBytes, err := r.Peek(min(56, r.Buffered())) if err == nil || err == bufio.ErrBufferFull { str := string(peekBytes) diff --git a/proxy/http/server_test.go b/proxy/http/server_test.go index 2578d5315e97..5b3563315de8 100644 --- a/proxy/http/server_test.go +++ b/proxy/http/server_test.go @@ -7,6 +7,7 @@ import ( "net/http" "strings" "testing" + "testing/iotest" ) // A malformed upstream response containing a bare '\n' before the real @@ -29,7 +30,7 @@ func TestReadResponseAndHandle100ContinueDoesNotPanicOnEarlyNewline(t *testing.T func TestReadResponseAndHandle100ContinueForwardsAndParsesFinalResponse(t *testing.T) { payload := "HTTP/1.1 100 Continue\r\n\r\n" + "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello" - r := bufio.NewReader(bytes.NewReader([]byte(payload))) + r := bufio.NewReader(iotest.OneByteReader(bytes.NewReader([]byte(payload)))) req, err := http.NewRequest("GET", "http://example.com/", nil) if err != nil { t.Fatal(err)