From 3c85d2a4d7285999285700839a6bab3cac2319ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8C=80=ED=9D=AC?= Date: Tue, 10 Feb 2026 18:02:08 +0900 Subject: [PATCH 1/4] feature(proxy): Adds special handling for client cancellations in proxy error handler Silences logging for client cancellations during polling to reduce noise in logs. Client-side cancellations are common during long-running operations and should not be treated as errors. --- internal/api/modules/amp/proxy.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/api/modules/amp/proxy.go b/internal/api/modules/amp/proxy.go index c460a0d6..b323ae5f 100644 --- a/internal/api/modules/amp/proxy.go +++ b/internal/api/modules/amp/proxy.go @@ -3,6 +3,7 @@ package amp import ( "bytes" "compress/gzip" + "context" "fmt" "io" "net/http" @@ -188,6 +189,10 @@ func createReverseProxy(upstreamURL string, secretSource SecretSource) (*httputi // Error handler for proxy failures proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) { + // Client-side cancellations are common during polling; return 499 without logging + if err == context.Canceled { + return + } log.Errorf("amp upstream proxy error for %s %s: %v", req.Method, req.URL.Path, err) rw.Header().Set("Content-Type", "application/json") rw.WriteHeader(http.StatusBadGateway) From ce0c6aa82beebb452c82e76be4db5dfa886d7bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8C=80=ED=9D=AC?= Date: Tue, 10 Feb 2026 19:07:49 +0900 Subject: [PATCH 2/4] Update internal/api/modules/amp/proxy.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/api/modules/amp/proxy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/api/modules/amp/proxy.go b/internal/api/modules/amp/proxy.go index b323ae5f..e2b68b85 100644 --- a/internal/api/modules/amp/proxy.go +++ b/internal/api/modules/amp/proxy.go @@ -189,7 +189,7 @@ func createReverseProxy(upstreamURL string, secretSource SecretSource) (*httputi // Error handler for proxy failures proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) { - // Client-side cancellations are common during polling; return 499 without logging + // Client-side cancellations are common during polling; suppress logging in this case if err == context.Canceled { return } From 6f2fbdcbaec2a30de1fe25e6ff4ea6b82a0a3c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8C=80=ED=9D=AC?= Date: Thu, 12 Feb 2026 10:30:05 +0900 Subject: [PATCH 3/4] Update internal/api/modules/amp/proxy.go Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- internal/api/modules/amp/proxy.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/api/modules/amp/proxy.go b/internal/api/modules/amp/proxy.go index e2b68b85..c9b992cb 100644 --- a/internal/api/modules/amp/proxy.go +++ b/internal/api/modules/amp/proxy.go @@ -190,7 +190,8 @@ func createReverseProxy(upstreamURL string, secretSource SecretSource) (*httputi // Error handler for proxy failures proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) { // Client-side cancellations are common during polling; suppress logging in this case - if err == context.Canceled { + if errors.Is(err, context.Canceled) { + rw.WriteHeader(gin.StatusClientClosedRequest) return } log.Errorf("amp upstream proxy error for %s %s: %v", req.Method, req.URL.Path, err) From 93147dddeb85d7a8369e07fa86e54d6fdddc1303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8C=80=ED=9D=AC?= Date: Thu, 12 Feb 2026 10:39:45 +0900 Subject: [PATCH 4/4] Improves error handling for canceled requests Adds explicit handling for context.Canceled errors in the reverse proxy error handler to return 499 status code without logging, which is more appropriate for client-side cancellations during polling. Also adds a test case to verify this behavior. --- internal/api/modules/amp/proxy.go | 2 +- internal/api/modules/amp/proxy_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/internal/api/modules/amp/proxy.go b/internal/api/modules/amp/proxy.go index c9b992cb..b7d10760 100644 --- a/internal/api/modules/amp/proxy.go +++ b/internal/api/modules/amp/proxy.go @@ -4,6 +4,7 @@ import ( "bytes" "compress/gzip" "context" + "errors" "fmt" "io" "net/http" @@ -191,7 +192,6 @@ func createReverseProxy(upstreamURL string, secretSource SecretSource) (*httputi proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) { // Client-side cancellations are common during polling; suppress logging in this case if errors.Is(err, context.Canceled) { - rw.WriteHeader(gin.StatusClientClosedRequest) return } log.Errorf("amp upstream proxy error for %s %s: %v", req.Method, req.URL.Path, err) diff --git a/internal/api/modules/amp/proxy_test.go b/internal/api/modules/amp/proxy_test.go index ff23e398..32f5d860 100644 --- a/internal/api/modules/amp/proxy_test.go +++ b/internal/api/modules/amp/proxy_test.go @@ -493,6 +493,30 @@ func TestReverseProxy_ErrorHandler(t *testing.T) { } } +func TestReverseProxy_ErrorHandler_ContextCanceled(t *testing.T) { + // Test that context.Canceled errors return 499 without generic error response + proxy, err := createReverseProxy("http://example.com", NewStaticSecretSource("")) + if err != nil { + t.Fatal(err) + } + + // Create a canceled context to trigger the cancellation path + ctx, cancel := context.WithCancel(context.Background()) + cancel() // Cancel immediately + + req := httptest.NewRequest(http.MethodGet, "/test", nil).WithContext(ctx) + rr := httptest.NewRecorder() + + // Directly invoke the ErrorHandler with context.Canceled + proxy.ErrorHandler(rr, req, context.Canceled) + + // Body should be empty for canceled requests (no JSON error response) + body := rr.Body.Bytes() + if len(body) > 0 { + t.Fatalf("expected empty body for canceled context, got: %s", body) + } +} + func TestReverseProxy_FullRoundTrip_Gzip(t *testing.T) { // Upstream returns gzipped JSON without Content-Encoding header upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {