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 }