mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00

Avoid creating a global context object that will be used while the daemon is running. Not only this object won't ever be garbage collected, but it won't ever be used for anything else than creating other contexts in each request. I think it's a bad practive to have something like this sprawling aroud the code. This change removes that global object and initializes a context in the cases we don't have already one, like shutting down the server. This also removes a bunch of context arguments from functions that did nothing with it. Signed-off-by: David Calavera <david.calavera@gmail.com>
414 lines
13 KiB
Go
414 lines
13 KiB
Go
package server
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/distribution/registry/api/errcode"
|
|
"github.com/docker/docker/api"
|
|
"github.com/docker/docker/context"
|
|
"github.com/docker/docker/daemon"
|
|
"github.com/docker/docker/pkg/sockets"
|
|
"github.com/docker/docker/pkg/stringid"
|
|
"github.com/docker/docker/utils"
|
|
)
|
|
|
|
// Config provides the configuration for the API server
|
|
type Config struct {
|
|
Logging bool
|
|
EnableCors bool
|
|
CorsHeaders string
|
|
Version string
|
|
SocketGroup string
|
|
TLSConfig *tls.Config
|
|
}
|
|
|
|
// Server contains instance details for the server
|
|
type Server struct {
|
|
daemon *daemon.Daemon
|
|
cfg *Config
|
|
router *mux.Router
|
|
start chan struct{}
|
|
servers []serverCloser
|
|
}
|
|
|
|
// New returns a new instance of the server based on the specified configuration.
|
|
func New(cfg *Config) *Server {
|
|
srv := &Server{
|
|
cfg: cfg,
|
|
start: make(chan struct{}),
|
|
}
|
|
srv.router = createRouter(srv)
|
|
return srv
|
|
}
|
|
|
|
// Close closes servers and thus stop receiving requests
|
|
func (s *Server) Close() {
|
|
for _, srv := range s.servers {
|
|
if err := srv.Close(); err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
type serverCloser interface {
|
|
Serve() error
|
|
Close() error
|
|
}
|
|
|
|
// ServeAPI loops through all of the protocols sent in to docker and spawns
|
|
// off a go routine to setup a serving http.Server for each.
|
|
func (s *Server) ServeAPI(protoAddrs []string) error {
|
|
var chErrors = make(chan error, len(protoAddrs))
|
|
|
|
for _, protoAddr := range protoAddrs {
|
|
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
|
if len(protoAddrParts) != 2 {
|
|
return fmt.Errorf("bad format, expected PROTO://ADDR")
|
|
}
|
|
srv, err := s.newServer(protoAddrParts[0], protoAddrParts[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.servers = append(s.servers, srv...)
|
|
|
|
for _, s := range srv {
|
|
logrus.Infof("Listening for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1])
|
|
go func(s serverCloser) {
|
|
if err := s.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") {
|
|
err = nil
|
|
}
|
|
chErrors <- err
|
|
}(s)
|
|
}
|
|
}
|
|
|
|
for i := 0; i < len(protoAddrs); i++ {
|
|
err := <-chErrors
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// HTTPServer contains an instance of http server and the listener.
|
|
// srv *http.Server, contains configuration to create a http server and a mux router with all api end points.
|
|
// l net.Listener, is a TCP or Socket listener that dispatches incoming request to the router.
|
|
type HTTPServer struct {
|
|
srv *http.Server
|
|
l net.Listener
|
|
}
|
|
|
|
// Serve starts listening for inbound requests.
|
|
func (s *HTTPServer) Serve() error {
|
|
return s.srv.Serve(s.l)
|
|
}
|
|
|
|
// Close closes the HTTPServer from listening for the inbound requests.
|
|
func (s *HTTPServer) Close() error {
|
|
return s.l.Close()
|
|
}
|
|
|
|
// HTTPAPIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
|
|
// Any function that has the appropriate signature can be register as a API endpoint (e.g. getVersion).
|
|
type HTTPAPIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
|
|
|
|
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
|
conn, _, err := w.(http.Hijacker).Hijack()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
// Flush the options to make sure the client sets the raw mode
|
|
conn.Write([]byte{})
|
|
return conn, conn, nil
|
|
}
|
|
|
|
func closeStreams(streams ...interface{}) {
|
|
for _, stream := range streams {
|
|
if tcpc, ok := stream.(interface {
|
|
CloseWrite() error
|
|
}); ok {
|
|
tcpc.CloseWrite()
|
|
} else if closer, ok := stream.(io.Closer); ok {
|
|
closer.Close()
|
|
}
|
|
}
|
|
}
|
|
|
|
// checkForJSON makes sure that the request's Content-Type is application/json.
|
|
func checkForJSON(r *http.Request) error {
|
|
ct := r.Header.Get("Content-Type")
|
|
|
|
// No Content-Type header is ok as long as there's no Body
|
|
if ct == "" {
|
|
if r.Body == nil || r.ContentLength == 0 {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Otherwise it better be json
|
|
if api.MatchesContentType(ct, "application/json") {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct)
|
|
}
|
|
|
|
//If we don't do this, POST method without Content-type (even with empty body) will fail
|
|
func parseForm(r *http.Request) error {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseMultipartForm(r *http.Request) error {
|
|
if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func httpError(w http.ResponseWriter, err error) {
|
|
if err == nil || w == nil {
|
|
logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
|
|
return
|
|
}
|
|
|
|
statusCode := http.StatusInternalServerError
|
|
errMsg := err.Error()
|
|
|
|
// Based on the type of error we get we need to process things
|
|
// slightly differently to extract the error message.
|
|
// In the 'errcode.*' cases there are two different type of
|
|
// error that could be returned. errocode.ErrorCode is the base
|
|
// type of error object - it is just an 'int' that can then be
|
|
// used as the look-up key to find the message. errorcode.Error
|
|
// extends errorcode.Error by adding error-instance specific
|
|
// data, like 'details' or variable strings to be inserted into
|
|
// the message.
|
|
//
|
|
// Ideally, we should just be able to call err.Error() for all
|
|
// cases but the errcode package doesn't support that yet.
|
|
//
|
|
// Additionally, in both errcode cases, there might be an http
|
|
// status code associated with it, and if so use it.
|
|
switch err.(type) {
|
|
case errcode.ErrorCode:
|
|
daError, _ := err.(errcode.ErrorCode)
|
|
statusCode = daError.Descriptor().HTTPStatusCode
|
|
errMsg = daError.Message()
|
|
|
|
case errcode.Error:
|
|
// For reference, if you're looking for a particular error
|
|
// then you can do something like :
|
|
// import ( derr "github.com/docker/docker/errors" )
|
|
// if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... }
|
|
|
|
daError, _ := err.(errcode.Error)
|
|
statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode
|
|
errMsg = daError.Message
|
|
|
|
default:
|
|
// This part of will be removed once we've
|
|
// converted everything over to use the errcode package
|
|
|
|
// FIXME: this is brittle and should not be necessary.
|
|
// If we need to differentiate between different possible error types,
|
|
// we should create appropriate error types with clearly defined meaning
|
|
errStr := strings.ToLower(err.Error())
|
|
for keyword, status := range map[string]int{
|
|
"not found": http.StatusNotFound,
|
|
"no such": http.StatusNotFound,
|
|
"bad parameter": http.StatusBadRequest,
|
|
"conflict": http.StatusConflict,
|
|
"impossible": http.StatusNotAcceptable,
|
|
"wrong login/password": http.StatusUnauthorized,
|
|
"hasn't been activated": http.StatusForbidden,
|
|
} {
|
|
if strings.Contains(errStr, keyword) {
|
|
statusCode = status
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if statusCode == 0 {
|
|
statusCode = http.StatusInternalServerError
|
|
}
|
|
|
|
logrus.WithFields(logrus.Fields{"statusCode": statusCode, "err": utils.GetErrorMessage(err)}).Error("HTTP Error")
|
|
http.Error(w, errMsg, statusCode)
|
|
}
|
|
|
|
// writeJSON writes the value v to the http response stream as json with standard
|
|
// json encoding.
|
|
func writeJSON(w http.ResponseWriter, code int, v interface{}) error {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(code)
|
|
return json.NewEncoder(w).Encode(v)
|
|
}
|
|
|
|
func (s *Server) optionsHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
w.WriteHeader(http.StatusOK)
|
|
return nil
|
|
}
|
|
func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string) {
|
|
logrus.Debugf("CORS header is enabled and set to: %s", corsHeaders)
|
|
w.Header().Add("Access-Control-Allow-Origin", corsHeaders)
|
|
w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth")
|
|
w.Header().Add("Access-Control-Allow-Methods", "HEAD, GET, POST, DELETE, PUT, OPTIONS")
|
|
}
|
|
|
|
func (s *Server) ping(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
_, err := w.Write([]byte{'O', 'K'})
|
|
return err
|
|
}
|
|
|
|
func (s *Server) initTCPSocket(addr string) (l net.Listener, err error) {
|
|
if s.cfg.TLSConfig == nil || s.cfg.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert {
|
|
logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
|
}
|
|
if l, err = sockets.NewTCPSocket(addr, s.cfg.TLSConfig, s.start); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := allocateDaemonPort(addr); err != nil {
|
|
return nil, err
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *Server) makeHTTPHandler(localMethod string, localRoute string, localHandler HTTPAPIFunc) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
// log the handler generation
|
|
logrus.Debugf("Calling %s %s", localMethod, localRoute)
|
|
|
|
// Define the context that we'll pass around to share info
|
|
// like the docker-request-id.
|
|
//
|
|
// The 'context' will be used for global data that should
|
|
// apply to all requests. Data that is specific to the
|
|
// immediate function being called should still be passed
|
|
// as 'args' on the function call.
|
|
ctx := context.Background()
|
|
|
|
reqID := stringid.TruncateID(stringid.GenerateNonCryptoID())
|
|
ctx = context.WithValue(ctx, context.RequestID, reqID)
|
|
handlerFunc := s.handleWithGlobalMiddlewares(localHandler)
|
|
|
|
if err := handlerFunc(ctx, w, r, mux.Vars(r)); err != nil {
|
|
logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, utils.GetErrorMessage(err))
|
|
httpError(w, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// createRouter initializes the main router the server uses.
|
|
// we keep enableCors just for legacy usage, need to be removed in the future
|
|
func createRouter(s *Server) *mux.Router {
|
|
r := mux.NewRouter()
|
|
if os.Getenv("DEBUG") != "" {
|
|
profilerSetup(r, "/debug/")
|
|
}
|
|
m := map[string]map[string]HTTPAPIFunc{
|
|
"HEAD": {
|
|
"/containers/{name:.*}/archive": s.headContainersArchive,
|
|
},
|
|
"GET": {
|
|
"/_ping": s.ping,
|
|
"/events": s.getEvents,
|
|
"/info": s.getInfo,
|
|
"/version": s.getVersion,
|
|
"/images/json": s.getImagesJSON,
|
|
"/images/search": s.getImagesSearch,
|
|
"/images/get": s.getImagesGet,
|
|
"/images/{name:.*}/get": s.getImagesGet,
|
|
"/images/{name:.*}/history": s.getImagesHistory,
|
|
"/images/{name:.*}/json": s.getImagesByName,
|
|
"/containers/json": s.getContainersJSON,
|
|
"/containers/{name:.*}/export": s.getContainersExport,
|
|
"/containers/{name:.*}/changes": s.getContainersChanges,
|
|
"/containers/{name:.*}/json": s.getContainersByName,
|
|
"/containers/{name:.*}/top": s.getContainersTop,
|
|
"/containers/{name:.*}/logs": s.getContainersLogs,
|
|
"/containers/{name:.*}/stats": s.getContainersStats,
|
|
"/containers/{name:.*}/attach/ws": s.wsContainersAttach,
|
|
"/exec/{id:.*}/json": s.getExecByID,
|
|
"/containers/{name:.*}/archive": s.getContainersArchive,
|
|
"/volumes": s.getVolumesList,
|
|
"/volumes/{name:.*}": s.getVolumeByName,
|
|
},
|
|
"POST": {
|
|
"/auth": s.postAuth,
|
|
"/commit": s.postCommit,
|
|
"/build": s.postBuild,
|
|
"/images/create": s.postImagesCreate,
|
|
"/images/load": s.postImagesLoad,
|
|
"/images/{name:.*}/push": s.postImagesPush,
|
|
"/images/{name:.*}/tag": s.postImagesTag,
|
|
"/containers/create": s.postContainersCreate,
|
|
"/containers/{name:.*}/kill": s.postContainersKill,
|
|
"/containers/{name:.*}/pause": s.postContainersPause,
|
|
"/containers/{name:.*}/unpause": s.postContainersUnpause,
|
|
"/containers/{name:.*}/restart": s.postContainersRestart,
|
|
"/containers/{name:.*}/start": s.postContainersStart,
|
|
"/containers/{name:.*}/stop": s.postContainersStop,
|
|
"/containers/{name:.*}/wait": s.postContainersWait,
|
|
"/containers/{name:.*}/resize": s.postContainersResize,
|
|
"/containers/{name:.*}/attach": s.postContainersAttach,
|
|
"/containers/{name:.*}/copy": s.postContainersCopy,
|
|
"/containers/{name:.*}/exec": s.postContainerExecCreate,
|
|
"/exec/{name:.*}/start": s.postContainerExecStart,
|
|
"/exec/{name:.*}/resize": s.postContainerExecResize,
|
|
"/containers/{name:.*}/rename": s.postContainerRename,
|
|
"/volumes": s.postVolumesCreate,
|
|
},
|
|
"PUT": {
|
|
"/containers/{name:.*}/archive": s.putContainersArchive,
|
|
},
|
|
"DELETE": {
|
|
"/containers/{name:.*}": s.deleteContainers,
|
|
"/images/{name:.*}": s.deleteImages,
|
|
"/volumes/{name:.*}": s.deleteVolumes,
|
|
},
|
|
"OPTIONS": {
|
|
"": s.optionsHandler,
|
|
},
|
|
}
|
|
|
|
for method, routes := range m {
|
|
for route, fct := range routes {
|
|
logrus.Debugf("Registering %s, %s", method, route)
|
|
// NOTE: scope issue, make sure the variables are local and won't be changed
|
|
localRoute := route
|
|
localFct := fct
|
|
localMethod := method
|
|
|
|
// build the handler function
|
|
f := s.makeHTTPHandler(localMethod, localRoute, localFct)
|
|
|
|
// add the new route
|
|
if localRoute == "" {
|
|
r.Methods(localMethod).HandlerFunc(f)
|
|
} else {
|
|
r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
|
|
r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
|
|
}
|
|
}
|
|
}
|
|
|
|
return r
|
|
}
|