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  }