github.com/a4a881d4/docker@v1.9.0-rc2/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  	"github.com/docker/docker/utils"
    17  )
    18  
    19  // APIVersionKey is the client's requested API version.
    20  const APIVersionKey = "api-version"
    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 register as a 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 api.MatchesContentType(ct, "application/json") {
    64  		return nil
    65  	}
    66  	return fmt.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 err
    77  	}
    78  	return nil
    79  }
    80  
    81  // ParseMultipartForm ensure the request form is parsed, even with invalid content types.
    82  func ParseMultipartForm(r *http.Request) error {
    83  	if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
    84  		return err
    85  	}
    86  	return nil
    87  }
    88  
    89  // WriteError decodes a specific docker error and sends it in the response.
    90  func WriteError(w http.ResponseWriter, err error) {
    91  	if err == nil || w == nil {
    92  		logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
    93  		return
    94  	}
    95  
    96  	statusCode := http.StatusInternalServerError
    97  	errMsg := err.Error()
    98  
    99  	// Based on the type of error we get we need to process things
   100  	// slightly differently to extract the error message.
   101  	// In the 'errcode.*' cases there are two different type of
   102  	// error that could be returned. errocode.ErrorCode is the base
   103  	// type of error object - it is just an 'int' that can then be
   104  	// used as the look-up key to find the message. errorcode.Error
   105  	// extends errorcode.Error by adding error-instance specific
   106  	// data, like 'details' or variable strings to be inserted into
   107  	// the message.
   108  	//
   109  	// Ideally, we should just be able to call err.Error() for all
   110  	// cases but the errcode package doesn't support that yet.
   111  	//
   112  	// Additionally, in both errcode cases, there might be an http
   113  	// status code associated with it, and if so use it.
   114  	switch err.(type) {
   115  	case errcode.ErrorCode:
   116  		daError, _ := err.(errcode.ErrorCode)
   117  		statusCode = daError.Descriptor().HTTPStatusCode
   118  		errMsg = daError.Message()
   119  
   120  	case errcode.Error:
   121  		// For reference, if you're looking for a particular error
   122  		// then you can do something like :
   123  		//   import ( derr "github.com/docker/docker/errors" )
   124  		//   if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... }
   125  
   126  		daError, _ := err.(errcode.Error)
   127  		statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode
   128  		errMsg = daError.Message
   129  
   130  	default:
   131  		// This part of will be removed once we've
   132  		// converted everything over to use the errcode package
   133  
   134  		// FIXME: this is brittle and should not be necessary.
   135  		// If we need to differentiate between different possible error types,
   136  		// we should create appropriate error types with clearly defined meaning
   137  		errStr := strings.ToLower(err.Error())
   138  		for keyword, status := range map[string]int{
   139  			"not found":             http.StatusNotFound,
   140  			"no such":               http.StatusNotFound,
   141  			"bad parameter":         http.StatusBadRequest,
   142  			"conflict":              http.StatusConflict,
   143  			"impossible":            http.StatusNotAcceptable,
   144  			"wrong login/password":  http.StatusUnauthorized,
   145  			"hasn't been activated": http.StatusForbidden,
   146  		} {
   147  			if strings.Contains(errStr, keyword) {
   148  				statusCode = status
   149  				break
   150  			}
   151  		}
   152  	}
   153  
   154  	if statusCode == 0 {
   155  		statusCode = http.StatusInternalServerError
   156  	}
   157  
   158  	logrus.WithFields(logrus.Fields{"statusCode": statusCode, "err": utils.GetErrorMessage(err)}).Error("HTTP Error")
   159  	http.Error(w, errMsg, statusCode)
   160  }
   161  
   162  // WriteJSON writes the value v to the http response stream as json with standard json encoding.
   163  func WriteJSON(w http.ResponseWriter, code int, v interface{}) error {
   164  	w.Header().Set("Content-Type", "application/json")
   165  	w.WriteHeader(code)
   166  	return json.NewEncoder(w).Encode(v)
   167  }
   168  
   169  // VersionFromContext returns an API version from the context using APIVersionKey.
   170  // It panics if the context value does not have version.Version type.
   171  func VersionFromContext(ctx context.Context) (ver version.Version) {
   172  	if ctx == nil {
   173  		return
   174  	}
   175  	val := ctx.Value(APIVersionKey)
   176  	if val == nil {
   177  		return
   178  	}
   179  	return val.(version.Version)
   180  }