github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/api/server/httputils/httputils.go (about)

     1  package httputils // import "github.com/docker/docker/api/server/httputils"
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"mime"
     7  	"net/http"
     8  	"strings"
     9  
    10  	"github.com/docker/docker/api/types"
    11  	"github.com/docker/docker/api/types/versions"
    12  	"github.com/docker/docker/errdefs"
    13  	"github.com/gorilla/mux"
    14  	"github.com/pkg/errors"
    15  	"github.com/sirupsen/logrus"
    16  	"google.golang.org/grpc/status"
    17  )
    18  
    19  // APIVersionKey is the client's requested API version.
    20  type APIVersionKey struct{}
    21  
    22  // APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
    23  // Any function that has the appropriate signature can be registered as an API endpoint (e.g. getVersion).
    24  type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
    25  
    26  // HijackConnection interrupts the http response writer to get the
    27  // underlying connection and operate with it.
    28  func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
    29  	conn, _, err := w.(http.Hijacker).Hijack()
    30  	if err != nil {
    31  		return nil, nil, err
    32  	}
    33  	// Flush the options to make sure the client sets the raw mode
    34  	_, _ = conn.Write([]byte{})
    35  	return conn, conn, nil
    36  }
    37  
    38  // CloseStreams ensures that a list for http streams are properly closed.
    39  func CloseStreams(streams ...interface{}) {
    40  	for _, stream := range streams {
    41  		if tcpc, ok := stream.(interface {
    42  			CloseWrite() error
    43  		}); ok {
    44  			_ = tcpc.CloseWrite()
    45  		} else if closer, ok := stream.(io.Closer); ok {
    46  			_ = closer.Close()
    47  		}
    48  	}
    49  }
    50  
    51  // CheckForJSON makes sure that the request's Content-Type is application/json.
    52  func CheckForJSON(r *http.Request) error {
    53  	ct := r.Header.Get("Content-Type")
    54  
    55  	// No Content-Type header is ok as long as there's no Body
    56  	if ct == "" {
    57  		if r.Body == nil || r.ContentLength == 0 {
    58  			return nil
    59  		}
    60  	}
    61  
    62  	// Otherwise it better be json
    63  	if matchesContentType(ct, "application/json") {
    64  		return nil
    65  	}
    66  	return errdefs.InvalidParameter(errors.Errorf("Content-Type specified (%s) must be 'application/json'", ct))
    67  }
    68  
    69  // ParseForm ensures the request form is parsed even with invalid content types.
    70  // If we don't do this, POST method without Content-type (even with empty body) will fail.
    71  func ParseForm(r *http.Request) error {
    72  	if r == nil {
    73  		return nil
    74  	}
    75  	if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
    76  		return errdefs.InvalidParameter(err)
    77  	}
    78  	return nil
    79  }
    80  
    81  // VersionFromContext returns an API version from the context using APIVersionKey.
    82  // It panics if the context value does not have version.Version type.
    83  func VersionFromContext(ctx context.Context) string {
    84  	if ctx == nil {
    85  		return ""
    86  	}
    87  
    88  	if val := ctx.Value(APIVersionKey{}); val != nil {
    89  		return val.(string)
    90  	}
    91  
    92  	return ""
    93  }
    94  
    95  // MakeErrorHandler makes an HTTP handler that decodes a Docker error and
    96  // returns it in the response.
    97  func MakeErrorHandler(err error) http.HandlerFunc {
    98  	return func(w http.ResponseWriter, r *http.Request) {
    99  		statusCode := errdefs.GetHTTPErrorStatusCode(err)
   100  		vars := mux.Vars(r)
   101  		if apiVersionSupportsJSONErrors(vars["version"]) {
   102  			response := &types.ErrorResponse{
   103  				Message: err.Error(),
   104  			}
   105  			_ = WriteJSON(w, statusCode, response)
   106  		} else {
   107  			http.Error(w, status.Convert(err).Message(), statusCode)
   108  		}
   109  	}
   110  }
   111  
   112  func apiVersionSupportsJSONErrors(version string) bool {
   113  	const firstAPIVersionWithJSONErrors = "1.23"
   114  	return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors)
   115  }
   116  
   117  // matchesContentType validates the content type against the expected one
   118  func matchesContentType(contentType, expectedType string) bool {
   119  	mimetype, _, err := mime.ParseMediaType(contentType)
   120  	if err != nil {
   121  		logrus.Errorf("Error parsing media type: %s error: %v", contentType, err)
   122  	}
   123  	return err == nil && mimetype == expectedType
   124  }