github.com/rawahars/moby@v24.0.4+incompatible/api/server/httputils/httputils.go (about) 1 package httputils // import "github.com/docker/docker/api/server/httputils" 2 3 import ( 4 "context" 5 "encoding/json" 6 "io" 7 "mime" 8 "net/http" 9 "strings" 10 11 "github.com/docker/docker/errdefs" 12 "github.com/pkg/errors" 13 ) 14 15 // APIVersionKey is the client's requested API version. 16 type APIVersionKey struct{} 17 18 // APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints. 19 // Any function that has the appropriate signature can be registered as an API endpoint (e.g. getVersion). 20 type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error 21 22 // HijackConnection interrupts the http response writer to get the 23 // underlying connection and operate with it. 24 func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { 25 conn, _, err := w.(http.Hijacker).Hijack() 26 if err != nil { 27 return nil, nil, err 28 } 29 // Flush the options to make sure the client sets the raw mode 30 _, _ = conn.Write([]byte{}) 31 return conn, conn, nil 32 } 33 34 // CloseStreams ensures that a list for http streams are properly closed. 35 func CloseStreams(streams ...interface{}) { 36 for _, stream := range streams { 37 if tcpc, ok := stream.(interface { 38 CloseWrite() error 39 }); ok { 40 _ = tcpc.CloseWrite() 41 } else if closer, ok := stream.(io.Closer); ok { 42 _ = closer.Close() 43 } 44 } 45 } 46 47 // CheckForJSON makes sure that the request's Content-Type is application/json. 48 func CheckForJSON(r *http.Request) error { 49 ct := r.Header.Get("Content-Type") 50 51 // No Content-Type header is ok as long as there's no Body 52 if ct == "" && (r.Body == nil || r.ContentLength == 0) { 53 return nil 54 } 55 56 // Otherwise it better be json 57 return matchesContentType(ct, "application/json") 58 } 59 60 // ReadJSON validates the request to have the correct content-type, and decodes 61 // the request's Body into out. 62 func ReadJSON(r *http.Request, out interface{}) error { 63 err := CheckForJSON(r) 64 if err != nil { 65 return err 66 } 67 if r.Body == nil || r.ContentLength == 0 { 68 // an empty body is not invalid, so don't return an error; see 69 // https://lists.w3.org/Archives/Public/ietf-http-wg/2010JulSep/0272.html 70 return nil 71 } 72 73 dec := json.NewDecoder(r.Body) 74 err = dec.Decode(out) 75 defer r.Body.Close() 76 if err != nil { 77 if err == io.EOF { 78 return errdefs.InvalidParameter(errors.New("invalid JSON: got EOF while reading request body")) 79 } 80 return errdefs.InvalidParameter(errors.Wrap(err, "invalid JSON")) 81 } 82 83 if dec.More() { 84 return errdefs.InvalidParameter(errors.New("unexpected content after JSON")) 85 } 86 return nil 87 } 88 89 // WriteJSON writes the value v to the http response stream as json with standard json encoding. 90 func WriteJSON(w http.ResponseWriter, code int, v interface{}) error { 91 w.Header().Set("Content-Type", "application/json") 92 w.WriteHeader(code) 93 enc := json.NewEncoder(w) 94 enc.SetEscapeHTML(false) 95 return enc.Encode(v) 96 } 97 98 // ParseForm ensures the request form is parsed even with invalid content types. 99 // If we don't do this, POST method without Content-type (even with empty body) will fail. 100 func ParseForm(r *http.Request) error { 101 if r == nil { 102 return nil 103 } 104 if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") { 105 return errdefs.InvalidParameter(err) 106 } 107 return nil 108 } 109 110 // VersionFromContext returns an API version from the context using APIVersionKey. 111 // It panics if the context value does not have version.Version type. 112 func VersionFromContext(ctx context.Context) string { 113 if ctx == nil { 114 return "" 115 } 116 117 if val := ctx.Value(APIVersionKey{}); val != nil { 118 return val.(string) 119 } 120 121 return "" 122 } 123 124 // matchesContentType validates the content type against the expected one 125 func matchesContentType(contentType, expectedType string) error { 126 mimetype, _, err := mime.ParseMediaType(contentType) 127 if err != nil { 128 return errdefs.InvalidParameter(errors.Wrapf(err, "malformed Content-Type header (%s)", contentType)) 129 } 130 if mimetype != expectedType { 131 return errdefs.InvalidParameter(errors.Errorf("unsupported Content-Type header (%s): must be '%s'", contentType, expectedType)) 132 } 133 return nil 134 }