httputils.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. package httputils // import "github.com/docker/docker/api/server/httputils"
  2. import (
  3. "context"
  4. "io"
  5. "mime"
  6. "net/http"
  7. "strings"
  8. "github.com/docker/docker/api/types"
  9. "github.com/docker/docker/api/types/versions"
  10. "github.com/docker/docker/errdefs"
  11. "github.com/gorilla/mux"
  12. "github.com/pkg/errors"
  13. "github.com/sirupsen/logrus"
  14. "google.golang.org/grpc/status"
  15. )
  16. // APIVersionKey is the client's requested API version.
  17. type APIVersionKey struct{}
  18. // APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
  19. // Any function that has the appropriate signature can be registered as an API endpoint (e.g. getVersion).
  20. type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
  21. // HijackConnection interrupts the http response writer to get the
  22. // underlying connection and operate with it.
  23. func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
  24. conn, _, err := w.(http.Hijacker).Hijack()
  25. if err != nil {
  26. return nil, nil, err
  27. }
  28. // Flush the options to make sure the client sets the raw mode
  29. _, _ = conn.Write([]byte{})
  30. return conn, conn, nil
  31. }
  32. // CloseStreams ensures that a list for http streams are properly closed.
  33. func CloseStreams(streams ...interface{}) {
  34. for _, stream := range streams {
  35. if tcpc, ok := stream.(interface {
  36. CloseWrite() error
  37. }); ok {
  38. _ = tcpc.CloseWrite()
  39. } else if closer, ok := stream.(io.Closer); ok {
  40. _ = closer.Close()
  41. }
  42. }
  43. }
  44. // CheckForJSON makes sure that the request's Content-Type is application/json.
  45. func CheckForJSON(r *http.Request) error {
  46. ct := r.Header.Get("Content-Type")
  47. // No Content-Type header is ok as long as there's no Body
  48. if ct == "" {
  49. if r.Body == nil || r.ContentLength == 0 {
  50. return nil
  51. }
  52. }
  53. // Otherwise it better be json
  54. if matchesContentType(ct, "application/json") {
  55. return nil
  56. }
  57. return errdefs.InvalidParameter(errors.Errorf("Content-Type specified (%s) must be 'application/json'", ct))
  58. }
  59. // ParseForm ensures the request form is parsed even with invalid content types.
  60. // If we don't do this, POST method without Content-type (even with empty body) will fail.
  61. func ParseForm(r *http.Request) error {
  62. if r == nil {
  63. return nil
  64. }
  65. if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
  66. return errdefs.InvalidParameter(err)
  67. }
  68. return nil
  69. }
  70. // VersionFromContext returns an API version from the context using APIVersionKey.
  71. // It panics if the context value does not have version.Version type.
  72. func VersionFromContext(ctx context.Context) string {
  73. if ctx == nil {
  74. return ""
  75. }
  76. if val := ctx.Value(APIVersionKey{}); val != nil {
  77. return val.(string)
  78. }
  79. return ""
  80. }
  81. // MakeErrorHandler makes an HTTP handler that decodes a Docker error and
  82. // returns it in the response.
  83. func MakeErrorHandler(err error) http.HandlerFunc {
  84. return func(w http.ResponseWriter, r *http.Request) {
  85. statusCode := errdefs.GetHTTPErrorStatusCode(err)
  86. vars := mux.Vars(r)
  87. if apiVersionSupportsJSONErrors(vars["version"]) {
  88. response := &types.ErrorResponse{
  89. Message: err.Error(),
  90. }
  91. _ = WriteJSON(w, statusCode, response)
  92. } else {
  93. http.Error(w, status.Convert(err).Message(), statusCode)
  94. }
  95. }
  96. }
  97. func apiVersionSupportsJSONErrors(version string) bool {
  98. const firstAPIVersionWithJSONErrors = "1.23"
  99. return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors)
  100. }
  101. // matchesContentType validates the content type against the expected one
  102. func matchesContentType(contentType, expectedType string) bool {
  103. mimetype, _, err := mime.ParseMediaType(contentType)
  104. if err != nil {
  105. logrus.Errorf("Error parsing media type: %s error: %v", contentType, err)
  106. }
  107. return err == nil && mimetype == expectedType
  108. }