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 }