github.com/mheon/docker@v0.11.2-0.20150922122814-44f47903a831/api/server/server.go (about) 1 package server 2 3 import ( 4 "crypto/tls" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net" 9 "net/http" 10 "os" 11 "runtime" 12 "strings" 13 14 "github.com/gorilla/mux" 15 16 "github.com/Sirupsen/logrus" 17 "github.com/docker/distribution/registry/api/errcode" 18 "github.com/docker/docker/api" 19 "github.com/docker/docker/autogen/dockerversion" 20 "github.com/docker/docker/context" 21 "github.com/docker/docker/daemon" 22 "github.com/docker/docker/pkg/sockets" 23 "github.com/docker/docker/pkg/stringid" 24 "github.com/docker/docker/pkg/version" 25 ) 26 27 // Config provides the configuration for the API server 28 type Config struct { 29 Logging bool 30 EnableCors bool 31 CorsHeaders string 32 Version string 33 SocketGroup string 34 TLSConfig *tls.Config 35 } 36 37 // Server contains instance details for the server 38 type Server struct { 39 daemon *daemon.Daemon 40 cfg *Config 41 router *mux.Router 42 start chan struct{} 43 servers []serverCloser 44 } 45 46 // New returns a new instance of the server based on the specified configuration. 47 func New(cfg *Config) *Server { 48 srv := &Server{ 49 cfg: cfg, 50 start: make(chan struct{}), 51 } 52 r := createRouter(srv) 53 srv.router = r 54 return srv 55 } 56 57 // Close closes servers and thus stop receiving requests 58 func (s *Server) Close() { 59 for _, srv := range s.servers { 60 if err := srv.Close(); err != nil { 61 logrus.Error(err) 62 } 63 } 64 } 65 66 type serverCloser interface { 67 Serve() error 68 Close() error 69 } 70 71 // ServeAPI loops through all of the protocols sent in to docker and spawns 72 // off a go routine to setup a serving http.Server for each. 73 func (s *Server) ServeAPI(protoAddrs []string) error { 74 var chErrors = make(chan error, len(protoAddrs)) 75 76 for _, protoAddr := range protoAddrs { 77 protoAddrParts := strings.SplitN(protoAddr, "://", 2) 78 if len(protoAddrParts) != 2 { 79 return fmt.Errorf("bad format, expected PROTO://ADDR") 80 } 81 srv, err := s.newServer(protoAddrParts[0], protoAddrParts[1]) 82 if err != nil { 83 return err 84 } 85 s.servers = append(s.servers, srv...) 86 87 for _, s := range srv { 88 logrus.Infof("Listening for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1]) 89 go func(s serverCloser) { 90 if err := s.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") { 91 err = nil 92 } 93 chErrors <- err 94 }(s) 95 } 96 } 97 98 for i := 0; i < len(protoAddrs); i++ { 99 err := <-chErrors 100 if err != nil { 101 return err 102 } 103 } 104 105 return nil 106 } 107 108 // HTTPServer contains an instance of http server and the listener. 109 // srv *http.Server, contains configuration to create a http server and a mux router with all api end points. 110 // l net.Listener, is a TCP or Socket listener that dispatches incoming request to the router. 111 type HTTPServer struct { 112 srv *http.Server 113 l net.Listener 114 } 115 116 // Serve starts listening for inbound requests. 117 func (s *HTTPServer) Serve() error { 118 return s.srv.Serve(s.l) 119 } 120 121 // Close closes the HTTPServer from listening for the inbound requests. 122 func (s *HTTPServer) Close() error { 123 return s.l.Close() 124 } 125 126 // HTTPAPIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints. 127 // Any function that has the appropriate signature can be register as a API endpoint (e.g. getVersion). 128 type HTTPAPIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error 129 130 func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { 131 conn, _, err := w.(http.Hijacker).Hijack() 132 if err != nil { 133 return nil, nil, err 134 } 135 // Flush the options to make sure the client sets the raw mode 136 conn.Write([]byte{}) 137 return conn, conn, nil 138 } 139 140 func closeStreams(streams ...interface{}) { 141 for _, stream := range streams { 142 if tcpc, ok := stream.(interface { 143 CloseWrite() error 144 }); ok { 145 tcpc.CloseWrite() 146 } else if closer, ok := stream.(io.Closer); ok { 147 closer.Close() 148 } 149 } 150 } 151 152 // checkForJSON makes sure that the request's Content-Type is application/json. 153 func checkForJSON(r *http.Request) error { 154 ct := r.Header.Get("Content-Type") 155 156 // No Content-Type header is ok as long as there's no Body 157 if ct == "" { 158 if r.Body == nil || r.ContentLength == 0 { 159 return nil 160 } 161 } 162 163 // Otherwise it better be json 164 if api.MatchesContentType(ct, "application/json") { 165 return nil 166 } 167 return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct) 168 } 169 170 //If we don't do this, POST method without Content-type (even with empty body) will fail 171 func parseForm(r *http.Request) error { 172 if r == nil { 173 return nil 174 } 175 if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") { 176 return err 177 } 178 return nil 179 } 180 181 func parseMultipartForm(r *http.Request) error { 182 if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") { 183 return err 184 } 185 return nil 186 } 187 188 func httpError(w http.ResponseWriter, err error) { 189 if err == nil || w == nil { 190 logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling") 191 return 192 } 193 194 statusCode := http.StatusInternalServerError 195 errMsg := err.Error() 196 197 // Based on the type of error we get we need to process things 198 // slightly differently to extract the error message. 199 // In the 'errcode.*' cases there are two different type of 200 // error that could be returned. errocode.ErrorCode is the base 201 // type of error object - it is just an 'int' that can then be 202 // used as the look-up key to find the message. errorcode.Error 203 // extends errorcode.Error by adding error-instance specific 204 // data, like 'details' or variable strings to be inserted into 205 // the message. 206 // 207 // Ideally, we should just be able to call err.Error() for all 208 // cases but the errcode package doesn't support that yet. 209 // 210 // Additionally, in both errcode cases, there might be an http 211 // status code associated with it, and if so use it. 212 switch err.(type) { 213 case errcode.ErrorCode: 214 daError, _ := err.(errcode.ErrorCode) 215 statusCode = daError.Descriptor().HTTPStatusCode 216 errMsg = daError.Message() 217 218 case errcode.Error: 219 // For reference, if you're looking for a particular error 220 // then you can do something like : 221 // import ( derr "github.com/docker/docker/errors" ) 222 // if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... } 223 224 daError, _ := err.(errcode.Error) 225 statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode 226 errMsg = daError.Message 227 228 default: 229 // This part of will be removed once we've 230 // converted everything over to use the errcode package 231 232 // FIXME: this is brittle and should not be necessary. 233 // If we need to differentiate between different possible error types, 234 // we should create appropriate error types with clearly defined meaning 235 errStr := strings.ToLower(err.Error()) 236 for keyword, status := range map[string]int{ 237 "not found": http.StatusNotFound, 238 "no such": http.StatusNotFound, 239 "bad parameter": http.StatusBadRequest, 240 "conflict": http.StatusConflict, 241 "impossible": http.StatusNotAcceptable, 242 "wrong login/password": http.StatusUnauthorized, 243 "hasn't been activated": http.StatusForbidden, 244 } { 245 if strings.Contains(errStr, keyword) { 246 statusCode = status 247 break 248 } 249 } 250 } 251 252 if statusCode == 0 { 253 statusCode = http.StatusInternalServerError 254 } 255 256 logrus.WithFields(logrus.Fields{"statusCode": statusCode, "err": err}).Error("HTTP Error") 257 http.Error(w, errMsg, statusCode) 258 } 259 260 // writeJSON writes the value v to the http response stream as json with standard 261 // json encoding. 262 func writeJSON(w http.ResponseWriter, code int, v interface{}) error { 263 w.Header().Set("Content-Type", "application/json") 264 w.WriteHeader(code) 265 return json.NewEncoder(w).Encode(v) 266 } 267 268 func (s *Server) optionsHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 269 w.WriteHeader(http.StatusOK) 270 return nil 271 } 272 func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string) { 273 logrus.Debugf("CORS header is enabled and set to: %s", corsHeaders) 274 w.Header().Add("Access-Control-Allow-Origin", corsHeaders) 275 w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth") 276 w.Header().Add("Access-Control-Allow-Methods", "HEAD, GET, POST, DELETE, PUT, OPTIONS") 277 } 278 279 func (s *Server) ping(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 280 _, err := w.Write([]byte{'O', 'K'}) 281 return err 282 } 283 284 func (s *Server) initTCPSocket(addr string) (l net.Listener, err error) { 285 if s.cfg.TLSConfig == nil || s.cfg.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert { 286 logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") 287 } 288 if l, err = sockets.NewTCPSocket(addr, s.cfg.TLSConfig, s.start); err != nil { 289 return nil, err 290 } 291 if err := allocateDaemonPort(addr); err != nil { 292 return nil, err 293 } 294 return 295 } 296 297 func makeHTTPHandler(logging bool, localMethod string, localRoute string, handlerFunc HTTPAPIFunc, corsHeaders string, dockerVersion version.Version) http.HandlerFunc { 298 return func(w http.ResponseWriter, r *http.Request) { 299 // Define the context that we'll pass around to share info 300 // like the docker-request-id. 301 // 302 // The 'context' will be used for global data that should 303 // apply to all requests. Data that is specific to the 304 // immediate function being called should still be passed 305 // as 'args' on the function call. 306 307 reqID := stringid.TruncateID(stringid.GenerateNonCryptoID()) 308 apiVersion := version.Version(mux.Vars(r)["version"]) 309 if apiVersion == "" { 310 apiVersion = api.Version 311 } 312 313 ctx := context.Background() 314 ctx = context.WithValue(ctx, context.RequestID, reqID) 315 ctx = context.WithValue(ctx, context.APIVersion, apiVersion) 316 317 // log the request 318 logrus.Debugf("Calling %s %s", localMethod, localRoute) 319 320 if logging { 321 logrus.Infof("%s %s", r.Method, r.RequestURI) 322 } 323 324 if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") { 325 userAgent := strings.Split(r.Header.Get("User-Agent"), "/") 326 327 // v1.20 onwards includes the GOOS of the client after the version 328 // such as Docker/1.7.0 (linux) 329 if len(userAgent) == 2 && strings.Contains(userAgent[1], " ") { 330 userAgent[1] = strings.Split(userAgent[1], " ")[0] 331 } 332 333 if len(userAgent) == 2 && !dockerVersion.Equal(version.Version(userAgent[1])) { 334 logrus.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion) 335 } 336 } 337 if corsHeaders != "" { 338 writeCorsHeaders(w, r, corsHeaders) 339 } 340 341 if apiVersion.GreaterThan(api.Version) { 342 http.Error(w, fmt.Errorf("client is newer than server (client API version: %s, server API version: %s)", apiVersion, api.Version).Error(), http.StatusBadRequest) 343 return 344 } 345 if apiVersion.LessThan(api.MinVersion) { 346 http.Error(w, fmt.Errorf("client is too old, minimum supported API version is %s, please upgrade your client to a newer version", api.MinVersion).Error(), http.StatusBadRequest) 347 return 348 } 349 350 w.Header().Set("Server", "Docker/"+dockerversion.VERSION+" ("+runtime.GOOS+")") 351 352 if err := handlerFunc(ctx, w, r, mux.Vars(r)); err != nil { 353 logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, err) 354 httpError(w, err) 355 } 356 } 357 } 358 359 // we keep enableCors just for legacy usage, need to be removed in the future 360 func createRouter(s *Server) *mux.Router { 361 r := mux.NewRouter() 362 if os.Getenv("DEBUG") != "" { 363 profilerSetup(r, "/debug/") 364 } 365 m := map[string]map[string]HTTPAPIFunc{ 366 "HEAD": { 367 "/containers/{name:.*}/archive": s.headContainersArchive, 368 }, 369 "GET": { 370 "/_ping": s.ping, 371 "/events": s.getEvents, 372 "/info": s.getInfo, 373 "/version": s.getVersion, 374 "/images/json": s.getImagesJSON, 375 "/images/search": s.getImagesSearch, 376 "/images/get": s.getImagesGet, 377 "/images/{name:.*}/get": s.getImagesGet, 378 "/images/{name:.*}/history": s.getImagesHistory, 379 "/images/{name:.*}/json": s.getImagesByName, 380 "/containers/json": s.getContainersJSON, 381 "/containers/{name:.*}/export": s.getContainersExport, 382 "/containers/{name:.*}/changes": s.getContainersChanges, 383 "/containers/{name:.*}/json": s.getContainersByName, 384 "/containers/{name:.*}/top": s.getContainersTop, 385 "/containers/{name:.*}/logs": s.getContainersLogs, 386 "/containers/{name:.*}/stats": s.getContainersStats, 387 "/containers/{name:.*}/attach/ws": s.wsContainersAttach, 388 "/exec/{id:.*}/json": s.getExecByID, 389 "/containers/{name:.*}/archive": s.getContainersArchive, 390 "/volumes": s.getVolumesList, 391 "/volumes/{name:.*}": s.getVolumeByName, 392 }, 393 "POST": { 394 "/auth": s.postAuth, 395 "/commit": s.postCommit, 396 "/build": s.postBuild, 397 "/images/create": s.postImagesCreate, 398 "/images/load": s.postImagesLoad, 399 "/images/{name:.*}/push": s.postImagesPush, 400 "/images/{name:.*}/tag": s.postImagesTag, 401 "/containers/create": s.postContainersCreate, 402 "/containers/{name:.*}/kill": s.postContainersKill, 403 "/containers/{name:.*}/pause": s.postContainersPause, 404 "/containers/{name:.*}/unpause": s.postContainersUnpause, 405 "/containers/{name:.*}/restart": s.postContainersRestart, 406 "/containers/{name:.*}/start": s.postContainersStart, 407 "/containers/{name:.*}/stop": s.postContainersStop, 408 "/containers/{name:.*}/wait": s.postContainersWait, 409 "/containers/{name:.*}/resize": s.postContainersResize, 410 "/containers/{name:.*}/attach": s.postContainersAttach, 411 "/containers/{name:.*}/copy": s.postContainersCopy, 412 "/containers/{name:.*}/exec": s.postContainerExecCreate, 413 "/exec/{name:.*}/start": s.postContainerExecStart, 414 "/exec/{name:.*}/resize": s.postContainerExecResize, 415 "/containers/{name:.*}/rename": s.postContainerRename, 416 "/volumes": s.postVolumesCreate, 417 }, 418 "PUT": { 419 "/containers/{name:.*}/archive": s.putContainersArchive, 420 }, 421 "DELETE": { 422 "/containers/{name:.*}": s.deleteContainers, 423 "/images/{name:.*}": s.deleteImages, 424 "/volumes/{name:.*}": s.deleteVolumes, 425 }, 426 "OPTIONS": { 427 "": s.optionsHandler, 428 }, 429 } 430 431 // If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*" 432 // otherwise, all head values will be passed to HTTP handler 433 corsHeaders := s.cfg.CorsHeaders 434 if corsHeaders == "" && s.cfg.EnableCors { 435 corsHeaders = "*" 436 } 437 438 for method, routes := range m { 439 for route, fct := range routes { 440 logrus.Debugf("Registering %s, %s", method, route) 441 // NOTE: scope issue, make sure the variables are local and won't be changed 442 localRoute := route 443 localFct := fct 444 localMethod := method 445 446 // build the handler function 447 f := makeHTTPHandler(s.cfg.Logging, localMethod, localRoute, localFct, corsHeaders, version.Version(s.cfg.Version)) 448 449 // add the new route 450 if localRoute == "" { 451 r.Methods(localMethod).HandlerFunc(f) 452 } else { 453 r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f) 454 r.Path(localRoute).Methods(localMethod).HandlerFunc(f) 455 } 456 } 457 } 458 459 return r 460 }