c40779224f
Fixes #52
133 lines
4.3 KiB
Go
133 lines
4.3 KiB
Go
package runtime
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"golang.org/x/net/context"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
)
|
|
|
|
// 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)
|
|
|
|
// ServeMux is a request multiplexer for grpc-gateway.
|
|
// It matches http requests to patterns and invokes the corresponding handler.
|
|
type ServeMux struct {
|
|
// handlers maps HTTP method to a list of handlers.
|
|
handlers map[string][]handler
|
|
forwardResponseOptions []func(context.Context, http.ResponseWriter, proto.Message) error
|
|
marshalers marshalerRegistry
|
|
}
|
|
|
|
// ServeMuxOption is an option that can be given to a ServeMux on construction.
|
|
type ServeMuxOption func(*ServeMux)
|
|
|
|
// WithForwardResponseOption returns a ServeMuxOption representing the forwardResponseOption.
|
|
//
|
|
// forwardResponseOption is an option that will be called on the relevant context.Context,
|
|
// http.ResponseWriter, and proto.Message before every forwarded response.
|
|
//
|
|
// The message may be nil in the case where just a header is being sent.
|
|
func WithForwardResponseOption(forwardResponseOption func(context.Context, http.ResponseWriter, proto.Message) error) ServeMuxOption {
|
|
return func(serveMux *ServeMux) {
|
|
serveMux.forwardResponseOptions = append(serveMux.forwardResponseOptions, forwardResponseOption)
|
|
}
|
|
}
|
|
|
|
// NewServeMux returns a new ServeMux whose internal mapping is empty.
|
|
func NewServeMux(opts ...ServeMuxOption) *ServeMux {
|
|
serveMux := &ServeMux{
|
|
handlers: make(map[string][]handler),
|
|
forwardResponseOptions: make([]func(context.Context, http.ResponseWriter, proto.Message) error, 0),
|
|
marshalers: makeMarshalerMIMERegistry(),
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(serveMux)
|
|
}
|
|
return serveMux
|
|
}
|
|
|
|
// Handle associates "h" to the pair of HTTP method and path pattern.
|
|
func (s *ServeMux) Handle(meth string, pat Pattern, h HandlerFunc) {
|
|
s.handlers[meth] = append(s.handlers[meth], handler{pat: pat, h: h})
|
|
}
|
|
|
|
// ServeHTTP dispatches the request to the first handler whose pattern matches to r.Method and r.Path.
|
|
func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
path := r.URL.Path
|
|
if !strings.HasPrefix(path, "/") {
|
|
OtherErrorHandler(w, r, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
components := strings.Split(path[1:], "/")
|
|
l := len(components)
|
|
var verb string
|
|
if idx := strings.LastIndex(components[l-1], ":"); idx == 0 {
|
|
OtherErrorHandler(w, r, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
|
return
|
|
} else if idx > 0 {
|
|
c := components[l-1]
|
|
components[l-1], verb = c[:idx], c[idx+1:]
|
|
}
|
|
|
|
if override := r.Header.Get("X-HTTP-Method-Override"); override != "" && isPathLengthFallback(r) {
|
|
r.Method = strings.ToUpper(override)
|
|
if err := r.ParseForm(); err != nil {
|
|
OtherErrorHandler(w, r, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
}
|
|
for _, h := range s.handlers[r.Method] {
|
|
pathParams, err := h.pat.Match(components, verb)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
h.h(w, r, pathParams)
|
|
return
|
|
}
|
|
|
|
// lookup other methods to handle fallback from GET to POST and
|
|
// to determine if it is MethodNotAllowed or NotFound.
|
|
for m, handlers := range s.handlers {
|
|
if m == r.Method {
|
|
continue
|
|
}
|
|
for _, h := range handlers {
|
|
pathParams, err := h.pat.Match(components, verb)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
// X-HTTP-Method-Override is optional. Always allow fallback to POST.
|
|
if isPathLengthFallback(r) {
|
|
if err := r.ParseForm(); err != nil {
|
|
OtherErrorHandler(w, r, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
h.h(w, r, pathParams)
|
|
return
|
|
}
|
|
OtherErrorHandler(w, r, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
}
|
|
OtherErrorHandler(w, r, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
|
}
|
|
|
|
// GetForwardResponseOptions returns the ForwardResponseOptions associated with this ServeMux.
|
|
func (s *ServeMux) GetForwardResponseOptions() []func(context.Context, http.ResponseWriter, proto.Message) error {
|
|
return s.forwardResponseOptions
|
|
}
|
|
|
|
func isPathLengthFallback(r *http.Request) bool {
|
|
return r.Method == "POST" && r.Header.Get("Content-Type") == "application/x-www-form-urlencoded"
|
|
}
|
|
|
|
type handler struct {
|
|
pat Pattern
|
|
h HandlerFunc
|
|
}
|