Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 27 additions & 24 deletions proxy/http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,34 +312,37 @@ 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) {
// 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)
ResponseLine := strings.Split(str, "\r\n")[0]
_, status, _ := strings.Cut(ResponseLine, " ")
// only handle 1xx response
if strings.HasPrefix(status, "1") {
ResponseHeader1xx := []byte{}
// read until \r\n\r\n (end of http response header)
for {
data, err := r.ReadSlice('\n')
if err != nil {
return nil, errors.New("failed to read http 1xx response").Base(err)
}
ResponseHeader1xx = append(ResponseHeader1xx, data...)
if len(ResponseHeader1xx) >= 4 && bytes.Equal(ResponseHeader1xx[len(ResponseHeader1xx)-4:], []byte{'\r', '\n', '\r', '\n'}) {
break
}
if len(ResponseHeader1xx) > 1024 {
return nil, errors.New("too big http 1xx response")
for {
// 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)
ResponseLine := strings.Split(str, "\r\n")[0]
_, status, _ := strings.Cut(ResponseLine, " ")
// only handle 1xx response
if strings.HasPrefix(status, "1") && !strings.HasPrefix(status, "101") {
ResponseHeader1xx := []byte{}
// read until \r\n\r\n (end of http response header)
for {
data, err := r.ReadSlice('\n')
if err != nil {
return nil, errors.New("failed to read http 1xx response").Base(err)
}
ResponseHeader1xx = append(ResponseHeader1xx, data...)
if len(ResponseHeader1xx) >= 4 && bytes.Equal(ResponseHeader1xx[len(ResponseHeader1xx)-4:], []byte{'\r', '\n', '\r', '\n'}) {
break
}
if len(ResponseHeader1xx) > 1024 {
return nil, errors.New("too big http 1xx response")
}
}
writer.Write(ResponseHeader1xx)
continue
}
writer.Write(ResponseHeader1xx)
}
return http.ReadResponse(r, req)
}
return http.ReadResponse(r, req)
}

func init() {
Expand Down
54 changes: 42 additions & 12 deletions proxy/http/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,53 @@ 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(iotest.OneByteReader(bytes.NewReader([]byte(payload))))
req, err := http.NewRequest("GET", "http://example.com/", nil)
if err != nil {
t.Fatal(err)
const cont = "HTTP/1.1 100 Continue\r\n\r\n"
const hint = "HTTP/1.1 103 Early Hints\r\nLink: </style.css>; rel=preload; as=style\r\n\r\n"
for _, tc := range []struct{ name, interim string }{
{"no_interim", ""},
{"continue", cont},
{"early_hints", hint},
{"multiple_hints", hint + hint},
{"continue_and_hints", cont + hint},
} {
t.Run(tc.name, func(t *testing.T) {
payload := tc.interim + "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello"
r := bufio.NewReader(iotest.OneByteReader(strings.NewReader(payload)))
req, err := http.NewRequest("GET", "http://example.com/", nil)
if err != nil {
t.Fatal(err)
}
var forwarded bytes.Buffer
resp, err := readResponseAndHandle100Continue(r, req, &forwarded)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != 200 || string(body) != "hello" || forwarded.String() != tc.interim {
t.Fatalf("status=%d body=%q forwarded=%q; want 200, hello, %q", resp.StatusCode, body, forwarded.String(), tc.interim)
}
})
}
}

func TestReadResponseAndHandle100ContinueStopsAtSwitchingProtocols(t *testing.T) {
const upgrade = "HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: example\r\n\r\n"
r := bufio.NewReader(iotest.OneByteReader(strings.NewReader(upgrade + "opaque")))
var forwarded bytes.Buffer
resp, err := readResponseAndHandle100Continue(r, req, &forwarded)
resp, err := readResponseAndHandle100Continue(r, &http.Request{Method: "GET"}, &forwarded)
if err != nil {
t.Fatalf("unexpected error: %v", err)
t.Fatal(err)
}
if resp.StatusCode != 200 {
t.Fatalf("expected status 200, got %d", resp.StatusCode)
defer resp.Body.Close()
remaining, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(forwarded.String(), "100 Continue") {
t.Fatalf("expected 1xx response to be forwarded, got %q", forwarded.String())
if resp.StatusCode != 101 || forwarded.Len() != 0 || string(remaining) != "opaque" {
t.Fatalf("status=%d forwarded=%q remaining=%q", resp.StatusCode, forwarded.String(), remaining)
}
}
Loading