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