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] 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) {