httputils.go 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  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/errdefs"
  9. "github.com/pkg/errors"
  10. )
  11. // APIVersionKey is the client's requested API version.
  12. type APIVersionKey struct{}
  13. // APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
  14. // Any function that has the appropriate signature can be registered as an API endpoint (e.g. getVersion).
  15. type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
  16. // HijackConnection interrupts the http response writer to get the
  17. // underlying connection and operate with it.
  18. func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
  19. conn, _, err := w.(http.Hijacker).Hijack()
  20. if err != nil {
  21. return nil, nil, err
  22. }
  23. // Flush the options to make sure the client sets the raw mode
  24. _, _ = conn.Write([]byte{})
  25. return conn, conn, nil
  26. }
  27. // CloseStreams ensures that a list for http streams are properly closed.
  28. func CloseStreams(streams ...interface{}) {
  29. for _, stream := range streams {
  30. if tcpc, ok := stream.(interface {
  31. CloseWrite() error
  32. }); ok {
  33. _ = tcpc.CloseWrite()
  34. } else if closer, ok := stream.(io.Closer); ok {
  35. _ = closer.Close()
  36. }
  37. }
  38. }
  39. // CheckForJSON makes sure that the request's Content-Type is application/json.
  40. func CheckForJSON(r *http.Request) error {
  41. ct := r.Header.Get("Content-Type")
  42. // No Content-Type header is ok as long as there's no Body
  43. if ct == "" && (r.Body == nil || r.ContentLength == 0) {
  44. return nil
  45. }
  46. // Otherwise it better be json
  47. return matchesContentType(ct, "application/json")
  48. }
  49. // ParseForm ensures the request form is parsed even with invalid content types.
  50. // If we don't do this, POST method without Content-type (even with empty body) will fail.
  51. func ParseForm(r *http.Request) error {
  52. if r == nil {
  53. return nil
  54. }
  55. if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
  56. return errdefs.InvalidParameter(err)
  57. }
  58. return nil
  59. }
  60. // VersionFromContext returns an API version from the context using APIVersionKey.
  61. // It panics if the context value does not have version.Version type.
  62. func VersionFromContext(ctx context.Context) string {
  63. if ctx == nil {
  64. return ""
  65. }
  66. if val := ctx.Value(APIVersionKey{}); val != nil {
  67. return val.(string)
  68. }
  69. return ""
  70. }
  71. // matchesContentType validates the content type against the expected one
  72. func matchesContentType(contentType, expectedType string) error {
  73. mimetype, _, err := mime.ParseMediaType(contentType)
  74. if err != nil {
  75. return errdefs.InvalidParameter(errors.Wrapf(err, "malformed Content-Type header (%s)", contentType))
  76. }
  77. if mimetype != expectedType {
  78. return errdefs.InvalidParameter(errors.Errorf("unsupported Content-Type header (%s): must be '%s'", contentType, expectedType))
  79. }
  80. return nil
  81. }