| package httputils |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "io" |
| "net/http" |
| "strings" |
| |
| "golang.org/x/net/context" |
| |
| "github.com/Sirupsen/logrus" |
| "github.com/docker/distribution/registry/api/errcode" |
| "github.com/docker/docker/api" |
| "github.com/docker/docker/pkg/version" |
| ) |
| |
| // APIVersionKey is the client's requested API version. |
| const APIVersionKey = "api-version" |
| |
| // APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints. |
| // Any function that has the appropriate signature can be register as a 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 api.MatchesContentType(ct, "application/json") { |
| return nil |
| } |
| return fmt.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 err |
| } |
| return nil |
| } |
| |
| // ParseMultipartForm ensure the request form is parsed, even with invalid content types. |
| func ParseMultipartForm(r *http.Request) error { |
| if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") { |
| return err |
| } |
| return nil |
| } |
| |
| // WriteError decodes a specific docker error and sends it in the response. |
| func WriteError(w http.ResponseWriter, err error) { |
| if err == nil || w == nil { |
| logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling") |
| return |
| } |
| |
| statusCode := http.StatusInternalServerError |
| errMsg := err.Error() |
| |
| // Based on the type of error we get we need to process things |
| // slightly differently to extract the error message. |
| // In the 'errcode.*' cases there are two different type of |
| // error that could be returned. errocode.ErrorCode is the base |
| // type of error object - it is just an 'int' that can then be |
| // used as the look-up key to find the message. errorcode.Error |
| // extends errorcode.Error by adding error-instance specific |
| // data, like 'details' or variable strings to be inserted into |
| // the message. |
| // |
| // Ideally, we should just be able to call err.Error() for all |
| // cases but the errcode package doesn't support that yet. |
| // |
| // Additionally, in both errcode cases, there might be an http |
| // status code associated with it, and if so use it. |
| switch err.(type) { |
| case errcode.ErrorCode: |
| daError, _ := err.(errcode.ErrorCode) |
| statusCode = daError.Descriptor().HTTPStatusCode |
| errMsg = daError.Message() |
| |
| case errcode.Error: |
| // For reference, if you're looking for a particular error |
| // then you can do something like : |
| // import ( derr "github.com/docker/docker/errors" ) |
| // if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... } |
| |
| daError, _ := err.(errcode.Error) |
| statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode |
| errMsg = daError.Message |
| |
| default: |
| // This part of will be removed once we've |
| // converted everything over to use the errcode package |
| |
| // FIXME: this is brittle and should not be necessary. |
| // If we need to differentiate between different possible error types, |
| // we should create appropriate error types with clearly defined meaning |
| errStr := strings.ToLower(err.Error()) |
| for keyword, status := range map[string]int{ |
| "not found": http.StatusNotFound, |
| "no such": http.StatusNotFound, |
| "bad parameter": http.StatusBadRequest, |
| "conflict": http.StatusConflict, |
| "impossible": http.StatusNotAcceptable, |
| "wrong login/password": http.StatusUnauthorized, |
| "hasn't been activated": http.StatusForbidden, |
| } { |
| if strings.Contains(errStr, keyword) { |
| statusCode = status |
| break |
| } |
| } |
| } |
| |
| if statusCode == 0 { |
| statusCode = http.StatusInternalServerError |
| } |
| |
| http.Error(w, errMsg, statusCode) |
| } |
| |
| // WriteJSON writes the value v to the http response stream as json with standard json encoding. |
| func WriteJSON(w http.ResponseWriter, code int, v interface{}) error { |
| w.Header().Set("Content-Type", "application/json") |
| w.WriteHeader(code) |
| return json.NewEncoder(w).Encode(v) |
| } |
| |
| // 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) (ver version.Version) { |
| if ctx == nil { |
| return |
| } |
| val := ctx.Value(APIVersionKey) |
| if val == nil { |
| return |
| } |
| return val.(version.Version) |
| } |