123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- package httputils // import "github.com/docker/docker/api/server/httputils"
- import (
- "context"
- "encoding/json"
- "io"
- "mime"
- "net/http"
- "strings"
- "github.com/docker/docker/errdefs"
- "github.com/pkg/errors"
- )
- // 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 == "" && (r.Body == nil || r.ContentLength == 0) {
- return nil
- }
- // Otherwise it better be json
- return matchesContentType(ct, "application/json")
- }
- // ReadJSON validates the request to have the correct content-type, and decodes
- // the request's Body into out.
- func ReadJSON(r *http.Request, out interface{}) error {
- err := CheckForJSON(r)
- if err != nil {
- return err
- }
- if r.Body == nil || r.ContentLength == 0 {
- // an empty body is not invalid, so don't return an error; see
- // https://lists.w3.org/Archives/Public/ietf-http-wg/2010JulSep/0272.html
- return nil
- }
- dec := json.NewDecoder(r.Body)
- err = dec.Decode(out)
- defer r.Body.Close()
- if err != nil {
- if err == io.EOF {
- return errdefs.InvalidParameter(errors.New("invalid JSON: got EOF while reading request body"))
- }
- return errdefs.InvalidParameter(errors.Wrap(err, "invalid JSON"))
- }
- if dec.More() {
- return errdefs.InvalidParameter(errors.New("unexpected content after JSON"))
- }
- return nil
- }
- // 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)
- enc := json.NewEncoder(w)
- enc.SetEscapeHTML(false)
- return enc.Encode(v)
- }
- // 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 ""
- }
- // matchesContentType validates the content type against the expected one
- func matchesContentType(contentType, expectedType string) error {
- mimetype, _, err := mime.ParseMediaType(contentType)
- if err != nil {
- return errdefs.InvalidParameter(errors.Wrapf(err, "malformed Content-Type header (%s)", contentType))
- }
- if mimetype != expectedType {
- return errdefs.InvalidParameter(errors.Errorf("unsupported Content-Type header (%s): must be '%s'", contentType, expectedType))
- }
- return nil
- }
|