httputils.go 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. package httputils
  2. import (
  3. "io"
  4. "mime"
  5. "net/http"
  6. "strings"
  7. "github.com/docker/docker/errdefs"
  8. "github.com/pkg/errors"
  9. "github.com/sirupsen/logrus"
  10. "golang.org/x/net/context"
  11. )
  12. // APIVersionKey is the client's requested API version.
  13. const APIVersionKey = "api-version"
  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 == "" {
  45. if r.Body == nil || r.ContentLength == 0 {
  46. return nil
  47. }
  48. }
  49. // Otherwise it better be json
  50. if matchesContentType(ct, "application/json") {
  51. return nil
  52. }
  53. return errdefs.InvalidParameter(errors.Errorf("Content-Type specified (%s) must be 'application/json'", ct))
  54. }
  55. // ParseForm ensures the request form is parsed even with invalid content types.
  56. // If we don't do this, POST method without Content-type (even with empty body) will fail.
  57. func ParseForm(r *http.Request) error {
  58. if r == nil {
  59. return nil
  60. }
  61. if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
  62. return errdefs.InvalidParameter(err)
  63. }
  64. return nil
  65. }
  66. // VersionFromContext returns an API version from the context using APIVersionKey.
  67. // It panics if the context value does not have version.Version type.
  68. func VersionFromContext(ctx context.Context) string {
  69. if ctx == nil {
  70. return ""
  71. }
  72. if val := ctx.Value(APIVersionKey); val != nil {
  73. return val.(string)
  74. }
  75. return ""
  76. }
  77. // matchesContentType validates the content type against the expected one
  78. func matchesContentType(contentType, expectedType string) bool {
  79. mimetype, _, err := mime.ParseMediaType(contentType)
  80. if err != nil {
  81. logrus.Errorf("Error parsing media type: %s error: %v", contentType, err)
  82. }
  83. return err == nil && mimetype == expectedType
  84. }