mux.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. package runtime
  2. import (
  3. "net/http"
  4. "strings"
  5. "golang.org/x/net/context"
  6. "github.com/golang/protobuf/proto"
  7. )
  8. // A HandlerFunc handles a specific pair of path pattern and HTTP method.
  9. type HandlerFunc func(w http.ResponseWriter, r *http.Request, pathParams map[string]string)
  10. // ServeMux is a request multiplexer for grpc-gateway.
  11. // It matches http requests to patterns and invokes the corresponding handler.
  12. type ServeMux struct {
  13. // handlers maps HTTP method to a list of handlers.
  14. handlers map[string][]handler
  15. forwardResponseOptions []func(context.Context, http.ResponseWriter, proto.Message) error
  16. marshalers marshalerRegistry
  17. }
  18. // ServeMuxOption is an option that can be given to a ServeMux on construction.
  19. type ServeMuxOption func(*ServeMux)
  20. // WithForwardResponseOption returns a ServeMuxOption representing the forwardResponseOption.
  21. //
  22. // forwardResponseOption is an option that will be called on the relevant context.Context,
  23. // http.ResponseWriter, and proto.Message before every forwarded response.
  24. //
  25. // The message may be nil in the case where just a header is being sent.
  26. func WithForwardResponseOption(forwardResponseOption func(context.Context, http.ResponseWriter, proto.Message) error) ServeMuxOption {
  27. return func(serveMux *ServeMux) {
  28. serveMux.forwardResponseOptions = append(serveMux.forwardResponseOptions, forwardResponseOption)
  29. }
  30. }
  31. // NewServeMux returns a new ServeMux whose internal mapping is empty.
  32. func NewServeMux(opts ...ServeMuxOption) *ServeMux {
  33. serveMux := &ServeMux{
  34. handlers: make(map[string][]handler),
  35. forwardResponseOptions: make([]func(context.Context, http.ResponseWriter, proto.Message) error, 0),
  36. marshalers: makeMarshalerMIMERegistry(),
  37. }
  38. for _, opt := range opts {
  39. opt(serveMux)
  40. }
  41. return serveMux
  42. }
  43. // Handle associates "h" to the pair of HTTP method and path pattern.
  44. func (s *ServeMux) Handle(meth string, pat Pattern, h HandlerFunc) {
  45. s.handlers[meth] = append(s.handlers[meth], handler{pat: pat, h: h})
  46. }
  47. // ServeHTTP dispatches the request to the first handler whose pattern matches to r.Method and r.Path.
  48. func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  49. path := r.URL.Path
  50. if !strings.HasPrefix(path, "/") {
  51. OtherErrorHandler(w, r, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
  52. return
  53. }
  54. components := strings.Split(path[1:], "/")
  55. l := len(components)
  56. var verb string
  57. if idx := strings.LastIndex(components[l-1], ":"); idx == 0 {
  58. OtherErrorHandler(w, r, http.StatusText(http.StatusNotFound), http.StatusNotFound)
  59. return
  60. } else if idx > 0 {
  61. c := components[l-1]
  62. components[l-1], verb = c[:idx], c[idx+1:]
  63. }
  64. if override := r.Header.Get("X-HTTP-Method-Override"); override != "" && isPathLengthFallback(r) {
  65. r.Method = strings.ToUpper(override)
  66. if err := r.ParseForm(); err != nil {
  67. OtherErrorHandler(w, r, err.Error(), http.StatusBadRequest)
  68. return
  69. }
  70. }
  71. for _, h := range s.handlers[r.Method] {
  72. pathParams, err := h.pat.Match(components, verb)
  73. if err != nil {
  74. continue
  75. }
  76. h.h(w, r, pathParams)
  77. return
  78. }
  79. // lookup other methods to handle fallback from GET to POST and
  80. // to determine if it is MethodNotAllowed or NotFound.
  81. for m, handlers := range s.handlers {
  82. if m == r.Method {
  83. continue
  84. }
  85. for _, h := range handlers {
  86. pathParams, err := h.pat.Match(components, verb)
  87. if err != nil {
  88. continue
  89. }
  90. // X-HTTP-Method-Override is optional. Always allow fallback to POST.
  91. if isPathLengthFallback(r) {
  92. if err := r.ParseForm(); err != nil {
  93. OtherErrorHandler(w, r, err.Error(), http.StatusBadRequest)
  94. return
  95. }
  96. h.h(w, r, pathParams)
  97. return
  98. }
  99. OtherErrorHandler(w, r, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
  100. return
  101. }
  102. }
  103. OtherErrorHandler(w, r, http.StatusText(http.StatusNotFound), http.StatusNotFound)
  104. }
  105. // GetForwardResponseOptions returns the ForwardResponseOptions associated with this ServeMux.
  106. func (s *ServeMux) GetForwardResponseOptions() []func(context.Context, http.ResponseWriter, proto.Message) error {
  107. return s.forwardResponseOptions
  108. }
  109. func isPathLengthFallback(r *http.Request) bool {
  110. return r.Method == "POST" && r.Header.Get("Content-Type") == "application/x-www-form-urlencoded"
  111. }
  112. type handler struct {
  113. pat Pattern
  114. h HandlerFunc
  115. }