|
@@ -43,9 +43,7 @@ const (
|
|
UnescapingModeDefault = UnescapingModeLegacy
|
|
UnescapingModeDefault = UnescapingModeLegacy
|
|
)
|
|
)
|
|
|
|
|
|
-var (
|
|
|
|
- encodedPathSplitter = regexp.MustCompile("(/|%2F)")
|
|
|
|
-)
|
|
|
|
|
|
+var encodedPathSplitter = regexp.MustCompile("(/|%2F)")
|
|
|
|
|
|
// A HandlerFunc handles a specific pair of path pattern and HTTP method.
|
|
// A HandlerFunc handles a specific pair of path pattern and HTTP method.
|
|
type HandlerFunc func(w http.ResponseWriter, r *http.Request, pathParams map[string]string)
|
|
type HandlerFunc func(w http.ResponseWriter, r *http.Request, pathParams map[string]string)
|
|
@@ -82,7 +80,7 @@ func WithForwardResponseOption(forwardResponseOption func(context.Context, http.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-// WithEscapingType sets the escaping type. See the definitions of UnescapingMode
|
|
|
|
|
|
+// WithUnescapingMode sets the escaping type. See the definitions of UnescapingMode
|
|
// for more information.
|
|
// for more information.
|
|
func WithUnescapingMode(mode UnescapingMode) ServeMuxOption {
|
|
func WithUnescapingMode(mode UnescapingMode) ServeMuxOption {
|
|
return func(serveMux *ServeMux) {
|
|
return func(serveMux *ServeMux) {
|
|
@@ -103,13 +101,14 @@ func SetQueryParameterParser(queryParameterParser QueryParameterParser) ServeMux
|
|
type HeaderMatcherFunc func(string) (string, bool)
|
|
type HeaderMatcherFunc func(string) (string, bool)
|
|
|
|
|
|
// DefaultHeaderMatcher is used to pass http request headers to/from gRPC context. This adds permanent HTTP header
|
|
// DefaultHeaderMatcher is used to pass http request headers to/from gRPC context. This adds permanent HTTP header
|
|
-// keys (as specified by the IANA) to gRPC context with grpcgateway- prefix. HTTP headers that start with
|
|
|
|
-// 'Grpc-Metadata-' are mapped to gRPC metadata after removing prefix 'Grpc-Metadata-'.
|
|
|
|
|
|
+// keys (as specified by the IANA, e.g: Accept, Cookie, Host) to the gRPC metadata with the grpcgateway- prefix. If you want to know which headers are considered permanent, you can view the isPermanentHTTPHeader function.
|
|
|
|
+// HTTP headers that start with 'Grpc-Metadata-' are mapped to gRPC metadata after removing the prefix 'Grpc-Metadata-'.
|
|
|
|
+// Other headers are not added to the gRPC metadata.
|
|
func DefaultHeaderMatcher(key string) (string, bool) {
|
|
func DefaultHeaderMatcher(key string) (string, bool) {
|
|
- key = textproto.CanonicalMIMEHeaderKey(key)
|
|
|
|
- if isPermanentHTTPHeader(key) {
|
|
|
|
|
|
+ switch key = textproto.CanonicalMIMEHeaderKey(key); {
|
|
|
|
+ case isPermanentHTTPHeader(key):
|
|
return MetadataPrefix + key, true
|
|
return MetadataPrefix + key, true
|
|
- } else if strings.HasPrefix(key, MetadataHeaderPrefix) {
|
|
|
|
|
|
+ case strings.HasPrefix(key, MetadataHeaderPrefix):
|
|
return key[len(MetadataHeaderPrefix):], true
|
|
return key[len(MetadataHeaderPrefix):], true
|
|
}
|
|
}
|
|
return "", false
|
|
return "", false
|
|
@@ -232,7 +231,6 @@ func WithHealthEndpointAt(healthCheckClient grpc_health_v1.HealthClient, endpoin
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
|
if resp.GetStatus() != grpc_health_v1.HealthCheckResponse_SERVING {
|
|
if resp.GetStatus() != grpc_health_v1.HealthCheckResponse_SERVING {
|
|
- var err error
|
|
|
|
switch resp.GetStatus() {
|
|
switch resp.GetStatus() {
|
|
case grpc_health_v1.HealthCheckResponse_NOT_SERVING, grpc_health_v1.HealthCheckResponse_UNKNOWN:
|
|
case grpc_health_v1.HealthCheckResponse_NOT_SERVING, grpc_health_v1.HealthCheckResponse_UNKNOWN:
|
|
err = status.Error(codes.Unavailable, resp.String())
|
|
err = status.Error(codes.Unavailable, resp.String())
|
|
@@ -322,17 +320,6 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
path = r.URL.RawPath
|
|
path = r.URL.RawPath
|
|
}
|
|
}
|
|
|
|
|
|
- var components []string
|
|
|
|
- // since in UnescapeModeLegacy, the URL will already have been fully unescaped, if we also split on "%2F"
|
|
|
|
- // in this escaping mode we would be double unescaping but in UnescapingModeAllCharacters, we still do as the
|
|
|
|
- // path is the RawPath (i.e. unescaped). That does mean that the behavior of this function will change its default
|
|
|
|
- // behavior when the UnescapingModeDefault gets changed from UnescapingModeLegacy to UnescapingModeAllExceptReserved
|
|
|
|
- if s.unescapingMode == UnescapingModeAllCharacters {
|
|
|
|
- components = encodedPathSplitter.Split(path[1:], -1)
|
|
|
|
- } else {
|
|
|
|
- components = strings.Split(path[1:], "/")
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
if override := r.Header.Get("X-HTTP-Method-Override"); override != "" && s.isPathLengthFallback(r) {
|
|
if override := r.Header.Get("X-HTTP-Method-Override"); override != "" && s.isPathLengthFallback(r) {
|
|
r.Method = strings.ToUpper(override)
|
|
r.Method = strings.ToUpper(override)
|
|
if err := r.ParseForm(); err != nil {
|
|
if err := r.ParseForm(); err != nil {
|
|
@@ -343,8 +330,18 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // Verb out here is to memoize for the fallback case below
|
|
|
|
- var verb string
|
|
|
|
|
|
+ var pathComponents []string
|
|
|
|
+ // since in UnescapeModeLegacy, the URL will already have been fully unescaped, if we also split on "%2F"
|
|
|
|
+ // in this escaping mode we would be double unescaping but in UnescapingModeAllCharacters, we still do as the
|
|
|
|
+ // path is the RawPath (i.e. unescaped). That does mean that the behavior of this function will change its default
|
|
|
|
+ // behavior when the UnescapingModeDefault gets changed from UnescapingModeLegacy to UnescapingModeAllExceptReserved
|
|
|
|
+ if s.unescapingMode == UnescapingModeAllCharacters {
|
|
|
|
+ pathComponents = encodedPathSplitter.Split(path[1:], -1)
|
|
|
|
+ } else {
|
|
|
|
+ pathComponents = strings.Split(path[1:], "/")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ lastPathComponent := pathComponents[len(pathComponents)-1]
|
|
|
|
|
|
for _, h := range s.handlers[r.Method] {
|
|
for _, h := range s.handlers[r.Method] {
|
|
// If the pattern has a verb, explicitly look for a suffix in the last
|
|
// If the pattern has a verb, explicitly look for a suffix in the last
|
|
@@ -355,23 +352,28 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
// parser because we know what verb we're looking for, however, there
|
|
// parser because we know what verb we're looking for, however, there
|
|
// are still some cases that the parser itself cannot disambiguate. See
|
|
// are still some cases that the parser itself cannot disambiguate. See
|
|
// the comment there if interested.
|
|
// the comment there if interested.
|
|
|
|
+
|
|
|
|
+ var verb string
|
|
patVerb := h.pat.Verb()
|
|
patVerb := h.pat.Verb()
|
|
- l := len(components)
|
|
|
|
- lastComponent := components[l-1]
|
|
|
|
- var idx int = -1
|
|
|
|
- if patVerb != "" && strings.HasSuffix(lastComponent, ":"+patVerb) {
|
|
|
|
- idx = len(lastComponent) - len(patVerb) - 1
|
|
|
|
|
|
+
|
|
|
|
+ idx := -1
|
|
|
|
+ if patVerb != "" && strings.HasSuffix(lastPathComponent, ":"+patVerb) {
|
|
|
|
+ idx = len(lastPathComponent) - len(patVerb) - 1
|
|
}
|
|
}
|
|
if idx == 0 {
|
|
if idx == 0 {
|
|
_, outboundMarshaler := MarshalerForRequest(s, r)
|
|
_, outboundMarshaler := MarshalerForRequest(s, r)
|
|
s.routingErrorHandler(ctx, s, outboundMarshaler, w, r, http.StatusNotFound)
|
|
s.routingErrorHandler(ctx, s, outboundMarshaler, w, r, http.StatusNotFound)
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ comps := make([]string, len(pathComponents))
|
|
|
|
+ copy(comps, pathComponents)
|
|
|
|
+
|
|
if idx > 0 {
|
|
if idx > 0 {
|
|
- components[l-1], verb = lastComponent[:idx], lastComponent[idx+1:]
|
|
|
|
|
|
+ comps[len(comps)-1], verb = lastPathComponent[:idx], lastPathComponent[idx+1:]
|
|
}
|
|
}
|
|
|
|
|
|
- pathParams, err := h.pat.MatchAndEscape(components, verb, s.unescapingMode)
|
|
|
|
|
|
+ pathParams, err := h.pat.MatchAndEscape(comps, verb, s.unescapingMode)
|
|
if err != nil {
|
|
if err != nil {
|
|
var mse MalformedSequenceError
|
|
var mse MalformedSequenceError
|
|
if ok := errors.As(err, &mse); ok {
|
|
if ok := errors.As(err, &mse); ok {
|
|
@@ -387,14 +389,33 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
- // lookup other methods to handle fallback from GET to POST and
|
|
|
|
- // to determine if it is NotImplemented or NotFound.
|
|
|
|
|
|
+ // if no handler has found for the request, lookup for other methods
|
|
|
|
+ // to handle POST -> GET fallback if the request is subject to path
|
|
|
|
+ // length fallback.
|
|
|
|
+ // Note we are not eagerly checking the request here as we want to return the
|
|
|
|
+ // right HTTP status code, and we need to process the fallback candidates in
|
|
|
|
+ // order to do that.
|
|
for m, handlers := range s.handlers {
|
|
for m, handlers := range s.handlers {
|
|
if m == r.Method {
|
|
if m == r.Method {
|
|
continue
|
|
continue
|
|
}
|
|
}
|
|
for _, h := range handlers {
|
|
for _, h := range handlers {
|
|
- pathParams, err := h.pat.MatchAndEscape(components, verb, s.unescapingMode)
|
|
|
|
|
|
+ var verb string
|
|
|
|
+ patVerb := h.pat.Verb()
|
|
|
|
+
|
|
|
|
+ idx := -1
|
|
|
|
+ if patVerb != "" && strings.HasSuffix(lastPathComponent, ":"+patVerb) {
|
|
|
|
+ idx = len(lastPathComponent) - len(patVerb) - 1
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ comps := make([]string, len(pathComponents))
|
|
|
|
+ copy(comps, pathComponents)
|
|
|
|
+
|
|
|
|
+ if idx > 0 {
|
|
|
|
+ comps[len(comps)-1], verb = lastPathComponent[:idx], lastPathComponent[idx+1:]
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ pathParams, err := h.pat.MatchAndEscape(comps, verb, s.unescapingMode)
|
|
if err != nil {
|
|
if err != nil {
|
|
var mse MalformedSequenceError
|
|
var mse MalformedSequenceError
|
|
if ok := errors.As(err, &mse); ok {
|
|
if ok := errors.As(err, &mse); ok {
|
|
@@ -406,8 +427,11 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
}
|
|
}
|
|
continue
|
|
continue
|
|
}
|
|
}
|
|
|
|
+
|
|
// X-HTTP-Method-Override is optional. Always allow fallback to POST.
|
|
// X-HTTP-Method-Override is optional. Always allow fallback to POST.
|
|
- if s.isPathLengthFallback(r) {
|
|
|
|
|
|
+ // Also, only consider POST -> GET fallbacks, and avoid falling back to
|
|
|
|
+ // potentially dangerous operations like DELETE.
|
|
|
|
+ if s.isPathLengthFallback(r) && m == http.MethodGet {
|
|
if err := r.ParseForm(); err != nil {
|
|
if err := r.ParseForm(); err != nil {
|
|
_, outboundMarshaler := MarshalerForRequest(s, r)
|
|
_, outboundMarshaler := MarshalerForRequest(s, r)
|
|
sterr := status.Error(codes.InvalidArgument, err.Error())
|
|
sterr := status.Error(codes.InvalidArgument, err.Error())
|