httputils.go 3.0 KB

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