This is an automated email from the ASF dual-hosted git repository.

alexstocks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go-pixiu.git


The following commit(s) were added to refs/heads/develop by this push:
     new c08a23c1 Unify  the errorcode (#14) (#782)
c08a23c1 is described below

commit c08a23c1577dcafbf5dcacc7804203eaa473b6c1
Author: dubbo-go-bot <[email protected]>
AuthorDate: Fri Oct 24 11:53:48 2025 +0800

    Unify  the errorcode (#14) (#782)
    
    * support http filter plugins (Open Policy Agent)
    
    * fmt
    
    * Streamlined judgment
    
    * accept the advices
    
    * add wrong example
    
    * style: run go fmt && imports-formatter
    
    * fix the bug of go.mod
    
    * change the key of opa
    
    * update the true sum
    
    * Add OPA DOC
    
    * change the opa.docs for Chinese and English version
    
    * remove docs/sample
    
    * roll back
    
    * modify the README
    
    * modify
    
    * unify the errorcode
    
    * add the test of context
    
    * simplify
    
    * use errors.new to replace fmt.Errof in some cases
    
    * fix api_config
    
    * fix call
    
    * fix jwt
    
    * change interface{} to any
    
    * change all interface{} to any
    
    * fix the logic error in filter\prometheus\metric
    
    * add judgment for client error
    
    * reliable timeout detection over substring matching
    
    * add mock test
    
    * Remove send and sendError to avoid confusion
    
    ---------
    
    Co-authored-by: Sirui Huang <[email protected]>
    Co-authored-by: Sirui Huang <[email protected]>
---
 pkg/common/constant/filter.go                      |   8 -
 pkg/common/http/manager.go                         |   7 +-
 pkg/common/mock/routerfilter.go                    |   8 +-
 pkg/context/http/context.go                        |   4 -
 pkg/context/http/context_test.go                   | 431 +++++++++++++++++++++
 pkg/context/http/erroresponse.go                   | 128 ++++++
 pkg/filter/auth/jwt/jwt.go                         |   5 +-
 pkg/filter/authority/authority.go                  |   7 +-
 pkg/filter/csrf/csrf.go                            |  11 +-
 pkg/filter/event/event.go                          |   4 +-
 pkg/filter/http/apiconfig/api_config.go            |  10 +-
 pkg/filter/http/dubboproxy/dubbo.go                |  46 ++-
 pkg/filter/http/grpcproxy/grpc.go                  |  52 ++-
 pkg/filter/http/httpproxy/routerfilter.go          |  15 +-
 pkg/filter/http/remote/call.go                     |  18 +-
 pkg/filter/llm/proxy/filter.go                     |  19 +-
 pkg/filter/mcp/mcpserver/filter.go                 |   3 +-
 pkg/filter/mcp/mcpserver/response.go               |   4 +-
 pkg/filter/prometheus/metric.go                    |  23 +-
 .../sentinel/circuitbreaker/circuit_breaker.go     |   4 +-
 pkg/filter/sentinel/ratelimit/rate_limit.go        |   9 +-
 21 files changed, 695 insertions(+), 121 deletions(-)

diff --git a/pkg/common/constant/filter.go b/pkg/common/constant/filter.go
index 73b5badd..4867f024 100644
--- a/pkg/common/constant/filter.go
+++ b/pkg/common/constant/filter.go
@@ -21,14 +21,6 @@ import (
        "time"
 )
 
-var (
-       Default403Body = []byte("403 for bidden")
-       Default404Body = []byte("404 page not found")
-       Default405Body = []byte("405 method not allowed")
-       Default406Body = []byte("406 api not up")
-       Default503Body = []byte("503 service unavailable")
-)
-
 const (
        // nolint
        // FileDateFormat
diff --git a/pkg/common/http/manager.go b/pkg/common/http/manager.go
index 8555483b..de925f65 100644
--- a/pkg/common/http/manager.go
+++ b/pkg/common/http/manager.go
@@ -101,7 +101,8 @@ func (hcm *HttpConnectionManager) handleHTTPRequest(c 
*pch.HttpContext) {
        defer func() {
                if err := recover(); err != nil {
                        logger.Warnf("[dubbo-go-pixiu] Occur An Unexpected Err: 
%+v", err)
-                       c.SendLocalReply(stdHttp.StatusInternalServerError, 
[]byte(fmt.Sprintf("Occur An Unexpected Err: %v", err)))
+                       errResp := 
pch.InternalError.WithError(fmt.Errorf("panic recovered: %v", err))
+                       c.SendLocalReply(errResp.Status, errResp.ToJSON())
                }
        }()
 
@@ -245,12 +246,12 @@ func (hcm *HttpConnectionManager) buildTargetResponse(c 
*pch.HttpContext) {
 func (hcm *HttpConnectionManager) findRoute(hc *pch.HttpContext) error {
        ra, err := hcm.routerCoordinator.Route(hc)
        if err != nil {
-               hc.SendLocalReply(stdHttp.StatusNotFound, 
constant.Default404Body)
+               errResp := pch.RouteNotFound.New()
+               hc.SendLocalReply(errResp.Status, errResp.ToJSON())
 
                e := errors.Errorf("Requested URL %s not found", hc.GetUrl())
                logger.Debug(e.Error())
                return e
-               // return 404
        }
        hc.RouteEntry(ra)
        return nil
diff --git a/pkg/common/mock/routerfilter.go b/pkg/common/mock/routerfilter.go
index f429ca30..4a0a2b57 100644
--- a/pkg/common/mock/routerfilter.go
+++ b/pkg/common/mock/routerfilter.go
@@ -18,7 +18,6 @@
 package mock
 
 import (
-       "encoding/json"
        "fmt"
        "net/http"
        "time"
@@ -104,8 +103,8 @@ func (f *Filter) Decode(hc *contexthttp.HttpContext) 
filter.FilterStatus {
 
        req, err = http.NewRequest(r.Method, r.URL.String(), r.Body)
        if err != nil {
-               bt, _ := json.Marshal(contexthttp.ErrResponse{Message: 
fmt.Sprintf("BUG: new request failed: %v", err)})
-               hc.SendLocalReply(http.StatusInternalServerError, bt)
+               errResp := contexthttp.InternalError.WithError(fmt.Errorf("new 
request failed: %w", err))
+               hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
        req.Header = r.Header
@@ -113,7 +112,8 @@ func (f *Filter) Decode(hc *contexthttp.HttpContext) 
filter.FilterStatus {
        resp, err := f.client.Do(req)
 
        if err != nil {
-               hc.SendLocalReply(http.StatusServiceUnavailable, 
[]byte(err.Error()))
+               errResp := contexthttp.ServiceUnavailable.WithError(err)
+               hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
diff --git a/pkg/context/http/context.go b/pkg/context/http/context.go
index 68f47449..6f173bfc 100644
--- a/pkg/context/http/context.go
+++ b/pkg/context/http/context.go
@@ -72,10 +72,6 @@ type HttpContext struct {
 }
 
 type (
-       // ErrResponse err response.
-       ErrResponse struct {
-               Message string `json:"message"`
-       }
 
        // FilterFunc filter func, filter
        FilterFunc func(c *HttpContext)
diff --git a/pkg/context/http/context_test.go b/pkg/context/http/context_test.go
index d26f2933..17961ff8 100644
--- a/pkg/context/http/context_test.go
+++ b/pkg/context/http/context_test.go
@@ -16,3 +16,434 @@
  */
 
 package http
+
+import (
+       "context"
+       "encoding/json"
+       "errors"
+       "fmt"
+       "net/http"
+       "testing"
+)
+
+// mockResponseWriter is a test implementation of http.ResponseWriter
+type mockResponseWriter struct {
+       header http.Header
+}
+
+func (w *mockResponseWriter) Header() http.Header {
+       if w.header == nil {
+               w.header = make(http.Header)
+       }
+       return w.header
+}
+
+func (w *mockResponseWriter) Write(b []byte) (int, error) {
+       return len(b), nil
+}
+
+func (w *mockResponseWriter) WriteHeader(statusCode int) {
+}
+
+// newTestHTTPContext creates a mock HttpContext for testing
+func newTestHTTPContext(r *http.Request) *HttpContext {
+       ctx := &HttpContext{
+               Index:   -1,
+               Request: r,
+               Writer:  &mockResponseWriter{},
+               Ctx:     context.Background(),
+       }
+       ctx.Reset()
+       return ctx
+}
+
+// TestErrorBuilder tests the ErrorBuilder methods
+func TestErrorBuilder(t *testing.T) {
+       tests := []struct {
+               name           string
+               builder        *ErrorBuilder
+               expectedStatus int
+               expectedMsg    string
+       }{
+               {
+                       name:           "BadRequest",
+                       builder:        BadRequest,
+                       expectedStatus: http.StatusBadRequest,
+                       expectedMsg:    "Bad request",
+               },
+               {
+                       name:           "NotFound",
+                       builder:        RouteNotFound,
+                       expectedStatus: http.StatusNotFound,
+                       expectedMsg:    "Route not found",
+               },
+               {
+                       name:           "InternalError",
+                       builder:        InternalError,
+                       expectedStatus: http.StatusInternalServerError,
+                       expectedMsg:    "Internal server error",
+               },
+               {
+                       name:           "ServiceUnavailable",
+                       builder:        ServiceUnavailable,
+                       expectedStatus: http.StatusServiceUnavailable,
+                       expectedMsg:    "Service unavailable",
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       // Test New()
+                       errResp := tt.builder.New()
+                       if errResp.Status != tt.expectedStatus {
+                               t.Errorf("expected status %d, got %d", 
tt.expectedStatus, errResp.Status)
+                       }
+                       if errResp.Message != tt.expectedMsg {
+                               t.Errorf("expected message %q, got %q", 
tt.expectedMsg, errResp.Message)
+                       }
+                       if errResp.Err != nil {
+                               t.Errorf("expected nil error, got %v", 
errResp.Err)
+                       }
+
+                       // Test GetStatus()
+                       if tt.builder.GetStatus() != tt.expectedStatus {
+                               t.Errorf("GetStatus() = %d, want %d", 
tt.builder.GetStatus(), tt.expectedStatus)
+                       }
+               })
+       }
+}
+
+// TestErrorBuilderWithError tests the WithError method
+func TestErrorBuilderWithError(t *testing.T) {
+       testErr := errors.New("test error")
+       wrappedErr := fmt.Errorf("wrapped: %w", testErr)
+
+       tests := []struct {
+               name        string
+               builder     *ErrorBuilder
+               err         error
+               wantStatus  int
+               wantMessage string
+       }{
+               {
+                       name:        "BadRequest with simple error",
+                       builder:     BadRequest,
+                       err:         testErr,
+                       wantStatus:  http.StatusBadRequest,
+                       wantMessage: "Bad request",
+               },
+               {
+                       name:        "InternalError with wrapped error",
+                       builder:     InternalError,
+                       err:         wrappedErr,
+                       wantStatus:  http.StatusInternalServerError,
+                       wantMessage: "Internal server error",
+               },
+               {
+                       name:        "GatewayTimeout with formatted error",
+                       builder:     GatewayTimeout,
+                       err:         fmt.Errorf("timeout after 30s: %w", 
testErr),
+                       wantStatus:  http.StatusGatewayTimeout,
+                       wantMessage: "Gateway timeout",
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       errResp := tt.builder.WithError(tt.err)
+                       if errResp.Status != tt.wantStatus {
+                               t.Errorf("Status = %d, want %d", 
errResp.Status, tt.wantStatus)
+                       }
+                       if errResp.Message != tt.wantMessage {
+                               t.Errorf("Message = %q, want %q", 
errResp.Message, tt.wantMessage)
+                       }
+                       if errResp.Err != tt.err {
+                               t.Errorf("Err = %v, want %v", errResp.Err, 
tt.err)
+                       }
+               })
+       }
+}
+
+// TestErrorResponseToJSON tests JSON serialization
+func TestErrorResponseToJSON(t *testing.T) {
+       tests := []struct {
+               name     string
+               errResp  *ErrorResponse
+               wantJSON string
+       }{
+               {
+                       name:     "without error",
+                       errResp:  BadRequest.New(),
+                       wantJSON: `{"status":400,"message":"Bad request"}`,
+               },
+               {
+                       name:     "with simple error",
+                       errResp:  BadRequest.WithError(errors.New("invalid 
parameter")),
+                       wantJSON: `{"status":400,"message":"Bad 
request","error":"invalid parameter"}`,
+               },
+               {
+                       name:     "with wrapped error",
+                       errResp:  InternalError.WithError(fmt.Errorf("failed to 
process: %w", errors.New("connection refused"))),
+                       wantJSON: `{"status":500,"message":"Internal server 
error","error":"failed to process: connection refused"}`,
+               },
+               {
+                       name:     "NotFound without error",
+                       errResp:  RouteNotFound.New(),
+                       wantJSON: `{"status":404,"message":"Route not found"}`,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       gotJSON := tt.errResp.ToJSON()
+
+                       // Compare JSON structure
+                       var got, want map[string]any
+                       if err := json.Unmarshal(gotJSON, &got); err != nil {
+                               t.Fatalf("failed to unmarshal got JSON: %v", 
err)
+                       }
+                       if err := json.Unmarshal([]byte(tt.wantJSON), &want); 
err != nil {
+                               t.Fatalf("failed to unmarshal want JSON: %v", 
err)
+                       }
+
+                       if got["status"] != want["status"] {
+                               t.Errorf("status = %v, want %v", got["status"], 
want["status"])
+                       }
+                       if got["message"] != want["message"] {
+                               t.Errorf("message = %v, want %v", 
got["message"], want["message"])
+                       }
+                       if want["error"] != nil && got["error"] != 
want["error"] {
+                               t.Errorf("error = %v, want %v", got["error"], 
want["error"])
+                       }
+               })
+       }
+}
+
+// TestErrorResponseError tests the Error() method
+func TestErrorResponseError(t *testing.T) {
+       tests := []struct {
+               name    string
+               errResp *ErrorResponse
+               wantStr string
+       }{
+               {
+                       name:    "without error",
+                       errResp: BadRequest.New(),
+                       wantStr: "[400] Bad request",
+               },
+               {
+                       name:    "with simple error",
+                       errResp: BadRequest.WithError(errors.New("invalid id")),
+                       wantStr: "[400] Bad request: invalid id",
+               },
+               {
+                       name:    "with wrapped error",
+                       errResp: InternalError.WithError(fmt.Errorf("database 
error: %w", errors.New("connection lost"))),
+                       wantStr: "[500] Internal server error: database error: 
connection lost",
+               },
+               {
+                       name:    "503 with context",
+                       errResp: 
ServiceUnavailable.WithError(fmt.Errorf("endpoint not found: %w", 
errors.New("no healthy hosts"))),
+                       wantStr: "[503] Service unavailable: endpoint not 
found: no healthy hosts",
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       got := tt.errResp.Error()
+                       if got != tt.wantStr {
+                               t.Errorf("Error() = %q, want %q", got, 
tt.wantStr)
+                       }
+               })
+       }
+}
+
+// TestAllErrorTypes verifies all predefined error types
+func TestAllErrorTypes(t *testing.T) {
+       errorTypes := []struct {
+               name    string
+               builder *ErrorBuilder
+               status  int
+       }{
+               // 404 errors
+               {"RouteNotFound", RouteNotFound, http.StatusNotFound},
+               {"ServiceNotFound", ServiceNotFound, http.StatusNotFound},
+               {"APINotFound", APINotFound, http.StatusNotFound},
+               // 400 errors
+               {"BadRequest", BadRequest, http.StatusBadRequest},
+               // 401 errors
+               {"Unauthorized", Unauthorized, http.StatusUnauthorized},
+               // 403 errors
+               {"Forbidden", Forbidden, http.StatusForbidden},
+               // 405 errors
+               {"MethodNotAllowed", MethodNotAllowed, 
http.StatusMethodNotAllowed},
+               // 406 errors
+               {"NotAcceptable", NotAcceptable, http.StatusNotAcceptable},
+               // 429 errors
+               {"RateLimited", RateLimited, http.StatusTooManyRequests},
+               // 500 errors
+               {"InternalError", InternalError, 
http.StatusInternalServerError},
+               {"ConfigurationError", ConfigurationError, 
http.StatusInternalServerError},
+               // 502 errors
+               {"BadGateway", BadGateway, http.StatusBadGateway},
+               // 503 errors
+               {"ServiceUnavailable", ServiceUnavailable, 
http.StatusServiceUnavailable},
+               // 504 errors
+               {"GatewayTimeout", GatewayTimeout, http.StatusGatewayTimeout},
+       }
+
+       for _, tt := range errorTypes {
+               t.Run(tt.name, func(t *testing.T) {
+                       if tt.builder == nil {
+                               t.Fatal("error builder is nil")
+                       }
+                       if tt.builder.GetStatus() != tt.status {
+                               t.Errorf("status = %d, want %d", 
tt.builder.GetStatus(), tt.status)
+                       }
+
+                       // Test that New() works
+                       errResp := tt.builder.New()
+                       if errResp.Status != tt.status {
+                               t.Errorf("New().Status = %d, want %d", 
errResp.Status, tt.status)
+                       }
+
+                       // Test that WithError() works
+                       testErr := errors.New("test")
+                       errResp = tt.builder.WithError(testErr)
+                       if errResp.Err != testErr {
+                               t.Errorf("WithError().Err = %v, want %v", 
errResp.Err, testErr)
+                       }
+               })
+       }
+}
+
+// TestErrorResponseJSONMarshaling tests edge cases in JSON marshaling
+func TestErrorResponseJSONMarshaling(t *testing.T) {
+       t.Run("error with special characters", func(t *testing.T) {
+               errResp := BadRequest.WithError(errors.New(`error with "quotes" 
and \backslash`))
+               jsonBytes := errResp.ToJSON()
+
+               var result map[string]any
+               if err := json.Unmarshal(jsonBytes, &result); err != nil {
+                       t.Fatalf("failed to unmarshal JSON: %v", err)
+               }
+
+               if result["error"] != `error with "quotes" and \backslash` {
+                       t.Errorf("error field not properly escaped: %v", 
result["error"])
+               }
+       })
+
+       t.Run("nil error", func(t *testing.T) {
+               errResp := &ErrorResponse{
+                       Status:  http.StatusBadRequest,
+                       Message: "Bad request",
+                       Err:     nil,
+               }
+               jsonBytes := errResp.ToJSON()
+
+               var result map[string]any
+               if err := json.Unmarshal(jsonBytes, &result); err != nil {
+                       t.Fatalf("failed to unmarshal JSON: %v", err)
+               }
+
+               if _, hasError := result["error"]; hasError {
+                       t.Error("error field should be omitted when Err is nil")
+               }
+       })
+
+       t.Run("WithError(nil) behavior", func(t *testing.T) {
+               errResp := BadRequest.WithError(nil)
+               jsonBytes := errResp.ToJSON()
+
+               var result map[string]any
+               if err := json.Unmarshal(jsonBytes, &result); err != nil {
+                       t.Fatalf("failed to unmarshal JSON: %v", err)
+               }
+
+               // Verify no error field when error is nil
+               if _, hasError := result["error"]; hasError {
+                       t.Error("WithError(nil) should not include error field 
in JSON")
+               }
+
+               // Verify basic fields are present
+               if result["status"] != float64(http.StatusBadRequest) {
+                       t.Errorf("status = %v, want %v", result["status"], 
http.StatusBadRequest)
+               }
+               if result["message"] != "Bad request" {
+                       t.Errorf("message = %v, want 'Bad request'", 
result["message"])
+               }
+       })
+
+       t.Run("empty message and zero status", func(t *testing.T) {
+               errResp := &ErrorResponse{
+                       Status:  0,
+                       Message: "",
+                       Err:     nil,
+               }
+
+               // Should not panic
+               jsonBytes := errResp.ToJSON()
+
+               var result map[string]any
+               if err := json.Unmarshal(jsonBytes, &result); err != nil {
+                       t.Fatalf("failed to unmarshal JSON: %v", err)
+               }
+
+               if result["status"] != float64(0) {
+                       t.Errorf("status = %v, want 0", result["status"])
+               }
+               if result["message"] != "" {
+                       t.Errorf("message = %v, want empty string", 
result["message"])
+               }
+
+               // No error field expected
+               if _, hasError := result["error"]; hasError {
+                       t.Error("error field should be omitted when Err is nil")
+               }
+       })
+
+       t.Run("Error() with empty message and zero status", func(t *testing.T) {
+               errResp := &ErrorResponse{
+                       Status:  0,
+                       Message: "",
+                       Err:     nil,
+               }
+
+               // Should not panic
+               got := errResp.Error()
+               want := "[0] "
+               if got != want {
+                       t.Errorf("Error() = %q, want %q", got, want)
+               }
+       })
+
+       t.Run("Error() with empty message but has error", func(t *testing.T) {
+               errResp := &ErrorResponse{
+                       Status:  http.StatusInternalServerError,
+                       Message: "",
+                       Err:     errors.New("internal error"),
+               }
+
+               // Should not panic
+               got := errResp.Error()
+               want := "[500] : internal error"
+               if got != want {
+                       t.Errorf("Error() = %q, want %q", got, want)
+               }
+       })
+
+       t.Run("Error() with zero status and has error", func(t *testing.T) {
+               errResp := &ErrorResponse{
+                       Status:  0,
+                       Message: "Unknown error",
+                       Err:     errors.New("something went wrong"),
+               }
+
+               // Should not panic
+               got := errResp.Error()
+               want := "[0] Unknown error: something went wrong"
+               if got != want {
+                       t.Errorf("Error() = %q, want %q", got, want)
+               }
+       })
+}
diff --git a/pkg/context/http/erroresponse.go b/pkg/context/http/erroresponse.go
new file mode 100644
index 00000000..97587b62
--- /dev/null
+++ b/pkg/context/http/erroresponse.go
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package http
+
+import (
+       "encoding/json"
+       "fmt"
+       "net/http"
+)
+
+// ErrorResponse represents the unified error response structure
+type ErrorResponse struct {
+       Status  int    `json:"status"`  // HTTP status code
+       Message string `json:"message"` // Standard error message
+       Err     error  `json:"-"`       // Underlying error, not marshaled 
directly
+}
+
+// ErrorBuilder builds error responses
+type ErrorBuilder struct {
+       status  int
+       message string
+}
+
+// Predefined error response builders
+var (
+       // 404 - Not Found
+       RouteNotFound   = newErrorBuilder(http.StatusNotFound, "Route not 
found")
+       ServiceNotFound = newErrorBuilder(http.StatusNotFound, "Service not 
found")
+       APINotFound     = newErrorBuilder(http.StatusNotFound, "API not found")
+
+       // 400 - Bad Request
+       BadRequest = newErrorBuilder(http.StatusBadRequest, "Bad request")
+
+       // 401 - Unauthorized
+       Unauthorized = newErrorBuilder(http.StatusUnauthorized, "Unauthorized")
+
+       // 403 - Forbidden
+       Forbidden = newErrorBuilder(http.StatusForbidden, "Forbidden")
+
+       // 405 - Method Not Allowed
+       MethodNotAllowed = newErrorBuilder(http.StatusMethodNotAllowed, "Method 
not allowed")
+
+       // 406 - Not Acceptable
+       NotAcceptable = newErrorBuilder(http.StatusNotAcceptable, "Not 
acceptable")
+
+       // 429 - Rate Limited
+       RateLimited = newErrorBuilder(http.StatusTooManyRequests, "Rate 
limited")
+
+       // 500 - Internal Server Error
+       InternalError      = newErrorBuilder(http.StatusInternalServerError, 
"Internal server error")
+       ConfigurationError = newErrorBuilder(http.StatusInternalServerError, 
"Configuration error")
+
+       // 502 - Bad Gateway
+       BadGateway = newErrorBuilder(http.StatusBadGateway, "Bad gateway")
+
+       // 503 - Service Unavailable
+       ServiceUnavailable = newErrorBuilder(http.StatusServiceUnavailable, 
"Service unavailable")
+
+       // 504 - Gateway Timeout
+       GatewayTimeout = newErrorBuilder(http.StatusGatewayTimeout, "Gateway 
timeout")
+)
+
+func newErrorBuilder(status int, message string) *ErrorBuilder {
+       return &ErrorBuilder{
+               status:  status,
+               message: message,
+       }
+}
+
+// New creates a standard error response without details
+func (eb *ErrorBuilder) New() *ErrorResponse {
+       return &ErrorResponse{
+               Status:  eb.status,
+               Message: eb.message,
+       }
+}
+
+func (eb *ErrorBuilder) WithError(err error) *ErrorResponse {
+       return &ErrorResponse{
+               Status:  eb.status,
+               Message: eb.message,
+               Err:     err,
+       }
+}
+
+func (eb *ErrorBuilder) GetStatus() int {
+       return eb.status
+}
+
+func (e *ErrorResponse) ToJSON() []byte {
+       type alias struct {
+               Status  int    `json:"status"`
+               Message string `json:"message"`
+               Error   string `json:"error,omitempty"`
+       }
+       payload := alias{Status: e.Status, Message: e.Message}
+       if e.Err != nil {
+               payload.Error = e.Err.Error()
+       }
+       data, err := json.Marshal(payload)
+       if err != nil {
+               return []byte(`{"status":500,"message":"Internal server 
error"}`)
+       }
+       return data
+}
+
+// Error implements the error interface
+func (e *ErrorResponse) Error() string {
+       if e.Err != nil {
+               return fmt.Sprintf("[%d] %s: %s", e.Status, e.Message, 
e.Err.Error())
+       }
+       return fmt.Sprintf("[%d] %s", e.Status, e.Message)
+}
diff --git a/pkg/filter/auth/jwt/jwt.go b/pkg/filter/auth/jwt/jwt.go
index 75e88d2c..f4ab3bec 100644
--- a/pkg/filter/auth/jwt/jwt.go
+++ b/pkg/filter/auth/jwt/jwt.go
@@ -19,6 +19,7 @@ package jwt
 
 import (
        "encoding/json"
+       "errors"
        "fmt"
        stdHttp "net/http"
        "strings"
@@ -190,8 +191,8 @@ func (factory *FilterFactory) Apply() error {
                factory.cfg.ErrMsg = "token invalid"
        }
 
-       errMsg, _ := json.Marshal(http.ErrResponse{Message: factory.cfg.ErrMsg})
-       factory.errMsg = errMsg
+       errResp := http.Unauthorized.WithError(errors.New(factory.cfg.ErrMsg))
+       factory.errMsg = errResp.ToJSON()
 
        return nil
 }
diff --git a/pkg/filter/authority/authority.go 
b/pkg/filter/authority/authority.go
index 5f0aa130..cf79e05d 100644
--- a/pkg/filter/authority/authority.go
+++ b/pkg/filter/authority/authority.go
@@ -17,10 +17,6 @@
 
 package authority
 
-import (
-       nh "net/http"
-)
-
 import (
        "github.com/apache/dubbo-go-pixiu/pkg/common/constant"
        "github.com/apache/dubbo-go-pixiu/pkg/common/extension/filter"
@@ -79,7 +75,8 @@ func (f *Filter) Decode(c *http.HttpContext) 
filter.FilterStatus {
 
                result := passCheck(item, r)
                if !result {
-                       c.SendLocalReply(nh.StatusForbidden, 
constant.Default403Body)
+                       errResp := http.Forbidden.New()
+                       c.SendLocalReply(errResp.Status, errResp.ToJSON())
                        return filter.Stop
                }
        }
diff --git a/pkg/filter/csrf/csrf.go b/pkg/filter/csrf/csrf.go
index f660c3f5..efedcd20 100644
--- a/pkg/filter/csrf/csrf.go
+++ b/pkg/filter/csrf/csrf.go
@@ -19,9 +19,8 @@ package csrf
 
 import (
        "encoding/base64"
-       "encoding/json"
+       "errors"
        "fmt"
-       stdHttp "net/http"
 )
 
 import (
@@ -90,16 +89,16 @@ func (f *Filter) Decode(ctx *http.HttpContext) 
filter.FilterStatus {
        salt := ctx.Request.Header.Get(csrfSalt)
 
        if salt == "" {
-               bt, _ := json.Marshal(http.ErrResponse{Message: f.cfg.ErrorMsg})
-               ctx.SendLocalReply(stdHttp.StatusForbidden, bt)
+               errResp := http.Forbidden.WithError(errors.New(f.cfg.ErrorMsg))
+               ctx.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
        token := tokenize(f.cfg.Secret, salt)
 
        if token != tokenGetter(ctx, f.cfg.Key) {
-               bt, _ := json.Marshal(http.ErrResponse{Message: f.cfg.ErrorMsg})
-               ctx.SendLocalReply(stdHttp.StatusForbidden, bt)
+               errResp := http.Forbidden.WithError(errors.New(f.cfg.ErrorMsg))
+               ctx.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
diff --git a/pkg/filter/event/event.go b/pkg/filter/event/event.go
index ca89eff6..0d5cbc96 100644
--- a/pkg/filter/event/event.go
+++ b/pkg/filter/event/event.go
@@ -19,7 +19,6 @@ package event
 
 import (
        "fmt"
-       sdkhttp "net/http"
 )
 
 import (
@@ -77,7 +76,8 @@ func (f *Filter) Decode(ctx *http.HttpContext) 
filter.FilterStatus {
        resp, err := mqClient.Call(req)
        if err != nil {
                logger.Errorf("[dubbo-go-pixiu] event client call err:%v!", err)
-               ctx.SendLocalReply(sdkhttp.StatusInternalServerError, 
[]byte(fmt.Sprintf("event client call err:%v", err)))
+               errResp := http.InternalError.WithError(fmt.Errorf("event 
client call error: %w", err))
+               ctx.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
        logger.Debugf("[dubbo-go-pixiu] event client call resp:%v", resp)
diff --git a/pkg/filter/http/apiconfig/api_config.go 
b/pkg/filter/http/apiconfig/api_config.go
index 6e929107..560a3459 100644
--- a/pkg/filter/http/apiconfig/api_config.go
+++ b/pkg/filter/http/apiconfig/api_config.go
@@ -17,10 +17,6 @@
 
 package apiconfig
 
-import (
-       "net/http"
-)
-
 import (
        fc "github.com/dubbo-go-pixiu/pixiu-api/pkg/api/config"
        "github.com/dubbo-go-pixiu/pixiu-api/pkg/router"
@@ -116,14 +112,16 @@ func (f *Filter) Decode(ctx *contexthttp.HttpContext) 
filter.FilterStatus {
        req := ctx.Request
        v, err := f.apiService.MatchAPI(req.URL.Path, fc.HTTPVerb(req.Method))
        if err != nil {
-               ctx.SendLocalReply(http.StatusNotFound, constant.Default404Body)
+               errResp := contexthttp.APINotFound.New()
+               ctx.SendLocalReply(errResp.Status, errResp.ToJSON())
                e := errors.Errorf("Requested URL %s not found", req.URL.Path)
                logger.Debug(e.Error())
                return filter.Stop
        }
 
        if !v.Enable {
-               ctx.SendLocalReply(http.StatusNotAcceptable, 
constant.Default406Body)
+               errResp := contexthttp.NotAcceptable.WithError(errors.New("API 
not online"))
+               ctx.SendLocalReply(errResp.Status, errResp.ToJSON())
                e := errors.Errorf("Requested API %s %s does not online", 
req.Method, req.URL.Path)
                logger.Debug(e.Error())
                return filter.Stop
diff --git a/pkg/filter/http/dubboproxy/dubbo.go 
b/pkg/filter/http/dubboproxy/dubbo.go
index 56d51c2a..b5a88255 100644
--- a/pkg/filter/http/dubboproxy/dubbo.go
+++ b/pkg/filter/http/dubboproxy/dubbo.go
@@ -20,9 +20,10 @@ package dubboproxy
 import (
        "context"
        "encoding/json"
+       "errors"
        "fmt"
        "io"
-       "net/http"
+       "os"
        "reflect"
        "strings"
 )
@@ -102,8 +103,8 @@ func (f *Filter) Decode(hc *pixiuHttp.HttpContext) 
filter.FilterStatus {
        rEntry := hc.GetRouteEntry()
        if rEntry == nil {
                logger.Info("[dubbo-go-pixiu] http not match route")
-               bt, _ := json.Marshal(pixiuHttp.ErrResponse{Message: "not match 
route"})
-               hc.SendLocalReply(http.StatusNotFound, bt)
+               errResp := pixiuHttp.RouteNotFound.New()
+               hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
        logger.Debugf("[dubbo-go-pixiu] client choose endpoint from cluster 
:%v", rEntry.Cluster)
@@ -113,8 +114,8 @@ func (f *Filter) Decode(hc *pixiuHttp.HttpContext) 
filter.FilterStatus {
        endpoint := clusterManager.PickEndpoint(clusterName, hc)
        if endpoint == nil {
                logger.Info("[dubbo-go-pixiu] cluster not found endpoint")
-               bt, _ := json.Marshal(pixiuHttp.ErrResponse{Message: "cluster 
not found endpoint"})
-               hc.SendLocalReply(http.StatusServiceUnavailable, bt)
+               errResp := 
pixiuHttp.ServiceUnavailable.WithError(errors.New("endpoint not found"))
+               hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
@@ -125,8 +126,8 @@ func (f *Filter) Decode(hc *pixiuHttp.HttpContext) 
filter.FilterStatus {
 
        if len(splits) != 3 {
                logger.Info("[dubbo-go-pixiu] http path pattern error. path 
pattern should be http://127.0.0.1/{application}/{service}/{method}";)
-               bt, _ := json.Marshal(pixiuHttp.ErrResponse{Message: "http path 
pattern error"})
-               hc.SendLocalReply(http.StatusBadRequest, bt)
+               errResp := pixiuHttp.BadRequest.WithError(errors.New("http path 
pattern error"))
+               hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
@@ -141,16 +142,16 @@ func (f *Filter) Decode(hc *pixiuHttp.HttpContext) 
filter.FilterStatus {
        rawBody, err := io.ReadAll(hc.Request.Body)
        if err != nil {
                logger.Infof("[dubbo-go-pixiu] read request body error %v", err)
-               bt, _ := json.Marshal(pixiuHttp.ErrResponse{Message: 
fmt.Sprintf("read request body error %v", err)})
-               hc.SendLocalReply(http.StatusBadRequest, bt)
+               errResp := pixiuHttp.BadRequest.WithError(fmt.Errorf("read 
request body: %w", err))
+               hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
        var body any
        if err := json.Unmarshal(rawBody, &body); err != nil {
                logger.Infof("[dubbo-go-pixiu] unmarshal request body error 
%v", err)
-               bt, _ := json.Marshal(pixiuHttp.ErrResponse{Message: 
fmt.Sprintf("unmarshal request body error %v", err)})
-               hc.SendLocalReply(http.StatusBadRequest, bt)
+               errResp := pixiuHttp.BadRequest.WithError(fmt.Errorf("unmarshal 
request body: %w", err))
+               hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
@@ -199,8 +200,8 @@ func (f *Filter) Decode(hc *pixiuHttp.HttpContext) 
filter.FilterStatus {
        )
        if err != nil {
                logger.Infof("[dubbo-go-pixiu] newURL error %v", err)
-               bt, _ := json.Marshal(pixiuHttp.ErrResponse{Message: 
fmt.Sprintf("newURL error %v", err)})
-               hc.SendLocalReply(http.StatusServiceUnavailable, bt)
+               errResp := pixiuHttp.BadGateway.WithError(fmt.Errorf("newURL 
error: %w", err))
+               hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
@@ -210,8 +211,8 @@ func (f *Filter) Decode(hc *pixiuHttp.HttpContext) 
filter.FilterStatus {
        invoker := dubboProtocol.Refer(url)
        if invoker == nil {
                logger.Info("[dubbo-go-pixiu] dubbo protocol refer error")
-               bt, _ := json.Marshal(pixiuHttp.ErrResponse{Message: "dubbo 
protocol refer error"})
-               hc.SendLocalReply(http.StatusServiceUnavailable, bt)
+               errResp := pixiuHttp.BadGateway.WithError(fmt.Errorf("upstream 
service error"))
+               hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
@@ -224,13 +225,16 @@ func (f *Filter) Decode(hc *pixiuHttp.HttpContext) 
filter.FilterStatus {
        result.SetAttachments(invoc.Attachments())
 
        if result.Error() != nil {
-               logger.Debugf("[dubbo-go-pixiu] invoke result error %v", 
result.Error())
-               bt, _ := json.Marshal(pixiuHttp.ErrResponse{Message: 
fmt.Sprintf("invoke result error %v", result.Error())})
-               // TODO statusCode I don't know what dubbo returns when it 
times out, first use the string to judge
-               if strings.Contains(result.Error().Error(), "timeout") {
-                       hc.SendLocalReply(http.StatusGatewayTimeout, bt)
+               err := result.Error()
+               logger.Debugf("[dubbo-go-pixiu] invoke result error %v", err)
+               // Prefer reliable timeout detection over substring matching
+               if errors.Is(err, context.DeadlineExceeded) || 
os.IsTimeout(err) {
+                       errResp := 
pixiuHttp.GatewayTimeout.WithError(fmt.Errorf("upstream timeout: %w", err))
+                       hc.SendLocalReply(errResp.Status, errResp.ToJSON())
+                       return filter.Stop
                }
-               hc.SendLocalReply(http.StatusServiceUnavailable, bt)
+               errResp := pixiuHttp.BadGateway.WithError(fmt.Errorf("invoke 
error: %w", err))
+               hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
diff --git a/pkg/filter/http/grpcproxy/grpc.go 
b/pkg/filter/http/grpcproxy/grpc.go
index 64a38a09..cfedb01f 100644
--- a/pkg/filter/http/grpcproxy/grpc.go
+++ b/pkg/filter/http/grpcproxy/grpc.go
@@ -189,7 +189,8 @@ func (f *Filter) Decode(c *http.HttpContext) 
filter.FilterStatus {
        e := server.GetClusterManager().PickEndpoint(re.Cluster, c)
        if e == nil {
                logger.Errorf("%s err {cluster not exists}", loggerHeader)
-               c.SendLocalReply(stdHttp.StatusServiceUnavailable, 
[]byte("cluster not exists"))
+               errResp := 
http.ServiceUnavailable.WithError(errors.New("cluster not exists"))
+               c.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
        // timeout for Dial and Invoke
@@ -208,7 +209,8 @@ func (f *Filter) Decode(c *http.HttpContext) 
filter.FilterStatus {
                clientConn, err = grpc.DialContext(ctx, ep, 
grpc.WithTransportCredentials(insecure.NewCredentials()))
                if err != nil || clientConn == nil {
                        logger.Errorf("%s err {failed to connect to grpc 
service provider}", loggerHeader)
-                       c.SendLocalReply(stdHttp.StatusServiceUnavailable, 
[]byte((fmt.Sprintf("%s", err))))
+                       errResp := 
http.ServiceUnavailable.WithError(fmt.Errorf("endpoint not found: %w", err))
+                       c.SendLocalReply(errResp.Status, errResp.ToJSON())
                        return filter.Stop
                }
        }
@@ -217,7 +219,8 @@ func (f *Filter) Decode(c *http.HttpContext) 
filter.FilterStatus {
        source, err := f.descriptor.getDescriptorSource(context.WithValue(ctx, 
ct.ContextKey(GrpcClientConnKey), clientConn), f.cfg)
        if err != nil {
                logger.Errorf("%s err %s : %s ", loggerHeader, "get desc source 
fail", err)
-               c.SendLocalReply(stdHttp.StatusInternalServerError, 
[]byte("service not config proto file or the server not support reflection 
API"))
+               errResp := 
http.ConfigurationError.WithError(fmt.Errorf("service not config proto file or 
the server not support reflection API"))
+               c.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
        //put DescriptorSource concurrent, del if no need
@@ -226,14 +229,16 @@ func (f *Filter) Decode(c *http.HttpContext) 
filter.FilterStatus {
        dscp, err := source.FindSymbol(svc)
        if err != nil {
                logger.Errorf("%s err {%s}", loggerHeader, "request path 
invalid")
-               c.SendLocalReply(stdHttp.StatusBadRequest, []byte("method not 
allow"))
+               errResp := http.MethodNotAllowed.New()
+               c.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
        svcDesc, ok := dscp.(*desc.ServiceDescriptor)
        if !ok {
                logger.Errorf("%s err {service not expose, %s}", loggerHeader, 
svc)
-               c.SendLocalReply(stdHttp.StatusBadRequest, 
[]byte(fmt.Sprintf("service not expose, %s", svc)))
+               errResp := http.BadRequest.WithError(fmt.Errorf("service not 
exposed: %s", svc))
+               c.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
@@ -242,7 +247,8 @@ func (f *Filter) Decode(c *http.HttpContext) 
filter.FilterStatus {
        err = f.registerExtension(source, mthDesc)
        if err != nil {
                logger.Errorf("%s err {%s}", loggerHeader, "register extension 
failed")
-               c.SendLocalReply(stdHttp.StatusInternalServerError, 
[]byte(fmt.Sprintf("%s", err)))
+               errResp := 
http.ConfigurationError.WithError(fmt.Errorf("register extension failed: %w", 
err))
+               c.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
@@ -252,7 +258,8 @@ func (f *Filter) Decode(c *http.HttpContext) 
filter.FilterStatus {
        err = jsonToProtoMsg(c.Request.Body, grpcReq)
        if err != nil && !errors.Is(err, io.EOF) {
                logger.Errorf("%s err {failed to convert json to proto msg, 
%s}", loggerHeader, err.Error())
-               c.SendLocalReply(stdHttp.StatusInternalServerError, 
[]byte(fmt.Sprintf("%s", err)))
+               errResp := http.BadGateway.WithError(fmt.Errorf("protocol 
conversion error: %w", err))
+               c.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
@@ -267,21 +274,40 @@ func (f *Filter) Decode(c *http.HttpContext) 
filter.FilterStatus {
 
        resp, err := Invoke(ctx, stub, mthDesc, grpcReq, grpc.Header(&md), 
grpc.Trailer(&t))
        // judge err is server side error or not
-       if st, ok := status.FromError(err); !ok || isServerError(st) {
-               if isServerTimeout(st) {
-                       logger.Errorf("%s err {failed to invoke grpc service 
provider because timeout, err:%s}", loggerHeader, err.Error())
-                       c.SendLocalReply(stdHttp.StatusGatewayTimeout, 
[]byte(fmt.Sprintf("%s", err)))
+       if st, ok := status.FromError(err); ok {
+               // Handle client-side gRPC errors (e.g., InvalidArgument)
+               if st.Code() != codes.OK && !isServerError(st) {
+                       logger.Errorf("%s err {gRPC client error, code: %s, 
msg: %s}", loggerHeader, st.Code(), st.Message())
+                       errResp := http.BadGateway.WithError(fmt.Errorf("gRPC 
client error: %w", err))
+                       c.SendLocalReply(errResp.Status, errResp.ToJSON())
                        return filter.Stop
                }
+               // Handle server-side gRPC errors
+               if isServerError(st) {
+                       if isServerTimeout(st) {
+                               logger.Errorf("%s err {failed to invoke grpc 
service provider because timeout, err:%s}", loggerHeader, err.Error())
+                               errResp := 
http.GatewayTimeout.WithError(fmt.Errorf("upstream timeout: %w", err))
+                               c.SendLocalReply(errResp.Status, 
errResp.ToJSON())
+                               return filter.Stop
+                       }
+                       logger.Errorf("%s err {failed to invoke grpc service 
provider, %s}", loggerHeader, err.Error())
+                       errResp := 
http.ServiceUnavailable.WithError(fmt.Errorf("gRPC invoke error: %w", err))
+                       c.SendLocalReply(errResp.Status, errResp.ToJSON())
+                       return filter.Stop
+               }
+       } else if err != nil {
+               // Handle non-gRPC errors
                logger.Errorf("%s err {failed to invoke grpc service provider, 
%s}", loggerHeader, err.Error())
-               c.SendLocalReply(stdHttp.StatusServiceUnavailable, 
[]byte(fmt.Sprintf("%s", err)))
+               errResp := http.ServiceUnavailable.WithError(fmt.Errorf("gRPC 
invoke error: %w", err))
+               c.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
        res, err := protoMsgToJson(resp)
        if err != nil {
                logger.Errorf("%s err {failed to convert proto msg to json, 
%s}", loggerHeader, err.Error())
-               c.SendLocalReply(stdHttp.StatusInternalServerError, 
[]byte(fmt.Sprintf("%s", err)))
+               errResp := http.BadGateway.WithError(fmt.Errorf("protocol 
conversion error: %w", err))
+               c.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
diff --git a/pkg/filter/http/httpproxy/routerfilter.go 
b/pkg/filter/http/httpproxy/routerfilter.go
index bb87be37..92e270be 100644
--- a/pkg/filter/http/httpproxy/routerfilter.go
+++ b/pkg/filter/http/httpproxy/routerfilter.go
@@ -18,7 +18,6 @@
 package httpproxy
 
 import (
-       "encoding/json"
        "errors"
        "fmt"
        "net/http"
@@ -119,8 +118,8 @@ func (f *Filter) Decode(hc *contexthttp.HttpContext) 
filter.FilterStatus {
        endpoint := clusterManager.PickEndpoint(clusterName, hc)
        if endpoint == nil {
                logger.Debugf("[dubbo-go-pixiu] cluster not found endpoint")
-               bt, _ := json.Marshal(contexthttp.ErrResponse{Message: "cluster 
not found endpoint"})
-               hc.SendLocalReply(http.StatusServiceUnavailable, bt)
+               errResp := 
contexthttp.ServiceUnavailable.WithError(errors.New("endpoint not found"))
+               hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
@@ -141,8 +140,8 @@ func (f *Filter) Decode(hc *contexthttp.HttpContext) 
filter.FilterStatus {
 
        req, err = http.NewRequest(r.Method, parsedURL.String(), r.Body)
        if err != nil {
-               bt, _ := json.Marshal(contexthttp.ErrResponse{Message: 
fmt.Sprintf("BUG: new request failed: %v", err)})
-               hc.SendLocalReply(http.StatusInternalServerError, bt)
+               errResp := contexthttp.InternalError.WithError(fmt.Errorf("new 
request failed: %w", err))
+               hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
        req.Header = r.Header
@@ -152,10 +151,12 @@ func (f *Filter) Decode(hc *contexthttp.HttpContext) 
filter.FilterStatus {
                var urlErr *url.Error
                ok := errors.As(err, &urlErr)
                if ok && urlErr.Timeout() {
-                       hc.SendLocalReply(http.StatusGatewayTimeout, 
[]byte(err.Error()))
+                       errResp := 
contexthttp.GatewayTimeout.WithError(fmt.Errorf("upstream timeout: %w", err))
+                       hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                        return filter.Stop
                }
-               hc.SendLocalReply(http.StatusServiceUnavailable, 
[]byte(err.Error()))
+               errResp := 
contexthttp.BadGateway.WithError(fmt.Errorf("upstream service error: %w", err))
+               hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
        logger.Debugf("[dubbo-go-pixiu] client call resp:%v", resp)
diff --git a/pkg/filter/http/remote/call.go b/pkg/filter/http/remote/call.go
index 731c75e8..0308b391 100644
--- a/pkg/filter/http/remote/call.go
+++ b/pkg/filter/http/remote/call.go
@@ -20,7 +20,6 @@ package remote
 import (
        "errors"
        "fmt"
-       "net/http"
        "os"
        "strconv"
        "strings"
@@ -77,6 +76,10 @@ type (
                // Resolver is the Resolver to resolve HTTP requests to Dubbo 
services.
                Resolver string `yaml:"resolver,omitempty" 
json:"resolver,omitempty" default:"StandardDubboResolver"`
        }
+
+       mockResponse struct {
+               Message string `json:"message"`
+       }
 )
 
 func (p *Plugin) Kind() string {
@@ -132,7 +135,8 @@ func (factory *FilterFactory) PrepareFilterChain(ctx 
*contexthttp.HttpContext, c
 func (f *Filter) Decode(c *contexthttp.HttpContext) filter.FilterStatus {
        if f.conf.DubboProxyConfig != nil && 
f.conf.DubboProxyConfig.AutoResolve {
                if err := f.resolve(c); err != nil {
-                       c.SendLocalReply(http.StatusInternalServerError, 
[]byte(fmt.Sprintf("auto resolve err: %s", err)))
+                       errResp := 
contexthttp.ConfigurationError.WithError(fmt.Errorf("auto resolve error: %w", 
err))
+                       c.SendLocalReply(errResp.Status, errResp.ToJSON())
                        return filter.Stop
                }
        }
@@ -140,9 +144,7 @@ func (f *Filter) Decode(c *contexthttp.HttpContext) 
filter.FilterStatus {
        api := c.GetAPI()
 
        if (f.conf.Level == OPEN && api.Mock) || (f.conf.Level == ALL) {
-               c.SourceResp = &contexthttp.ErrResponse{
-                       Message: "mock success",
-               }
+               c.SourceResp = &mockResponse{Message: "mock success"}
                return filter.Continue
        }
 
@@ -159,10 +161,12 @@ func (f *Filter) Decode(c *contexthttp.HttpContext) 
filter.FilterStatus {
        if err != nil {
                logger.Errorf("[dubbo-go-pixiu] client call err: %v!", err)
                if strings.Contains(strings.ToLower(err.Error()), "timeout") {
-                       c.SendLocalReply(http.StatusGatewayTimeout, 
[]byte(fmt.Sprintf("client call timeout err: %s", err)))
+                       errResp := 
contexthttp.GatewayTimeout.WithError(fmt.Errorf("client timeout: %w", err))
+                       c.SendLocalReply(errResp.Status, errResp.ToJSON())
                        return filter.Stop
                }
-               c.SendLocalReply(http.StatusInternalServerError, 
[]byte(fmt.Sprintf("client call err: %s", err)))
+               errResp := 
contexthttp.InternalError.WithError(fmt.Errorf("client call error: %w", err))
+               c.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
diff --git a/pkg/filter/llm/proxy/filter.go b/pkg/filter/llm/proxy/filter.go
index 7edc8bdc..67791229 100644
--- a/pkg/filter/llm/proxy/filter.go
+++ b/pkg/filter/llm/proxy/filter.go
@@ -19,7 +19,6 @@ package proxy
 
 import (
        "bytes"
-       "encoding/json"
        "errors"
        "fmt"
        "io"
@@ -150,14 +149,16 @@ func (factory *FilterFactory) PrepareFilterChain(ctx 
*contexthttp.HttpContext, c
 func (f *Filter) Decode(hc *contexthttp.HttpContext) filter.FilterStatus {
        rEntry := hc.GetRouteEntry()
        if rEntry == nil {
-               sendJSONError(hc, http.StatusBadRequest, "no route entry found 
for request")
+               errResp := contexthttp.BadRequest.WithError(errors.New("no 
route entry found for request"))
+               hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
        logger.Debugf("[dubbo-go-pixiu] client choose endpoint from cluster: 
%v", rEntry.Cluster)
 
        // Ensure the request body can be re-read for retries
        if err := f.prepareRequestBody(hc); err != nil {
-               sendJSONError(hc, http.StatusInternalServerError, 
fmt.Sprintf("failed to read request body: %v", err))
+               errResp := 
contexthttp.InternalError.WithError(fmt.Errorf("failed to read request body: 
%w", err))
+               hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
        defer hc.Request.Body.Close()
@@ -178,10 +179,12 @@ func (f *Filter) Decode(hc *contexthttp.HttpContext) 
filter.FilterStatus {
                logger.Infof("[dubbo-go-pixiu] request execution failed after 
all attempts: %v", err)
                var urlErr *url.Error
                if errors.As(err, &urlErr) && urlErr.Timeout() {
-                       sendJSONError(hc, http.StatusGatewayTimeout, 
err.Error())
+                       errResp := contexthttp.GatewayTimeout.WithError(err)
+                       hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                } else if resp == nil {
                        // This handles errors where no response was ever 
received (e.g., DNS error, connection refused)
-                       sendJSONError(hc, http.StatusServiceUnavailable, 
err.Error())
+                       errResp := contexthttp.ServiceUnavailable.WithError(err)
+                       hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                } else {
                        // A response was received, but it was a failure. Pass 
it along.
                        hc.SourceResp = resp
@@ -370,9 +373,3 @@ func getNextFallbackEndpoint(currentEndpoint 
*model.Endpoint, executor *RequestE
 
        return nextEndpoint
 }
-
-// sendJSONError is a helper to send a structured JSON error message.
-func sendJSONError(hc *contexthttp.HttpContext, code int, message string) {
-       bt, _ := json.Marshal(contexthttp.ErrResponse{Message: message})
-       hc.SendLocalReply(code, bt)
-}
diff --git a/pkg/filter/mcp/mcpserver/filter.go 
b/pkg/filter/mcp/mcpserver/filter.go
index 5392af2e..948a9618 100644
--- a/pkg/filter/mcp/mcpserver/filter.go
+++ b/pkg/filter/mcp/mcpserver/filter.go
@@ -220,7 +220,8 @@ func (f *MCPServerFilter) sendJSONResponse(ctx *MCPContext, 
response any) filter
        responseBody, err := json.Marshal(response)
        if err != nil {
                logger.Errorf("[dubbo-go-pixiu] mcp server failed to marshal 
response: %v", err)
-               ctx.SendLocalReply(http.StatusInternalServerError, 
[]byte("internal server error"))
+               errResp := 
contexthttp.InternalError.WithError(fmt.Errorf("marshal response failed: %w", 
err))
+               ctx.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
diff --git a/pkg/filter/mcp/mcpserver/response.go 
b/pkg/filter/mcp/mcpserver/response.go
index 429976b0..7612bb2b 100644
--- a/pkg/filter/mcp/mcpserver/response.go
+++ b/pkg/filter/mcp/mcpserver/response.go
@@ -30,6 +30,7 @@ import (
 
 import (
        "github.com/apache/dubbo-go-pixiu/pkg/common/extension/filter"
+       contexthttp "github.com/apache/dubbo-go-pixiu/pkg/context/http"
        "github.com/apache/dubbo-go-pixiu/pkg/logger"
 )
 
@@ -140,7 +141,8 @@ func (eh *ErrorHandler) sendResponse(ctx *MCPContext, 
response any) filter.Filte
        responseBody, err := json.Marshal(response)
        if err != nil {
                logger.Errorf("[dubbo-go-pixiu] mcp server failed to marshal 
response: %v", err)
-               ctx.SendLocalReply(http.StatusInternalServerError, 
[]byte("internal server error"))
+               errResp := 
contexthttp.InternalError.WithError(fmt.Errorf("marshal response failed: %w", 
err))
+               ctx.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
 
diff --git a/pkg/filter/prometheus/metric.go b/pkg/filter/prometheus/metric.go
index c37bf5fe..044c4d00 100644
--- a/pkg/filter/prometheus/metric.go
+++ b/pkg/filter/prometheus/metric.go
@@ -17,10 +17,6 @@
 
 package prometheus
 
-import (
-       stdHttp "net/http"
-)
-
 import (
        "github.com/apache/dubbo-go-pixiu/pkg/common/constant"
        "github.com/apache/dubbo-go-pixiu/pkg/common/extension/filter"
@@ -91,23 +87,28 @@ func (f *Filter) Decode(ctx *contextHttp.HttpContext) 
filter.FilterStatus {
 
        if f.Cfg == nil {
                logger.Errorf("Message:Filter Metric Collect Configuration is 
null")
-               ctx.SendLocalReply(stdHttp.StatusForbidden, 
constant.Default403Body)
-               return filter.Continue
+               errResp := contextHttp.Forbidden.New()
+               ctx.SendLocalReply(errResp.Status, errResp.ToJSON())
+               return filter.Stop
        }
        if f.Prom == nil {
                logger.Errorf("Message:Prometheus Collector is not initialized")
-               ctx.SendLocalReply(stdHttp.StatusForbidden, 
constant.Default403Body)
-               return filter.Continue
+               errResp := contextHttp.Forbidden.New()
+               ctx.SendLocalReply(errResp.Status, errResp.ToJSON())
+               return filter.Stop
        }
        if f.Cfg.Rules.CounterPush && f.Cfg.Rules.PushIntervalThreshold == 0 {
-               ctx.SendLocalReply(stdHttp.StatusForbidden, 
constant.Default403Body)
-               return filter.Continue
+               errResp := contextHttp.Forbidden.New()
+               ctx.SendLocalReply(errResp.Status, errResp.ToJSON())
+               return filter.Stop
        }
        start := f.Prom.HandlerFunc()
        err := start(ctx)
        if err != nil {
                logger.Errorf("Message:Context HandlerFunc error")
-               ctx.SendLocalReply(stdHttp.StatusForbidden, 
constant.Default403Body)
+               errResp := contextHttp.Forbidden.New()
+               ctx.SendLocalReply(errResp.Status, errResp.ToJSON())
+               return filter.Stop
        }
        return filter.Continue
 }
diff --git a/pkg/filter/sentinel/circuitbreaker/circuit_breaker.go 
b/pkg/filter/sentinel/circuitbreaker/circuit_breaker.go
index ce972dd7..fd22ad88 100644
--- a/pkg/filter/sentinel/circuitbreaker/circuit_breaker.go
+++ b/pkg/filter/sentinel/circuitbreaker/circuit_breaker.go
@@ -19,7 +19,6 @@ package circuitbreaker
 
 import (
        "fmt"
-       stdHttp "net/http"
        "strings"
 )
 
@@ -97,7 +96,8 @@ func (f *Filter) Decode(ctx *http.HttpContext) 
filter.FilterStatus {
 
        // if blockErr not nil, indicates the request was blocked by Sentinel
        if blockErr != nil {
-               ctx.SendLocalReply(stdHttp.StatusServiceUnavailable, 
constant.Default503Body)
+               errResp := http.ServiceUnavailable.New()
+               ctx.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
        defer entry.Exit()
diff --git a/pkg/filter/sentinel/ratelimit/rate_limit.go 
b/pkg/filter/sentinel/ratelimit/rate_limit.go
index 6dfa59d1..5ea4ad91 100644
--- a/pkg/filter/sentinel/ratelimit/rate_limit.go
+++ b/pkg/filter/sentinel/ratelimit/rate_limit.go
@@ -17,11 +17,6 @@
 
 package ratelimit
 
-import (
-       "encoding/json"
-       "net/http"
-)
-
 import (
        sentinel "github.com/alibaba/sentinel-golang/api"
        "github.com/alibaba/sentinel-golang/core/base"
@@ -91,8 +86,8 @@ func (f *Filter) Decode(hc *contexthttp.HttpContext) 
filter.FilterStatus {
 
        //if blockErr not nil, indicates the request was blocked by Sentinel
        if blockErr != nil {
-               bt, _ := json.Marshal(contexthttp.ErrResponse{Message: "blocked 
by rate limit"})
-               hc.SendLocalReply(http.StatusTooManyRequests, bt)
+               errResp := contexthttp.RateLimited.New()
+               hc.SendLocalReply(errResp.Status, errResp.ToJSON())
                return filter.Stop
        }
        defer entry.Exit()

Reply via email to