github.com/ncdc/docker@v0.10.1-0.20160129113957-6c6729ef5b74/api/server/httputils/httputils.go (about)

     1  package httputils
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"strings"
     9  
    10  	"golang.org/x/net/context"
    11  
    12  	"github.com/Sirupsen/logrus"
    13  	"github.com/docker/distribution/registry/api/errcode"
    14  	"github.com/docker/docker/api"
    15  	"github.com/docker/docker/pkg/version"
    16  )
    17  
    18  // APIVersionKey is the client's requested API version.
    19  const APIVersionKey = "api-version"
    20  
    21  // APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
    22  // Any function that has the appropriate signature can be register as a API endpoint (e.g. getVersion).
    23  type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
    24  
    25  // HijackConnection interrupts the http response writer to get the
    26  // underlying connection and operate with it.
    27  func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
    28  	conn, _, err := w.(http.Hijacker).Hijack()
    29  	if err != nil {
    30  		return nil, nil, err
    31  	}
    32  	// Flush the options to make sure the client sets the raw mode
    33  	conn.Write([]byte{})
    34  	return conn, conn, nil
    35  }
    36  
    37  // CloseStreams ensures that a list for http streams are properly closed.
    38  func CloseStreams(streams ...interface{}) {
    39  	for _, stream := range streams {
    40  		if tcpc, ok := stream.(interface {
    41  			CloseWrite() error
    42  		}); ok {
    43  			tcpc.CloseWrite()
    44  		} else if closer, ok := stream.(io.Closer); ok {
    45  			closer.Close()
    46  		}
    47  	}
    48  }
    49  
    50  // CheckForJSON makes sure that the request's Content-Type is application/json.
    51  func CheckForJSON(r *http.Request) error {
    52  	ct := r.Header.Get("Content-Type")
    53  
    54  	// No Content-Type header is ok as long as there's no Body
    55  	if ct == "" {
    56  		if r.Body == nil || r.ContentLength == 0 {
    57  			return nil
    58  		}
    59  	}
    60  
    61  	// Otherwise it better be json
    62  	if api.MatchesContentType(ct, "application/json") {
    63  		return nil
    64  	}
    65  	return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct)
    66  }
    67  
    68  // ParseForm ensures the request form is parsed even with invalid content types.
    69  // If we don't do this, POST method without Content-type (even with empty body) will fail.
    70  func ParseForm(r *http.Request) error {
    71  	if r == nil {
    72  		return nil
    73  	}
    74  	if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
    75  		return err
    76  	}
    77  	return nil
    78  }
    79  
    80  // ParseMultipartForm ensure the request form is parsed, even with invalid content types.
    81  func ParseMultipartForm(r *http.Request) error {
    82  	if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
    83  		return err
    84  	}
    85  	return nil
    86  }
    87  
    88  // WriteError decodes a specific docker error and sends it in the response.
    89  func WriteError(w http.ResponseWriter, err error) {
    90  	if err == nil || w == nil {
    91  		logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
    92  		return
    93  	}
    94  
    95  	statusCode := http.StatusInternalServerError
    96  	errMsg := err.Error()
    97  
    98  	// Based on the type of error we get we need to process things
    99  	// slightly differently to extract the error message.
   100  	// In the 'errcode.*' cases there are two different type of
   101  	// error that could be returned. errocode.ErrorCode is the base
   102  	// type of error object - it is just an 'int' that can then be
   103  	// used as the look-up key to find the message. errorcode.Error
   104  	// extends errorcode.Error by adding error-instance specific
   105  	// data, like 'details' or variable strings to be inserted into
   106  	// the message.
   107  	//
   108  	// Ideally, we should just be able to call err.Error() for all
   109  	// cases but the errcode package doesn't support that yet.
   110  	//
   111  	// Additionally, in both errcode cases, there might be an http
   112  	// status code associated with it, and if so use it.
   113  	switch err.(type) {
   114  	case errcode.ErrorCode:
   115  		daError, _ := err.(errcode.ErrorCode)
   116  		statusCode = daError.Descriptor().HTTPStatusCode
   117  		errMsg = daError.Message()
   118  
   119  	case errcode.Error:
   120  		// For reference, if you're looking for a particular error
   121  		// then you can do something like :
   122  		//   import ( derr "github.com/docker/docker/errors" )
   123  		//   if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... }
   124  
   125  		daError, _ := err.(errcode.Error)
   126  		statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode
   127  		errMsg = daError.Message
   128  
   129  	default:
   130  		// This part of will be removed once we've
   131  		// converted everything over to use the errcode package
   132  
   133  		// FIXME: this is brittle and should not be necessary.
   134  		// If we need to differentiate between different possible error types,
   135  		// we should create appropriate error types with clearly defined meaning
   136  		errStr := strings.ToLower(err.Error())
   137  		for keyword, status := range map[string]int{
   138  			"not found":             http.StatusNotFound,
   139  			"no such":               http.StatusNotFound,
   140  			"bad parameter":         http.StatusBadRequest,
   141  			"conflict":              http.StatusConflict,
   142  			"impossible":            http.StatusNotAcceptable,
   143  			"wrong login/password":  http.StatusUnauthorized,
   144  			"hasn't been activated": http.StatusForbidden,
   145  		} {
   146  			if strings.Contains(errStr, keyword) {
   147  				statusCode = status
   148  				break
   149  			}
   150  		}
   151  	}
   152  
   153  	if statusCode == 0 {
   154  		statusCode = http.StatusInternalServerError
   155  	}
   156  
   157  	http.Error(w, errMsg, statusCode)
   158  }
   159  
   160  // WriteJSON writes the value v to the http response stream as json with standard json encoding.
   161  func WriteJSON(w http.ResponseWriter, code int, v interface{}) error {
   162  	w.Header().Set("Content-Type", "application/json")
   163  	w.WriteHeader(code)
   164  	return json.NewEncoder(w).Encode(v)
   165  }
   166  
   167  // VersionFromContext returns an API version from the context using APIVersionKey.
   168  // It panics if the context value does not have version.Version type.
   169  func VersionFromContext(ctx context.Context) (ver version.Version) {
   170  	if ctx == nil {
   171  		return
   172  	}
   173  	val := ctx.Value(APIVersionKey)
   174  	if val == nil {
   175  		return
   176  	}
   177  	return val.(version.Version)
   178  }