github.com/rhatdan/docker@v0.7.7-0.20180119204836-47a0dcbcd20a/api/server/httputils/httputils.go (about)

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