mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
ebcb7d6b40
Use strongly typed errors to set HTTP status codes. Error interfaces are defined in the api/errors package and errors returned from controllers are checked against these interfaces. Errors can be wraeped in a pkg/errors.Causer, as long as somewhere in the line of causes one of the interfaces is implemented. The special error interfaces take precedence over Causer, meaning if both Causer and one of the new error interfaces are implemented, the Causer is not traversed. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
111 lines
3 KiB
Go
111 lines
3 KiB
Go
package httputils
|
|
|
|
import (
|
|
"io"
|
|
"mime"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
// 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 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()
|
|
}
|
|
}
|
|
}
|
|
|
|
type validationError struct {
|
|
cause error
|
|
}
|
|
|
|
func (e validationError) Error() string {
|
|
return e.cause.Error()
|
|
}
|
|
|
|
func (e validationError) Cause() error {
|
|
return e.cause
|
|
}
|
|
|
|
func (e validationError) InvalidParameter() {}
|
|
|
|
// 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 validationError{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 validationError{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 ""
|
|
}
|
|
|
|
// 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
|
|
}
|