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()