httputils.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. package httputils
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "strings"
  8. "golang.org/x/net/context"
  9. "github.com/Sirupsen/logrus"
  10. "github.com/docker/distribution/registry/api/errcode"
  11. "github.com/docker/docker/api"
  12. "github.com/docker/docker/pkg/version"
  13. )
  14. // APIVersionKey is the client's requested API version.
  15. const APIVersionKey = "api-version"
  16. // APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
  17. // Any function that has the appropriate signature can be register as a API endpoint (e.g. getVersion).
  18. type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
  19. // HijackConnection interrupts the http response writer to get the
  20. // underlying connection and operate with it.
  21. func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
  22. conn, _, err := w.(http.Hijacker).Hijack()
  23. if err != nil {
  24. return nil, nil, err
  25. }
  26. // Flush the options to make sure the client sets the raw mode
  27. conn.Write([]byte{})
  28. return conn, conn, nil
  29. }
  30. // CloseStreams ensures that a list for http streams are properly closed.
  31. func CloseStreams(streams ...interface{}) {
  32. for _, stream := range streams {
  33. if tcpc, ok := stream.(interface {
  34. CloseWrite() error
  35. }); ok {
  36. tcpc.CloseWrite()
  37. } else if closer, ok := stream.(io.Closer); ok {
  38. closer.Close()
  39. }
  40. }
  41. }
  42. // CheckForJSON makes sure that the request's Content-Type is application/json.
  43. func CheckForJSON(r *http.Request) error {
  44. ct := r.Header.Get("Content-Type")
  45. // No Content-Type header is ok as long as there's no Body
  46. if ct == "" {
  47. if r.Body == nil || r.ContentLength == 0 {
  48. return nil
  49. }
  50. }
  51. // Otherwise it better be json
  52. if api.MatchesContentType(ct, "application/json") {
  53. return nil
  54. }
  55. return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct)
  56. }
  57. // ParseForm ensures the request form is parsed even with invalid content types.
  58. // If we don't do this, POST method without Content-type (even with empty body) will fail.
  59. func ParseForm(r *http.Request) error {
  60. if r == nil {
  61. return nil
  62. }
  63. if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
  64. return err
  65. }
  66. return nil
  67. }
  68. // ParseMultipartForm ensure the request form is parsed, even with invalid content types.
  69. func ParseMultipartForm(r *http.Request) error {
  70. if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
  71. return err
  72. }
  73. return nil
  74. }
  75. // WriteError decodes a specific docker error and sends it in the response.
  76. func WriteError(w http.ResponseWriter, err error) {
  77. if err == nil || w == nil {
  78. logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
  79. return
  80. }
  81. statusCode := http.StatusInternalServerError
  82. errMsg := err.Error()
  83. // Based on the type of error we get we need to process things
  84. // slightly differently to extract the error message.
  85. // In the 'errcode.*' cases there are two different type of
  86. // error that could be returned. errocode.ErrorCode is the base
  87. // type of error object - it is just an 'int' that can then be
  88. // used as the look-up key to find the message. errorcode.Error
  89. // extends errorcode.Error by adding error-instance specific
  90. // data, like 'details' or variable strings to be inserted into
  91. // the message.
  92. //
  93. // Ideally, we should just be able to call err.Error() for all
  94. // cases but the errcode package doesn't support that yet.
  95. //
  96. // Additionally, in both errcode cases, there might be an http
  97. // status code associated with it, and if so use it.
  98. switch err.(type) {
  99. case errcode.ErrorCode:
  100. daError, _ := err.(errcode.ErrorCode)
  101. statusCode = daError.Descriptor().HTTPStatusCode
  102. errMsg = daError.Message()
  103. case errcode.Error:
  104. // For reference, if you're looking for a particular error
  105. // then you can do something like :
  106. // import ( derr "github.com/docker/docker/errors" )
  107. // if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... }
  108. daError, _ := err.(errcode.Error)
  109. statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode
  110. errMsg = daError.Message
  111. default:
  112. // This part of will be removed once we've
  113. // converted everything over to use the errcode package
  114. // FIXME: this is brittle and should not be necessary.
  115. // If we need to differentiate between different possible error types,
  116. // we should create appropriate error types with clearly defined meaning
  117. errStr := strings.ToLower(err.Error())
  118. for keyword, status := range map[string]int{
  119. "not found": http.StatusNotFound,
  120. "no such": http.StatusNotFound,
  121. "bad parameter": http.StatusBadRequest,
  122. "conflict": http.StatusConflict,
  123. "impossible": http.StatusNotAcceptable,
  124. "wrong login/password": http.StatusUnauthorized,
  125. "hasn't been activated": http.StatusForbidden,
  126. } {
  127. if strings.Contains(errStr, keyword) {
  128. statusCode = status
  129. break
  130. }
  131. }
  132. }
  133. if statusCode == 0 {
  134. statusCode = http.StatusInternalServerError
  135. }
  136. http.Error(w, errMsg, statusCode)
  137. }
  138. // WriteJSON writes the value v to the http response stream as json with standard json encoding.
  139. func WriteJSON(w http.ResponseWriter, code int, v interface{}) error {
  140. w.Header().Set("Content-Type", "application/json")
  141. w.WriteHeader(code)
  142. return json.NewEncoder(w).Encode(v)
  143. }
  144. // VersionFromContext returns an API version from the context using APIVersionKey.
  145. // It panics if the context value does not have version.Version type.
  146. func VersionFromContext(ctx context.Context) (ver version.Version) {
  147. if ctx == nil {
  148. return
  149. }
  150. val := ctx.Value(APIVersionKey)
  151. if val == nil {
  152. return
  153. }
  154. return val.(version.Version)
  155. }