| package httputils // import "github.com/docker/docker/api/server/httputils" |
| |
| import ( |
| "context" |
| "io" |
| "mime" |
| "net/http" |
| "strings" |
| |
| "github.com/docker/docker/api/types" |
| "github.com/docker/docker/api/types/versions" |
| "github.com/docker/docker/errdefs" |
| "github.com/gorilla/mux" |
| "github.com/pkg/errors" |
| "github.com/sirupsen/logrus" |
| "google.golang.org/grpc/status" |
| ) |
| |
| // APIVersionKey is the client's requested API version. |
| type APIVersionKey struct{} |
| |
| // APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints. |
| // Any function that has the appropriate signature can be registered as an API endpoint (e.g. getVersion). |
| type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error |
| |
| // HijackConnection interrupts the http response writer to get the |
| // underlying connection and operate with it. |
| func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { |
| conn, _, err := w.(http.Hijacker).Hijack() |
| if err != nil { |
| return nil, nil, err |
| } |
| // Flush the options to make sure the client sets the raw mode |
| conn.Write([]byte{}) |
| return conn, conn, nil |
| } |
| |
| // CloseStreams ensures that a list for http streams are properly closed. |
| func CloseStreams(streams ...interface{}) { |
| for _, stream := range streams { |
| if tcpc, ok := stream.(interface { |
| CloseWrite() error |
| }); ok { |
| tcpc.CloseWrite() |
| } else if closer, ok := stream.(io.Closer); ok { |
| closer.Close() |
| } |
| } |
| } |
| |
| // CheckForJSON makes sure that the request's Content-Type is application/json. |
| func CheckForJSON(r *http.Request) error { |
| ct := r.Header.Get("Content-Type") |
| |
| // No Content-Type header is ok as long as there's no Body |
| if ct == "" { |
| if r.Body == nil || r.ContentLength == 0 { |
| return nil |
| } |
| } |
| |
| // Otherwise it better be json |
| if matchesContentType(ct, "application/json") { |
| return nil |
| } |
| return errdefs.InvalidParameter(errors.Errorf("Content-Type specified (%s) must be 'application/json'", ct)) |
| } |
| |
| // ParseForm ensures the request form is parsed even with invalid content types. |
| // If we don't do this, POST method without Content-type (even with empty body) will fail. |
| func ParseForm(r *http.Request) error { |
| if r == nil { |
| return nil |
| } |
| if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") { |
| return errdefs.InvalidParameter(err) |
| } |
| return nil |
| } |
| |
| // VersionFromContext returns an API version from the context using APIVersionKey. |
| // It panics if the context value does not have version.Version type. |
| func VersionFromContext(ctx context.Context) string { |
| if ctx == nil { |
| return "" |
| } |
| |
| if val := ctx.Value(APIVersionKey{}); val != nil { |
| return val.(string) |
| } |
| |
| return "" |
| } |
| |
| // MakeErrorHandler makes an HTTP handler that decodes a Docker error and |
| // returns it in the response. |
| func MakeErrorHandler(err error) http.HandlerFunc { |
| return func(w http.ResponseWriter, r *http.Request) { |
| statusCode := errdefs.GetHTTPErrorStatusCode(err) |
| vars := mux.Vars(r) |
| if apiVersionSupportsJSONErrors(vars["version"]) { |
| response := &types.ErrorResponse{ |
| Message: err.Error(), |
| } |
| WriteJSON(w, statusCode, response) |
| } else { |
| http.Error(w, status.Convert(err).Message(), statusCode) |
| } |
| } |
| } |
| |
| func apiVersionSupportsJSONErrors(version string) bool { |
| const firstAPIVersionWithJSONErrors = "1.23" |
| return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors) |
| } |
| |
| // matchesContentType validates the content type against the expected one |
| func matchesContentType(contentType, expectedType string) bool { |
| mimetype, _, err := mime.ParseMediaType(contentType) |
| if err != nil { |
| logrus.Errorf("Error parsing media type: %s error: %v", contentType, err) |
| } |
| return err == nil && mimetype == expectedType |
| } |