httputils.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. package httputils // import "github.com/docker/docker/api/server/httputils"
  2. import (
  3. "context"
  4. "encoding/json"
  5. "io"
  6. "mime"
  7. "net/http"
  8. "strings"
  9. "github.com/docker/docker/errdefs"
  10. "github.com/pkg/errors"
  11. )
  12. // APIVersionKey is the client's requested API version.
  13. type APIVersionKey struct{}
  14. // APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
  15. // Any function that has the appropriate signature can be registered as an API endpoint (e.g. getVersion).
  16. type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
  17. // HijackConnection interrupts the http response writer to get the
  18. // underlying connection and operate with it.
  19. func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
  20. conn, _, err := w.(http.Hijacker).Hijack()
  21. if err != nil {
  22. return nil, nil, err
  23. }
  24. // Flush the options to make sure the client sets the raw mode
  25. _, _ = conn.Write([]byte{})
  26. return conn, conn, nil
  27. }
  28. // CloseStreams ensures that a list for http streams are properly closed.
  29. func CloseStreams(streams ...interface{}) {
  30. for _, stream := range streams {
  31. if tcpc, ok := stream.(interface {
  32. CloseWrite() error
  33. }); ok {
  34. _ = tcpc.CloseWrite()
  35. } else if closer, ok := stream.(io.Closer); ok {
  36. _ = closer.Close()
  37. }
  38. }
  39. }
  40. // CheckForJSON makes sure that the request's Content-Type is application/json.
  41. func CheckForJSON(r *http.Request) error {
  42. ct := r.Header.Get("Content-Type")
  43. // No Content-Type header is ok as long as there's no Body
  44. if ct == "" && (r.Body == nil || r.ContentLength == 0) {
  45. return nil
  46. }
  47. // Otherwise it better be json
  48. return matchesContentType(ct, "application/json")
  49. }
  50. // ReadJSON validates the request to have the correct content-type, and decodes
  51. // the request's Body into out.
  52. func ReadJSON(r *http.Request, out interface{}) error {
  53. err := CheckForJSON(r)
  54. if err != nil {
  55. return err
  56. }
  57. if r.Body == nil || r.ContentLength == 0 {
  58. // an empty body is not invalid, so don't return an error; see
  59. // https://lists.w3.org/Archives/Public/ietf-http-wg/2010JulSep/0272.html
  60. return nil
  61. }
  62. dec := json.NewDecoder(r.Body)
  63. err = dec.Decode(out)
  64. defer r.Body.Close()
  65. if err != nil {
  66. if err == io.EOF {
  67. return errdefs.InvalidParameter(errors.New("invalid JSON: got EOF while reading request body"))
  68. }
  69. return errdefs.InvalidParameter(errors.Wrap(err, "invalid JSON"))
  70. }
  71. if dec.More() {
  72. return errdefs.InvalidParameter(errors.New("unexpected content after JSON"))
  73. }
  74. return nil
  75. }
  76. // WriteJSON writes the value v to the http response stream as json with standard json encoding.
  77. func WriteJSON(w http.ResponseWriter, code int, v interface{}) error {
  78. w.Header().Set("Content-Type", "application/json")
  79. w.WriteHeader(code)
  80. enc := json.NewEncoder(w)
  81. enc.SetEscapeHTML(false)
  82. return enc.Encode(v)
  83. }
  84. // ParseForm ensures the request form is parsed even with invalid content types.
  85. // If we don't do this, POST method without Content-type (even with empty body) will fail.
  86. func ParseForm(r *http.Request) error {
  87. if r == nil {
  88. return nil
  89. }
  90. if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
  91. return errdefs.InvalidParameter(err)
  92. }
  93. return nil
  94. }
  95. // VersionFromContext returns an API version from the context using APIVersionKey.
  96. // It panics if the context value does not have version.Version type.
  97. func VersionFromContext(ctx context.Context) string {
  98. if ctx == nil {
  99. return ""
  100. }
  101. if val := ctx.Value(APIVersionKey{}); val != nil {
  102. return val.(string)
  103. }
  104. return ""
  105. }
  106. // matchesContentType validates the content type against the expected one
  107. func matchesContentType(contentType, expectedType string) error {
  108. mimetype, _, err := mime.ParseMediaType(contentType)
  109. if err != nil {
  110. return errdefs.InvalidParameter(errors.Wrapf(err, "malformed Content-Type header (%s)", contentType))
  111. }
  112. if mimetype != expectedType {
  113. return errdefs.InvalidParameter(errors.Errorf("unsupported Content-Type header (%s): must be '%s'", contentType, expectedType))
  114. }
  115. return nil
  116. }