You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

151 line
4.4 KiB

  1. // Copyright 2013 The Gorilla Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package handlers
  5. import (
  6. "bufio"
  7. "fmt"
  8. "net"
  9. "net/http"
  10. "sort"
  11. "strings"
  12. )
  13. // MethodHandler is an http.Handler that dispatches to a handler whose key in the
  14. // MethodHandler's map matches the name of the HTTP request's method, eg: GET
  15. //
  16. // If the request's method is OPTIONS and OPTIONS is not a key in the map then
  17. // the handler responds with a status of 200 and sets the Allow header to a
  18. // comma-separated list of available methods.
  19. //
  20. // If the request's method doesn't match any of its keys the handler responds
  21. // with a status of HTTP 405 "Method Not Allowed" and sets the Allow header to a
  22. // comma-separated list of available methods.
  23. type MethodHandler map[string]http.Handler
  24. func (h MethodHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  25. if handler, ok := h[req.Method]; ok {
  26. handler.ServeHTTP(w, req)
  27. } else {
  28. allow := []string{}
  29. for k := range h {
  30. allow = append(allow, k)
  31. }
  32. sort.Strings(allow)
  33. w.Header().Set("Allow", strings.Join(allow, ", "))
  34. if req.Method == http.MethodOptions {
  35. w.WriteHeader(http.StatusOK)
  36. } else {
  37. http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
  38. }
  39. }
  40. }
  41. // responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP
  42. // status code and body size.
  43. type responseLogger struct {
  44. w http.ResponseWriter
  45. status int
  46. size int
  47. }
  48. func (l *responseLogger) Write(b []byte) (int, error) {
  49. size, err := l.w.Write(b)
  50. l.size += size
  51. return size, err
  52. }
  53. func (l *responseLogger) WriteHeader(s int) {
  54. l.w.WriteHeader(s)
  55. l.status = s
  56. }
  57. func (l *responseLogger) Status() int {
  58. return l.status
  59. }
  60. func (l *responseLogger) Size() int {
  61. return l.size
  62. }
  63. func (l *responseLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) {
  64. conn, rw, err := l.w.(http.Hijacker).Hijack()
  65. if err == nil && l.status == 0 {
  66. // The status will be StatusSwitchingProtocols if there was no error and
  67. // WriteHeader has not been called yet
  68. l.status = http.StatusSwitchingProtocols
  69. }
  70. return conn, rw, err
  71. }
  72. // isContentType validates the Content-Type header matches the supplied
  73. // contentType. That is, its type and subtype match.
  74. func isContentType(h http.Header, contentType string) bool {
  75. ct := h.Get("Content-Type")
  76. if i := strings.IndexRune(ct, ';'); i != -1 {
  77. ct = ct[0:i]
  78. }
  79. return ct == contentType
  80. }
  81. // ContentTypeHandler wraps and returns a http.Handler, validating the request
  82. // content type is compatible with the contentTypes list. It writes a HTTP 415
  83. // error if that fails.
  84. //
  85. // Only PUT, POST, and PATCH requests are considered.
  86. func ContentTypeHandler(h http.Handler, contentTypes ...string) http.Handler {
  87. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  88. if !(r.Method == http.MethodPut || r.Method == http.MethodPost || r.Method == http.MethodPatch) {
  89. h.ServeHTTP(w, r)
  90. return
  91. }
  92. for _, ct := range contentTypes {
  93. if isContentType(r.Header, ct) {
  94. h.ServeHTTP(w, r)
  95. return
  96. }
  97. }
  98. http.Error(w, fmt.Sprintf("Unsupported content type %q; expected one of %q",
  99. r.Header.Get("Content-Type"),
  100. contentTypes),
  101. http.StatusUnsupportedMediaType)
  102. })
  103. }
  104. const (
  105. // HTTPMethodOverrideHeader is a commonly used
  106. // http header to override a request method.
  107. HTTPMethodOverrideHeader = "X-HTTP-Method-Override"
  108. // HTTPMethodOverrideFormKey is a commonly used
  109. // HTML form key to override a request method.
  110. HTTPMethodOverrideFormKey = "_method"
  111. )
  112. // HTTPMethodOverrideHandler wraps and returns a http.Handler which checks for
  113. // the X-HTTP-Method-Override header or the _method form key, and overrides (if
  114. // valid) request.Method with its value.
  115. //
  116. // This is especially useful for HTTP clients that don't support many http verbs.
  117. // It isn't secure to override e.g a GET to a POST, so only POST requests are
  118. // considered. Likewise, the override method can only be a "write" method: PUT,
  119. // PATCH or DELETE.
  120. //
  121. // Form method takes precedence over header method.
  122. func HTTPMethodOverrideHandler(h http.Handler) http.Handler {
  123. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  124. if r.Method == http.MethodPost {
  125. om := r.FormValue(HTTPMethodOverrideFormKey)
  126. if om == "" {
  127. om = r.Header.Get(HTTPMethodOverrideHeader)
  128. }
  129. if om == http.MethodPut || om == http.MethodPatch || om == http.MethodDelete {
  130. r.Method = om
  131. }
  132. }
  133. h.ServeHTTP(w, r)
  134. })
  135. }