From 51020be53d0802cd04e5fdd600fd8a9f5b3c9d8a Mon Sep 17 00:00:00 2001 From: Sendya <18x@loacg.com> Date: Wed, 14 Jan 2026 19:23:17 +0800 Subject: [PATCH] feat(http): implement Hijack method in ResponseRecorder and enhance error handling for chunked responses --- pkg/x/http/response_recorder.go | 17 ++++++++++++++++- server/server.go | 26 +++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/pkg/x/http/response_recorder.go b/pkg/x/http/response_recorder.go index e48bcf9..dd5a683 100644 --- a/pkg/x/http/response_recorder.go +++ b/pkg/x/http/response_recorder.go @@ -1,8 +1,15 @@ package http -import "net/http" +import ( + "bufio" + "net" + "net/http" +) + +var _ http.Hijacker = (*ResponseRecorder)(nil) type ResponseRecorder struct { + http.Hijacker http.ResponseWriter status int @@ -34,6 +41,14 @@ func (r *ResponseRecorder) WriteHeader(s int) { } } +// Hijack implements http.Hijacker. +func (r *ResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) { + if hj, ok := r.ResponseWriter.(http.Hijacker); ok { + return hj.Hijack() + } + return nil, nil, http.ErrHijacked +} + func (r *ResponseRecorder) Status() int { return r.status } diff --git a/server/server.go b/server/server.go index cb11887..7866489 100644 --- a/server/server.go +++ b/server/server.go @@ -275,17 +275,26 @@ func (s *HTTPServer) buildHandler(tripper http.RoundTripper) http.HandlerFunc { }() want := resp.Header.Get("Content-Length") + hasChunked := slices.Contains(resp.TransferEncoding, "chunked") sent, err := io.CopyBuffer(w, resp.Body, *buf) + + // handle copy error if err != nil && !errors.Is(err, io.EOF) { - // abort ? continue ? + // quickabort ? continue ? + + // if the body is chunked or no Content-Length, just close the connection + if hasChunked || want == "" { + wrapUpstreamError(w) + } + clog.Errorf("failed to copy response body to client: [%s] %s %s sent=%d want=%s err=%s", req.Proto, req.Method, req.URL.Path, sent, want, err) _metricRequestUnexpectedClosedTotal.WithLabelValues(req.Proto, req.Method).Inc() return } - if slices.Contains(resp.TransferEncoding, "chunked") || want == "" { - clog.Debugf("copied %d response body bytes chunked body from upstream to client", sent) + if hasChunked || want == "" { + clog.Debugf("copied %d response chunked body bytes to client", sent) return } @@ -362,3 +371,14 @@ func (s *HTTPServer) globalOptions(src map[string]any) map[string]any { return src } + +func wrapUpstreamError(w http.ResponseWriter) { + if hj, ok := w.(http.Hijacker); ok { + conn, _, err := hj.Hijack() + if err != nil { + return + } + + _ = conn.Close() + } +}