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  }