chore: always ignore unknown fields for gateway requests (#5072)

Signed-off-by: kevin <wanjunfeng@gmail.com>
This commit is contained in:
Kevin Wan
2025-08-09 13:07:17 +08:00
committed by GitHub
parent 00e67b9d20
commit 2583673c8b
4 changed files with 43 additions and 57 deletions

View File

@@ -10,10 +10,6 @@ type (
GatewayConf struct { GatewayConf struct {
rest.RestConf rest.RestConf
Upstreams []Upstream Upstreams []Upstream
// IgnoreUnknownFields specifies whether to ignore unknown fields during proto unmarshaling.
// When set to true, extra query parameters or JSON fields that don't exist in the gRPC message
// will be ignored instead of causing an error.
IgnoreUnknownFields bool `json:",default=false"`
} }
// HttpClientConf is the configuration for an HTTP client. // HttpClientConf is the configuration for an HTTP client.

View File

@@ -13,7 +13,7 @@ import (
) )
// NewRequestParser creates a new request parser from the given http.Request and resolver. // NewRequestParser creates a new request parser from the given http.Request and resolver.
func NewRequestParser(r *http.Request, resolver jsonpb.AnyResolver, ignoreUnknownFields bool) (grpcurl.RequestParser, error) { func NewRequestParser(r *http.Request, resolver jsonpb.AnyResolver) (grpcurl.RequestParser, error) {
vars := pathvar.Vars(r) vars := pathvar.Vars(r)
params, err := httpx.GetFormValues(r) params, err := httpx.GetFormValues(r)
if err != nil { if err != nil {
@@ -26,14 +26,11 @@ func NewRequestParser(r *http.Request, resolver jsonpb.AnyResolver, ignoreUnknow
body, ok := getBody(r) body, ok := getBody(r)
if !ok { if !ok {
return buildJsonRequestParserFromMap(params, resolver, ignoreUnknownFields) return buildJsonRequestParserFromMap(params, resolver)
} }
if len(params) == 0 { if len(params) == 0 {
if ignoreUnknownFields { return buildJsonRequestParserFromReader(body, resolver)
return buildJsonRequestParserWithUnknownFields(body, resolver)
}
return buildJsonRequestParser(body, resolver)
} }
m := make(map[string]any) m := make(map[string]any)
@@ -45,32 +42,27 @@ func NewRequestParser(r *http.Request, resolver jsonpb.AnyResolver, ignoreUnknow
m[k] = v m[k] = v
} }
return buildJsonRequestParserFromMap(m, resolver, ignoreUnknownFields) return buildJsonRequestParserFromMap(m, resolver)
} }
func buildJsonRequestParserFromMap(data map[string]any, resolver jsonpb.AnyResolver, ignoreUnknownFields bool) (grpcurl.RequestParser, error) { func buildJsonRequestParserFromMap(data map[string]any, resolver jsonpb.AnyResolver) (
grpcurl.RequestParser, error) {
var buf bytes.Buffer var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(data); err != nil { if err := json.NewEncoder(&buf).Encode(data); err != nil {
return nil, err return nil, err
} }
if ignoreUnknownFields { return buildJsonRequestParserFromReader(&buf, resolver)
return buildJsonRequestParserWithUnknownFields(&buf, resolver)
}
return buildJsonRequestParser(&buf, resolver)
} }
// buildJsonRequestParser creates a JSON request parser with default settings // buildJsonRequestParserFromReader creates a JSON request parser with ignoring unknown fields.
func buildJsonRequestParser(data io.Reader, resolver jsonpb.AnyResolver) (grpcurl.RequestParser, error) { func buildJsonRequestParserFromReader(data io.Reader, resolver jsonpb.AnyResolver) (
return grpcurl.NewJSONRequestParser(data, resolver), nil grpcurl.RequestParser, error) {
}
// buildJsonRequestParserWithUnknownFields creates a JSON request parser that ignores unknown fields
func buildJsonRequestParserWithUnknownFields(data io.Reader, resolver jsonpb.AnyResolver) (grpcurl.RequestParser, error) {
unmarshaler := jsonpb.Unmarshaler{ unmarshaler := jsonpb.Unmarshaler{
AllowUnknownFields: true, AllowUnknownFields: true,
AnyResolver: resolver, AnyResolver: resolver,
} }
return grpcurl.NewJSONRequestParserWithUnmarshaler(data, unmarshaler), nil return grpcurl.NewJSONRequestParserWithUnmarshaler(data, unmarshaler), nil
} }

View File

@@ -14,7 +14,7 @@ import (
func TestNewRequestParserNoVar(t *testing.T) { func TestNewRequestParserNoVar(t *testing.T) {
req := httptest.NewRequest("GET", "/", http.NoBody) req := httptest.NewRequest("GET", "/", http.NoBody)
parser, err := NewRequestParser(req, nil, false) parser, err := NewRequestParser(req, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, parser) assert.NotNil(t, parser)
} }
@@ -22,14 +22,14 @@ func TestNewRequestParserNoVar(t *testing.T) {
func TestNewRequestParserWithVars(t *testing.T) { func TestNewRequestParserWithVars(t *testing.T) {
req := httptest.NewRequest("GET", "/", http.NoBody) req := httptest.NewRequest("GET", "/", http.NoBody)
req = pathvar.WithVars(req, map[string]string{"a": "b"}) req = pathvar.WithVars(req, map[string]string{"a": "b"})
parser, err := NewRequestParser(req, nil, false) parser, err := NewRequestParser(req, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, parser) assert.NotNil(t, parser)
} }
func TestNewRequestParserNoVarWithBody(t *testing.T) { func TestNewRequestParserNoVarWithBody(t *testing.T) {
req := httptest.NewRequest("GET", "/", strings.NewReader(`{"a": "b"}`)) req := httptest.NewRequest("GET", "/", strings.NewReader(`{"a": "b"}`))
parser, err := NewRequestParser(req, nil, false) parser, err := NewRequestParser(req, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, parser) assert.NotNil(t, parser)
} }
@@ -37,7 +37,7 @@ func TestNewRequestParserNoVarWithBody(t *testing.T) {
func TestNewRequestParserWithNegativeContentLength(t *testing.T) { func TestNewRequestParserWithNegativeContentLength(t *testing.T) {
req := httptest.NewRequest("GET", "/", strings.NewReader(`{"a": "b"}`)) req := httptest.NewRequest("GET", "/", strings.NewReader(`{"a": "b"}`))
req.ContentLength = -1 req.ContentLength = -1
parser, err := NewRequestParser(req, nil, false) parser, err := NewRequestParser(req, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, parser) assert.NotNil(t, parser)
} }
@@ -45,7 +45,7 @@ func TestNewRequestParserWithNegativeContentLength(t *testing.T) {
func TestNewRequestParserWithVarsWithBody(t *testing.T) { func TestNewRequestParserWithVarsWithBody(t *testing.T) {
req := httptest.NewRequest("GET", "/", strings.NewReader(`{"a": "b"}`)) req := httptest.NewRequest("GET", "/", strings.NewReader(`{"a": "b"}`))
req = pathvar.WithVars(req, map[string]string{"c": "d"}) req = pathvar.WithVars(req, map[string]string{"c": "d"})
parser, err := NewRequestParser(req, nil, false) parser, err := NewRequestParser(req, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, parser) assert.NotNil(t, parser)
} }
@@ -53,14 +53,14 @@ func TestNewRequestParserWithVarsWithBody(t *testing.T) {
func TestNewRequestParserWithVarsWithWrongBody(t *testing.T) { func TestNewRequestParserWithVarsWithWrongBody(t *testing.T) {
req := httptest.NewRequest("GET", "/", strings.NewReader(`{"a": "b"`)) req := httptest.NewRequest("GET", "/", strings.NewReader(`{"a": "b"`))
req = pathvar.WithVars(req, map[string]string{"c": "d"}) req = pathvar.WithVars(req, map[string]string{"c": "d"})
parser, err := NewRequestParser(req, nil, false) parser, err := NewRequestParser(req, nil)
assert.NotNil(t, err) assert.NotNil(t, err)
assert.Nil(t, parser) assert.Nil(t, parser)
} }
func TestNewRequestParserWithForm(t *testing.T) { func TestNewRequestParserWithForm(t *testing.T) {
req := httptest.NewRequest("GET", "/val?a=b", nil) req := httptest.NewRequest("GET", "/val?a=b", nil)
parser, err := NewRequestParser(req, nil, false) parser, err := NewRequestParser(req, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, parser) assert.NotNil(t, parser)
} }
@@ -68,7 +68,7 @@ func TestNewRequestParserWithForm(t *testing.T) {
func TestNewRequestParserWithNilBody(t *testing.T) { func TestNewRequestParserWithNilBody(t *testing.T) {
req := httptest.NewRequest("GET", "/val?a=b", http.NoBody) req := httptest.NewRequest("GET", "/val?a=b", http.NoBody)
req.Body = nil req.Body = nil
parser, err := NewRequestParser(req, nil, false) parser, err := NewRequestParser(req, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, parser) assert.NotNil(t, parser)
} }
@@ -76,20 +76,20 @@ func TestNewRequestParserWithNilBody(t *testing.T) {
func TestNewRequestParserWithBadBody(t *testing.T) { func TestNewRequestParserWithBadBody(t *testing.T) {
req := httptest.NewRequest("GET", "/val?a=b", badBody{}) req := httptest.NewRequest("GET", "/val?a=b", badBody{})
req.Body = badBody{} req.Body = badBody{}
parser, err := NewRequestParser(req, nil, false) parser, err := NewRequestParser(req, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, parser) assert.NotNil(t, parser)
} }
func TestNewRequestParserWithBadForm(t *testing.T) { func TestNewRequestParserWithBadForm(t *testing.T) {
req := httptest.NewRequest("GET", "/val?a%1=b", http.NoBody) req := httptest.NewRequest("GET", "/val?a%1=b", http.NoBody)
parser, err := NewRequestParser(req, nil, false) parser, err := NewRequestParser(req, nil)
assert.NotNil(t, err) assert.NotNil(t, err)
assert.Nil(t, parser) assert.Nil(t, parser)
} }
func TestRequestParser_buildJsonRequestParserFromMap(t *testing.T) { func TestRequestParser_buildJsonRequestParserFromMap(t *testing.T) {
parser, err := buildJsonRequestParserFromMap(map[string]any{"a": make(chan int)}, nil, false) parser, err := buildJsonRequestParserFromMap(map[string]any{"a": make(chan int)}, nil)
assert.NotNil(t, err) assert.NotNil(t, err)
assert.Nil(t, parser) assert.Nil(t, parser)
} }
@@ -107,23 +107,23 @@ func TestNewRequestParserWithIgnoreUnknownFields(t *testing.T) {
// Test case 1: No body, no vars - should work with both true and false // Test case 1: No body, no vars - should work with both true and false
req1 := httptest.NewRequest("GET", "/", http.NoBody) req1 := httptest.NewRequest("GET", "/", http.NoBody)
parser1, err1 := NewRequestParser(req1, resolver, true) parser1, err1 := NewRequestParser(req1, resolver)
assert.Nil(t, err1) assert.Nil(t, err1)
assert.NotNil(t, parser1) assert.NotNil(t, parser1)
req2 := httptest.NewRequest("GET", "/", http.NoBody) req2 := httptest.NewRequest("GET", "/", http.NoBody)
parser2, err2 := NewRequestParser(req2, resolver, false) parser2, err2 := NewRequestParser(req2, resolver)
assert.Nil(t, err2) assert.Nil(t, err2)
assert.NotNil(t, parser2) assert.NotNil(t, parser2)
// Test case 2: With JSON body - tests the body parsing path // Test case 2: With JSON body - tests the body parsing path
req3 := httptest.NewRequest("POST", "/", strings.NewReader(`{"field": "value"}`)) req3 := httptest.NewRequest("POST", "/", strings.NewReader(`{"field": "value"}`))
parser3, err3 := NewRequestParser(req3, resolver, true) parser3, err3 := NewRequestParser(req3, resolver)
assert.Nil(t, err3) assert.Nil(t, err3)
assert.NotNil(t, parser3) assert.NotNil(t, parser3)
req4 := httptest.NewRequest("POST", "/", strings.NewReader(`{"field": "value"}`)) req4 := httptest.NewRequest("POST", "/", strings.NewReader(`{"field": "value"}`))
parser4, err4 := NewRequestParser(req4, resolver, false) parser4, err4 := NewRequestParser(req4, resolver)
assert.Nil(t, err4) assert.Nil(t, err4)
assert.NotNil(t, parser4) assert.NotNil(t, parser4)
} }
@@ -134,14 +134,14 @@ func TestNewRequestParserWithVarsAndIgnoreUnknownFields(t *testing.T) {
// Test with path variables and ignoreUnknownFields = true // Test with path variables and ignoreUnknownFields = true
req := httptest.NewRequest("GET", "/", http.NoBody) req := httptest.NewRequest("GET", "/", http.NoBody)
req = pathvar.WithVars(req, map[string]string{"a": "b"}) req = pathvar.WithVars(req, map[string]string{"a": "b"})
parser, err := NewRequestParser(req, resolver, true) parser, err := NewRequestParser(req, resolver)
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, parser) assert.NotNil(t, parser)
// Test with path variables and ignoreUnknownFields = false // Test with path variables and ignoreUnknownFields = false
req2 := httptest.NewRequest("GET", "/", http.NoBody) req2 := httptest.NewRequest("GET", "/", http.NoBody)
req2 = pathvar.WithVars(req2, map[string]string{"c": "d"}) req2 = pathvar.WithVars(req2, map[string]string{"c": "d"})
parser2, err2 := NewRequestParser(req2, resolver, false) parser2, err2 := NewRequestParser(req2, resolver)
assert.Nil(t, err2) assert.Nil(t, err2)
assert.NotNil(t, parser2) assert.NotNil(t, parser2)
} }
@@ -151,13 +151,13 @@ func TestNewRequestParserWithBodyAndIgnoreUnknownFields(t *testing.T) {
// Test with body and ignoreUnknownFields = true // Test with body and ignoreUnknownFields = true
req := httptest.NewRequest("POST", "/", strings.NewReader(`{"a": "b"}`)) req := httptest.NewRequest("POST", "/", strings.NewReader(`{"a": "b"}`))
parser, err := NewRequestParser(req, resolver, true) parser, err := NewRequestParser(req, resolver)
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, parser) assert.NotNil(t, parser)
// Test with body and ignoreUnknownFields = false // Test with body and ignoreUnknownFields = false
req2 := httptest.NewRequest("POST", "/", strings.NewReader(`{"c": "d"}`)) req2 := httptest.NewRequest("POST", "/", strings.NewReader(`{"c": "d"}`))
parser2, err2 := NewRequestParser(req2, resolver, false) parser2, err2 := NewRequestParser(req2, resolver)
assert.Nil(t, err2) assert.Nil(t, err2)
assert.NotNil(t, parser2) assert.NotNil(t, parser2)
} }
@@ -168,14 +168,14 @@ func TestNewRequestParserWithVarsBodyAndIgnoreUnknownFields(t *testing.T) {
// Test with both path variables and body, ignoreUnknownFields = true // Test with both path variables and body, ignoreUnknownFields = true
req := httptest.NewRequest("POST", "/", strings.NewReader(`{"a": "b"}`)) req := httptest.NewRequest("POST", "/", strings.NewReader(`{"a": "b"}`))
req = pathvar.WithVars(req, map[string]string{"c": "d"}) req = pathvar.WithVars(req, map[string]string{"c": "d"})
parser, err := NewRequestParser(req, resolver, true) parser, err := NewRequestParser(req, resolver)
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, parser) assert.NotNil(t, parser)
// Test with both path variables and body, ignoreUnknownFields = false // Test with both path variables and body, ignoreUnknownFields = false
req2 := httptest.NewRequest("POST", "/", strings.NewReader(`{"e": "f"}`)) req2 := httptest.NewRequest("POST", "/", strings.NewReader(`{"e": "f"}`))
req2 = pathvar.WithVars(req2, map[string]string{"g": "h"}) req2 = pathvar.WithVars(req2, map[string]string{"g": "h"})
parser2, err2 := NewRequestParser(req2, resolver, false) parser2, err2 := NewRequestParser(req2, resolver)
assert.Nil(t, err2) assert.Nil(t, err2)
assert.NotNil(t, parser2) assert.NotNil(t, parser2)
} }
@@ -185,12 +185,12 @@ func TestBuildJsonRequestParserFromMapWithIgnoreUnknownFields(t *testing.T) {
// Test buildJsonRequestParserFromMap with ignoreUnknownFields = true // Test buildJsonRequestParserFromMap with ignoreUnknownFields = true
data := map[string]any{"key": "value"} data := map[string]any{"key": "value"}
parser, err := buildJsonRequestParserFromMap(data, resolver, true) parser, err := buildJsonRequestParserFromMap(data, resolver)
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, parser) assert.NotNil(t, parser)
// Test buildJsonRequestParserFromMap with ignoreUnknownFields = false // Test buildJsonRequestParserFromMap with ignoreUnknownFields = false
parser2, err2 := buildJsonRequestParserFromMap(data, resolver, false) parser2, err2 := buildJsonRequestParserFromMap(data, resolver)
assert.Nil(t, err2) assert.Nil(t, err2)
assert.NotNil(t, parser2) assert.NotNil(t, parser2)
} }
@@ -200,7 +200,7 @@ func TestBuildJsonRequestParserWithUnknownFields(t *testing.T) {
// Test buildJsonRequestParserWithUnknownFields // Test buildJsonRequestParserWithUnknownFields
data := strings.NewReader(`{"test": "value"}`) data := strings.NewReader(`{"test": "value"}`)
parser, err := buildJsonRequestParserWithUnknownFields(data, resolver) parser, err := buildJsonRequestParserFromReader(data, resolver)
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, parser) assert.NotNil(t, parser)
} }

View File

@@ -30,12 +30,11 @@ type (
// Server is a gateway server. // Server is a gateway server.
Server struct { Server struct {
*rest.Server *rest.Server
upstreams []Upstream upstreams []Upstream
conns []zrpc.Client conns []zrpc.Client
processHeader func(http.Header) []string processHeader func(http.Header) []string
dialer func(conf zrpc.RpcClientConf) zrpc.Client dialer func(conf zrpc.RpcClientConf) zrpc.Client
middlewares []rest.Middleware middlewares []rest.Middleware
ignoreUnknownFields bool
} }
// Option defines the method to customize Server. // Option defines the method to customize Server.
@@ -45,9 +44,8 @@ type (
// MustNewServer creates a new gateway server. // MustNewServer creates a new gateway server.
func MustNewServer(c GatewayConf, opts ...Option) *Server { func MustNewServer(c GatewayConf, opts ...Option) *Server {
svr := &Server{ svr := &Server{
upstreams: c.Upstreams, upstreams: c.Upstreams,
Server: rest.MustNewServer(c.RestConf), Server: rest.MustNewServer(c.RestConf),
ignoreUnknownFields: c.IgnoreUnknownFields,
} }
for _, opt := range opts { for _, opt := range opts {
opt(svr) opt(svr)
@@ -116,7 +114,7 @@ func (s *Server) buildChainHandler(handler http.HandlerFunc) http.HandlerFunc {
func (s *Server) buildGrpcHandler(source grpcurl.DescriptorSource, resolver jsonpb.AnyResolver, func (s *Server) buildGrpcHandler(source grpcurl.DescriptorSource, resolver jsonpb.AnyResolver,
cli zrpc.Client, rpcPath string) func(http.ResponseWriter, *http.Request) { cli zrpc.Client, rpcPath string) func(http.ResponseWriter, *http.Request) {
handler := func(w http.ResponseWriter, r *http.Request) { handler := func(w http.ResponseWriter, r *http.Request) {
parser, err := internal.NewRequestParser(r, resolver, s.ignoreUnknownFields) parser, err := internal.NewRequestParser(r, resolver)
if err != nil { if err != nil {
httpx.ErrorCtx(r.Context(), w, err) httpx.ErrorCtx(r.Context(), w, err)
return return