From da982cf5511814b6897244ecaa9c016f8800340a Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 23 Sep 2015 19:42:08 -0400 Subject: [PATCH] Separate API router from server. Implement basic interfaces to write custom routers that can be plugged to the server. Remove server coupling with the daemon. Signed-off-by: David Calavera --- api/server/form.go | 57 ---- api/server/httputils/form.go | 63 ++++ api/server/{ => httputils}/form_test.go | 10 +- api/server/httputils/httputils.go | 180 +++++++++++ api/server/middleware.go | 32 +- api/server/middleware_test.go | 5 +- api/server/{ => router/local}/auth.go | 7 +- api/server/{ => router/local}/container.go | 127 ++++---- api/server/{ => router/local}/copy.go | 27 +- api/server/{ => router/local}/exec.go | 27 +- api/server/{ => router/local}/image.go | 93 +++--- .../{daemon.go => router/local/info.go} | 17 +- api/server/{ => router/local}/inspect.go | 9 +- api/server/router/local/local.go | 161 ++++++++++ api/server/{ => router/local}/volume.go | 27 +- api/server/router/network/network.go | 26 ++ .../router/network/network_experimental.go | 51 +++ api/server/router/network/network_stable.go | 20 ++ api/server/router/router.go | 25 ++ api/server/server.go | 299 +++--------------- api/server/server_experimental_unix.go | 22 -- api/server/server_stub.go | 6 - api/server/server_test.go | 4 +- api/server/server_unix.go | 20 +- api/server/server_windows.go | 16 +- daemon/daemon.go | 5 + daemon/daemon_unix.go | 8 - docker/daemon.go | 34 +- docker/daemon_freebsd.go | 7 + docker/daemon_linux.go | 7 + docker/daemon_none.go | 4 + docker/daemon_windows.go | 4 + 32 files changed, 809 insertions(+), 591 deletions(-) delete mode 100644 api/server/form.go create mode 100644 api/server/httputils/form.go rename api/server/{ => httputils}/form_test.go (86%) create mode 100644 api/server/httputils/httputils.go rename api/server/{ => router/local}/auth.go (69%) rename api/server/{ => router/local}/container.go (75%) rename api/server/{ => router/local}/copy.go (72%) rename api/server/{ => router/local}/exec.go (78%) rename api/server/{ => router/local}/image.go (79%) rename api/server/{daemon.go => router/local/info.go} (89%) rename api/server/{ => router/local}/inspect.go (72%) create mode 100644 api/server/router/local/local.go rename api/server/{ => router/local}/volume.go (55%) create mode 100644 api/server/router/network/network.go create mode 100644 api/server/router/network/network_experimental.go create mode 100644 api/server/router/network/network_stable.go create mode 100644 api/server/router/router.go delete mode 100644 api/server/server_experimental_unix.go delete mode 100644 api/server/server_stub.go create mode 100644 docker/daemon_freebsd.go diff --git a/api/server/form.go b/api/server/form.go deleted file mode 100644 index 996b11a3e1..0000000000 --- a/api/server/form.go +++ /dev/null @@ -1,57 +0,0 @@ -package server - -import ( - "fmt" - "net/http" - "path/filepath" - "strconv" - "strings" -) - -func boolValue(r *http.Request, k string) bool { - s := strings.ToLower(strings.TrimSpace(r.FormValue(k))) - return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none") -} - -// boolValueOrDefault returns the default bool passed if the query param is -// missing, otherwise it's just a proxy to boolValue above -func boolValueOrDefault(r *http.Request, k string, d bool) bool { - if _, ok := r.Form[k]; !ok { - return d - } - return boolValue(r, k) -} - -func int64ValueOrZero(r *http.Request, k string) int64 { - val, err := strconv.ParseInt(r.FormValue(k), 10, 64) - if err != nil { - return 0 - } - return val -} - -type archiveOptions struct { - name string - path string -} - -func archiveFormValues(r *http.Request, vars map[string]string) (archiveOptions, error) { - if vars == nil { - return archiveOptions{}, fmt.Errorf("Missing parameter") - } - if err := parseForm(r); err != nil { - return archiveOptions{}, err - } - - name := vars["name"] - path := filepath.FromSlash(r.Form.Get("path")) - - switch { - case name == "": - return archiveOptions{}, fmt.Errorf("bad parameter: 'name' cannot be empty") - case path == "": - return archiveOptions{}, fmt.Errorf("bad parameter: 'path' cannot be empty") - } - - return archiveOptions{name, path}, nil -} diff --git a/api/server/httputils/form.go b/api/server/httputils/form.go new file mode 100644 index 0000000000..e6daaa7340 --- /dev/null +++ b/api/server/httputils/form.go @@ -0,0 +1,63 @@ +package httputils + +import ( + "fmt" + "net/http" + "path/filepath" + "strconv" + "strings" +) + +// BoolValue transforms a form value in different formats into a boolean type. +func BoolValue(r *http.Request, k string) bool { + s := strings.ToLower(strings.TrimSpace(r.FormValue(k))) + return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none") +} + +// BoolValueOrDefault returns the default bool passed if the query param is +// missing, otherwise it's just a proxy to boolValue above +func BoolValueOrDefault(r *http.Request, k string, d bool) bool { + if _, ok := r.Form[k]; !ok { + return d + } + return BoolValue(r, k) +} + +// Int64ValueOrZero parses a form value into a int64 type. +// It returns 0 if the parsing fails. +func Int64ValueOrZero(r *http.Request, k string) int64 { + val, err := strconv.ParseInt(r.FormValue(k), 10, 64) + if err != nil { + return 0 + } + return val +} + +// ArchiveOptions stores archive information for different operations. +type ArchiveOptions struct { + Name string + Path string +} + +// ArchiveFormValues parses form values and turns them into ArchiveOptions. +// It fails if the archive name and path are not in the request. +func ArchiveFormValues(r *http.Request, vars map[string]string) (ArchiveOptions, error) { + if vars == nil { + return ArchiveOptions{}, fmt.Errorf("Missing parameter") + } + if err := ParseForm(r); err != nil { + return ArchiveOptions{}, err + } + + name := vars["name"] + path := filepath.FromSlash(r.Form.Get("path")) + + switch { + case name == "": + return ArchiveOptions{}, fmt.Errorf("bad parameter: 'name' cannot be empty") + case path == "": + return ArchiveOptions{}, fmt.Errorf("bad parameter: 'path' cannot be empty") + } + + return ArchiveOptions{name, path}, nil +} diff --git a/api/server/form_test.go b/api/server/httputils/form_test.go similarity index 86% rename from api/server/form_test.go rename to api/server/httputils/form_test.go index 5b3bd718d2..5ac0e18b09 100644 --- a/api/server/form_test.go +++ b/api/server/httputils/form_test.go @@ -1,4 +1,4 @@ -package server +package httputils import ( "net/http" @@ -26,7 +26,7 @@ func TestBoolValue(t *testing.T) { r, _ := http.NewRequest("POST", "", nil) r.Form = v - a := boolValue(r, "test") + a := BoolValue(r, "test") if a != e { t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a) } @@ -35,7 +35,7 @@ func TestBoolValue(t *testing.T) { func TestBoolValueOrDefault(t *testing.T) { r, _ := http.NewRequest("GET", "", nil) - if !boolValueOrDefault(r, "queryparam", true) { + if !BoolValueOrDefault(r, "queryparam", true) { t.Fatal("Expected to get true default value, got false") } @@ -43,7 +43,7 @@ func TestBoolValueOrDefault(t *testing.T) { v.Set("param", "") r, _ = http.NewRequest("GET", "", nil) r.Form = v - if boolValueOrDefault(r, "param", true) { + if BoolValueOrDefault(r, "param", true) { t.Fatal("Expected not to get true") } } @@ -62,7 +62,7 @@ func TestInt64ValueOrZero(t *testing.T) { r, _ := http.NewRequest("POST", "", nil) r.Form = v - a := int64ValueOrZero(r, "test") + a := Int64ValueOrZero(r, "test") if a != e { t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a) } diff --git a/api/server/httputils/httputils.go b/api/server/httputils/httputils.go new file mode 100644 index 0000000000..2997dc0356 --- /dev/null +++ b/api/server/httputils/httputils.go @@ -0,0 +1,180 @@ +package httputils + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + + "golang.org/x/net/context" + + "github.com/Sirupsen/logrus" + "github.com/docker/distribution/registry/api/errcode" + "github.com/docker/docker/api" + "github.com/docker/docker/pkg/version" + "github.com/docker/docker/utils" +) + +// APIVersionKey is the client's requested API version. +const APIVersionKey = "api-version" + +// APIFunc 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 APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error + +// HijackConnection interrupts the http response writer to get the +// underlying connection and operate with it. +func HijackConnection(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 +} + +// CloseStreams ensures that a list for http streams are properly closed. +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) +} + +// ParseForm ensures the request form is parsed even with invalid content types. +// 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 +} + +// ParseMultipartForm ensure the request form is parsed, even with invalid content types. +func ParseMultipartForm(r *http.Request) error { + if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") { + return err + } + return nil +} + +// WriteError decodes a specific docker error and sends it in the response. +func WriteError(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) +} + +// VersionFromContext returns an API version from the context using APIVersionKey. +// It panics if the context value does not have version.Version type. +func VersionFromContext(ctx context.Context) (ver version.Version) { + if ctx == nil { + return + } + val := ctx.Value(APIVersionKey) + if val == nil { + return + } + return val.(version.Version) +} diff --git a/api/server/middleware.go b/api/server/middleware.go index 014bc9daf9..47e52ee5b2 100644 --- a/api/server/middleware.go +++ b/api/server/middleware.go @@ -7,21 +7,19 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api" + "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/errors" "github.com/docker/docker/pkg/version" "golang.org/x/net/context" ) -// apiVersionKey is the client's requested API version. -const apiVersionKey = "api-version" - // middleware is an adapter to allow the use of ordinary functions as Docker API filters. // Any function that has the appropriate signature can be register as a middleware. -type middleware func(handler HTTPAPIFunc) HTTPAPIFunc +type middleware func(handler httputils.APIFunc) httputils.APIFunc // loggingMiddleware logs each request when logging is enabled. -func (s *Server) loggingMiddleware(handler HTTPAPIFunc) HTTPAPIFunc { +func (s *Server) loggingMiddleware(handler httputils.APIFunc) httputils.APIFunc { return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if s.cfg.Logging { logrus.Infof("%s %s", r.Method, r.RequestURI) @@ -31,7 +29,7 @@ func (s *Server) loggingMiddleware(handler HTTPAPIFunc) HTTPAPIFunc { } // userAgentMiddleware checks the User-Agent header looking for a valid docker client spec. -func (s *Server) userAgentMiddleware(handler HTTPAPIFunc) HTTPAPIFunc { +func (s *Server) userAgentMiddleware(handler httputils.APIFunc) httputils.APIFunc { return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") { dockerVersion := version.Version(s.cfg.Version) @@ -53,7 +51,7 @@ func (s *Server) userAgentMiddleware(handler HTTPAPIFunc) HTTPAPIFunc { } // corsMiddleware sets the CORS header expectations in the server. -func (s *Server) corsMiddleware(handler HTTPAPIFunc) HTTPAPIFunc { +func (s *Server) corsMiddleware(handler httputils.APIFunc) httputils.APIFunc { return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { // If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*" // otherwise, all head values will be passed to HTTP handler @@ -70,7 +68,7 @@ func (s *Server) corsMiddleware(handler HTTPAPIFunc) HTTPAPIFunc { } // versionMiddleware checks the api version requirements before passing the request to the server handler. -func versionMiddleware(handler HTTPAPIFunc) HTTPAPIFunc { +func versionMiddleware(handler httputils.APIFunc) httputils.APIFunc { return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { apiVersion := version.Version(vars["version"]) if apiVersion == "" { @@ -85,7 +83,7 @@ func versionMiddleware(handler HTTPAPIFunc) HTTPAPIFunc { } w.Header().Set("Server", "Docker/"+dockerversion.VERSION+" ("+runtime.GOOS+")") - ctx = context.WithValue(ctx, apiVersionKey, apiVersion) + ctx = context.WithValue(ctx, httputils.APIVersionKey, apiVersion) return handler(ctx, w, r, vars) } } @@ -103,7 +101,8 @@ func versionMiddleware(handler HTTPAPIFunc) HTTPAPIFunc { // ) // ) // ) -func (s *Server) handleWithGlobalMiddlewares(handler HTTPAPIFunc) HTTPAPIFunc { +// ) +func (s *Server) handleWithGlobalMiddlewares(handler httputils.APIFunc) httputils.APIFunc { middlewares := []middleware{ versionMiddleware, s.corsMiddleware, @@ -117,16 +116,3 @@ func (s *Server) handleWithGlobalMiddlewares(handler HTTPAPIFunc) HTTPAPIFunc { } return h } - -// versionFromContext returns an API version from the context using apiVersionKey. -// It panics if the context value does not have version.Version type. -func versionFromContext(ctx context.Context) (ver version.Version) { - if ctx == nil { - return - } - val := ctx.Value(apiVersionKey) - if val == nil { - return - } - return val.(version.Version) -} diff --git a/api/server/middleware_test.go b/api/server/middleware_test.go index 7d6741f947..4f48b20990 100644 --- a/api/server/middleware_test.go +++ b/api/server/middleware_test.go @@ -6,13 +6,14 @@ import ( "testing" "github.com/docker/distribution/registry/api/errcode" + "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/errors" "golang.org/x/net/context" ) func TestVersionMiddleware(t *testing.T) { handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if versionFromContext(ctx) == "" { + if httputils.VersionFromContext(ctx) == "" { t.Fatalf("Expected version, got empty string") } return nil @@ -30,7 +31,7 @@ func TestVersionMiddleware(t *testing.T) { func TestVersionMiddlewareWithErrors(t *testing.T) { handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if versionFromContext(ctx) == "" { + if httputils.VersionFromContext(ctx) == "" { t.Fatalf("Expected version, got empty string") } return nil diff --git a/api/server/auth.go b/api/server/router/local/auth.go similarity index 69% rename from api/server/auth.go rename to api/server/router/local/auth.go index d4d8c3ebad..d7baba08b1 100644 --- a/api/server/auth.go +++ b/api/server/router/local/auth.go @@ -1,15 +1,16 @@ -package server +package local import ( "encoding/json" "net/http" + "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/types" "github.com/docker/docker/cliconfig" "golang.org/x/net/context" ) -func (s *Server) postAuth(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) postAuth(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { var config *cliconfig.AuthConfig err := json.NewDecoder(r.Body).Decode(&config) r.Body.Close() @@ -20,7 +21,7 @@ func (s *Server) postAuth(ctx context.Context, w http.ResponseWriter, r *http.Re if err != nil { return err } - return writeJSON(w, http.StatusOK, &types.AuthResponse{ + return httputils.WriteJSON(w, http.StatusOK, &types.AuthResponse{ Status: status, }) } diff --git a/api/server/container.go b/api/server/router/local/container.go similarity index 75% rename from api/server/container.go rename to api/server/router/local/container.go index 2c83d01792..76244f193c 100644 --- a/api/server/container.go +++ b/api/server/router/local/container.go @@ -1,4 +1,4 @@ -package server +package local import ( "fmt" @@ -11,6 +11,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/registry/api/errcode" + "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/types" "github.com/docker/docker/daemon" derr "github.com/docker/docker/errors" @@ -22,14 +23,14 @@ import ( "golang.org/x/net/websocket" ) -func (s *Server) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } config := &daemon.ContainersConfig{ - All: boolValue(r, "all"), - Size: boolValue(r, "size"), + All: httputils.BoolValue(r, "all"), + Size: httputils.BoolValue(r, "size"), Since: r.Form.Get("since"), Before: r.Form.Get("before"), Filters: r.Form.Get("filters"), @@ -48,18 +49,18 @@ func (s *Server) getContainersJSON(ctx context.Context, w http.ResponseWriter, r return err } - return writeJSON(w, http.StatusOK, containers) + return httputils.WriteJSON(w, http.StatusOK, containers) } -func (s *Server) getContainersStats(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) getContainersStats(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } if vars == nil { return fmt.Errorf("Missing parameter") } - stream := boolValueOrDefault(r, "stream", true) + stream := httputils.BoolValueOrDefault(r, "stream", true) var out io.Writer if !stream { w.Header().Set("Content-Type", "application/json") @@ -77,14 +78,14 @@ func (s *Server) getContainersStats(ctx context.Context, w http.ResponseWriter, Stream: stream, OutStream: out, Stop: closeNotifier, - Version: versionFromContext(ctx), + Version: httputils.VersionFromContext(ctx), } return s.daemon.ContainerStats(vars["name"], config) } -func (s *Server) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } if vars == nil { @@ -96,7 +97,7 @@ func (s *Server) getContainersLogs(ctx context.Context, w http.ResponseWriter, r // daemon is going to stream. By sending this initial HTTP 200 we can't report // any error after the stream starts (i.e. container not found, wrong parameters) // with the appropriate status code. - stdout, stderr := boolValue(r, "stdout"), boolValue(r, "stderr") + stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr") if !(stdout || stderr) { return fmt.Errorf("Bad parameters: you must choose at least one stream") } @@ -127,8 +128,8 @@ func (s *Server) getContainersLogs(ctx context.Context, w http.ResponseWriter, r outStream.Write(nil) logsConfig := &daemon.ContainerLogsConfig{ - Follow: boolValue(r, "follow"), - Timestamps: boolValue(r, "timestamps"), + Follow: httputils.BoolValue(r, "follow"), + Timestamps: httputils.BoolValue(r, "timestamps"), Since: since, Tail: r.Form.Get("tail"), UseStdout: stdout, @@ -147,7 +148,7 @@ func (s *Server) getContainersLogs(ctx context.Context, w http.ResponseWriter, r return nil } -func (s *Server) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -155,7 +156,7 @@ func (s *Server) getContainersExport(ctx context.Context, w http.ResponseWriter, return s.daemon.ContainerExport(vars["name"], w) } -func (s *Server) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -168,7 +169,7 @@ func (s *Server) postContainersStart(ctx context.Context, w http.ResponseWriter, // allow a nil body for backwards compatibility var hostConfig *runconfig.HostConfig if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) { - if err := checkForJSON(r); err != nil { + if err := httputils.CheckForJSON(r); err != nil { return err } @@ -187,8 +188,8 @@ func (s *Server) postContainersStart(ctx context.Context, w http.ResponseWriter, return nil } -func (s *Server) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } if vars == nil { @@ -205,11 +206,11 @@ func (s *Server) postContainersStop(ctx context.Context, w http.ResponseWriter, return nil } -func (s *Server) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } - if err := parseForm(r); err != nil { + if err := httputils.ParseForm(r); err != nil { return err } @@ -231,7 +232,7 @@ func (s *Server) postContainersKill(ctx context.Context, w http.ResponseWriter, // Return error that's not caused because the container is stopped. // Return error if the container is not running and the api is >= 1.20 // to keep backwards compatibility. - version := versionFromContext(ctx) + version := httputils.VersionFromContext(ctx) if version.GreaterThanOrEqualTo("1.20") || !isStopped { return fmt.Errorf("Cannot kill container %s: %v", name, err) } @@ -241,8 +242,8 @@ func (s *Server) postContainersKill(ctx context.Context, w http.ResponseWriter, return nil } -func (s *Server) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } if vars == nil { @@ -260,11 +261,11 @@ func (s *Server) postContainersRestart(ctx context.Context, w http.ResponseWrite return nil } -func (s *Server) postContainersPause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) postContainersPause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } - if err := parseForm(r); err != nil { + if err := httputils.ParseForm(r); err != nil { return err } @@ -277,11 +278,11 @@ func (s *Server) postContainersPause(ctx context.Context, w http.ResponseWriter, return nil } -func (s *Server) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } - if err := parseForm(r); err != nil { + if err := httputils.ParseForm(r); err != nil { return err } @@ -294,7 +295,7 @@ func (s *Server) postContainersUnpause(ctx context.Context, w http.ResponseWrite return nil } -func (s *Server) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -304,12 +305,12 @@ func (s *Server) postContainersWait(ctx context.Context, w http.ResponseWriter, return err } - return writeJSON(w, http.StatusOK, &types.ContainerWaitResponse{ + return httputils.WriteJSON(w, http.StatusOK, &types.ContainerWaitResponse{ StatusCode: status, }) } -func (s *Server) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -319,15 +320,15 @@ func (s *Server) getContainersChanges(ctx context.Context, w http.ResponseWriter return err } - return writeJSON(w, http.StatusOK, changes) + return httputils.WriteJSON(w, http.StatusOK, changes) } -func (s *Server) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } - if err := parseForm(r); err != nil { + if err := httputils.ParseForm(r); err != nil { return err } @@ -336,11 +337,11 @@ func (s *Server) getContainersTop(ctx context.Context, w http.ResponseWriter, r return err } - return writeJSON(w, http.StatusOK, procList) + return httputils.WriteJSON(w, http.StatusOK, procList) } -func (s *Server) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } if vars == nil { @@ -356,11 +357,11 @@ func (s *Server) postContainerRename(ctx context.Context, w http.ResponseWriter, return nil } -func (s *Server) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } - if err := checkForJSON(r); err != nil { + if err := httputils.CheckForJSON(r); err != nil { return err } @@ -370,7 +371,7 @@ func (s *Server) postContainersCreate(ctx context.Context, w http.ResponseWriter if err != nil { return err } - version := versionFromContext(ctx) + version := httputils.VersionFromContext(ctx) adjustCPUShares := version.LessThan("1.19") ccr, err := s.daemon.ContainerCreate(name, config, hostConfig, adjustCPUShares) @@ -378,11 +379,11 @@ func (s *Server) postContainersCreate(ctx context.Context, w http.ResponseWriter return err } - return writeJSON(w, http.StatusCreated, ccr) + return httputils.WriteJSON(w, http.StatusCreated, ccr) } -func (s *Server) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } if vars == nil { @@ -391,9 +392,9 @@ func (s *Server) deleteContainers(ctx context.Context, w http.ResponseWriter, r name := vars["name"] config := &daemon.ContainerRmConfig{ - ForceRemove: boolValue(r, "force"), - RemoveVolume: boolValue(r, "v"), - RemoveLink: boolValue(r, "link"), + ForceRemove: httputils.BoolValue(r, "force"), + RemoveVolume: httputils.BoolValue(r, "v"), + RemoveLink: httputils.BoolValue(r, "link"), } if err := s.daemon.ContainerRm(name, config); err != nil { @@ -409,8 +410,8 @@ func (s *Server) deleteContainers(ctx context.Context, w http.ResponseWriter, r return nil } -func (s *Server) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } if vars == nil { @@ -429,8 +430,8 @@ func (s *Server) postContainersResize(ctx context.Context, w http.ResponseWriter return s.daemon.ContainerResize(vars["name"], height, width) } -func (s *Server) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } if vars == nil { @@ -442,11 +443,11 @@ func (s *Server) postContainersAttach(ctx context.Context, w http.ResponseWriter return derr.ErrorCodeNoSuchContainer.WithArgs(containerName) } - inStream, outStream, err := hijackServer(w) + inStream, outStream, err := httputils.HijackConnection(w) if err != nil { return err } - defer closeStreams(inStream, outStream) + defer httputils.CloseStreams(inStream, outStream) if _, ok := r.Header["Upgrade"]; ok { fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n") @@ -457,11 +458,11 @@ func (s *Server) postContainersAttach(ctx context.Context, w http.ResponseWriter attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{ InStream: inStream, OutStream: outStream, - UseStdin: boolValue(r, "stdin"), - UseStdout: boolValue(r, "stdout"), - UseStderr: boolValue(r, "stderr"), - Logs: boolValue(r, "logs"), - Stream: boolValue(r, "stream"), + UseStdin: httputils.BoolValue(r, "stdin"), + UseStdout: httputils.BoolValue(r, "stdout"), + UseStderr: httputils.BoolValue(r, "stderr"), + Logs: httputils.BoolValue(r, "logs"), + Stream: httputils.BoolValue(r, "stream"), } if err := s.daemon.ContainerAttachWithLogs(containerName, attachWithLogsConfig); err != nil { @@ -471,8 +472,8 @@ func (s *Server) postContainersAttach(ctx context.Context, w http.ResponseWriter return nil } -func (s *Server) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } if vars == nil { @@ -491,8 +492,8 @@ func (s *Server) wsContainersAttach(ctx context.Context, w http.ResponseWriter, InStream: ws, OutStream: ws, ErrStream: ws, - Logs: boolValue(r, "logs"), - Stream: boolValue(r, "stream"), + Logs: httputils.BoolValue(r, "logs"), + Stream: httputils.BoolValue(r, "stream"), } if err := s.daemon.ContainerWsAttachWithLogs(containerName, wsAttachWithLogsConfig); err != nil { diff --git a/api/server/copy.go b/api/server/router/local/copy.go similarity index 72% rename from api/server/copy.go rename to api/server/router/local/copy.go index 8b9fcba4c9..72ceb0a44e 100644 --- a/api/server/copy.go +++ b/api/server/router/local/copy.go @@ -1,4 +1,4 @@ -package server +package local import ( "encoding/base64" @@ -9,17 +9,18 @@ import ( "os" "strings" + "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/types" "golang.org/x/net/context" ) // postContainersCopy is deprecated in favor of getContainersArchive. -func (s *Server) postContainersCopy(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) postContainersCopy(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } - if err := checkForJSON(r); err != nil { + if err := httputils.CheckForJSON(r); err != nil { return err } @@ -68,13 +69,13 @@ func setContainerPathStatHeader(stat *types.ContainerPathStat, header http.Heade return nil } -func (s *Server) headContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - v, err := archiveFormValues(r, vars) +func (s *router) headContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + v, err := httputils.ArchiveFormValues(r, vars) if err != nil { return err } - stat, err := s.daemon.ContainerStatPath(v.name, v.path) + stat, err := s.daemon.ContainerStatPath(v.Name, v.Path) if err != nil { return err } @@ -82,13 +83,13 @@ func (s *Server) headContainersArchive(ctx context.Context, w http.ResponseWrite return setContainerPathStatHeader(stat, w.Header()) } -func (s *Server) getContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - v, err := archiveFormValues(r, vars) +func (s *router) getContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + v, err := httputils.ArchiveFormValues(r, vars) if err != nil { return err } - tarArchive, stat, err := s.daemon.ContainerArchivePath(v.name, v.path) + tarArchive, stat, err := s.daemon.ContainerArchivePath(v.Name, v.Path) if err != nil { return err } @@ -104,12 +105,12 @@ func (s *Server) getContainersArchive(ctx context.Context, w http.ResponseWriter return err } -func (s *Server) putContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - v, err := archiveFormValues(r, vars) +func (s *router) putContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + v, err := httputils.ArchiveFormValues(r, vars) if err != nil { return err } - noOverwriteDirNonDir := boolValue(r, "noOverwriteDirNonDir") - return s.daemon.ContainerExtractToDir(v.name, v.path, noOverwriteDirNonDir, r.Body) + noOverwriteDirNonDir := httputils.BoolValue(r, "noOverwriteDirNonDir") + return s.daemon.ContainerExtractToDir(v.Name, v.Path, noOverwriteDirNonDir, r.Body) } diff --git a/api/server/exec.go b/api/server/router/local/exec.go similarity index 78% rename from api/server/exec.go rename to api/server/router/local/exec.go index 87c16a0f5e..b5b6364480 100644 --- a/api/server/exec.go +++ b/api/server/router/local/exec.go @@ -1,4 +1,4 @@ -package server +package local import ( "encoding/json" @@ -8,13 +8,14 @@ import ( "strconv" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/runconfig" "golang.org/x/net/context" ) -func (s *Server) getExecByID(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) getExecByID(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter 'id'") } @@ -24,14 +25,14 @@ func (s *Server) getExecByID(ctx context.Context, w http.ResponseWriter, r *http return err } - return writeJSON(w, http.StatusOK, eConfig) + return httputils.WriteJSON(w, http.StatusOK, eConfig) } -func (s *Server) postContainerExecCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) postContainerExecCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } - if err := checkForJSON(r); err != nil { + if err := httputils.CheckForJSON(r); err != nil { return err } name := vars["name"] @@ -53,14 +54,14 @@ func (s *Server) postContainerExecCreate(ctx context.Context, w http.ResponseWri return err } - return writeJSON(w, http.StatusCreated, &types.ContainerExecCreateResponse{ + return httputils.WriteJSON(w, http.StatusCreated, &types.ContainerExecCreateResponse{ ID: id, }) } // TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start. -func (s *Server) postContainerExecStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) postContainerExecStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } var ( @@ -77,11 +78,11 @@ func (s *Server) postContainerExecStart(ctx context.Context, w http.ResponseWrit if !execStartCheck.Detach { var err error // Setting up the streaming http interface. - inStream, outStream, err = hijackServer(w) + inStream, outStream, err = httputils.HijackConnection(w) if err != nil { return err } - defer closeStreams(inStream, outStream) + defer httputils.CloseStreams(inStream, outStream) if _, ok := r.Header["Upgrade"]; ok { fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n") @@ -106,8 +107,8 @@ func (s *Server) postContainerExecStart(ctx context.Context, w http.ResponseWrit return nil } -func (s *Server) postContainerExecResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) postContainerExecResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } if vars == nil { diff --git a/api/server/image.go b/api/server/router/local/image.go similarity index 79% rename from api/server/image.go rename to api/server/router/local/image.go index ad369d34ef..019b2ed5a2 100644 --- a/api/server/image.go +++ b/api/server/router/local/image.go @@ -1,4 +1,4 @@ -package server +package local import ( "encoding/base64" @@ -10,6 +10,7 @@ import ( "strings" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/types" "github.com/docker/docker/builder" "github.com/docker/docker/cliconfig" @@ -23,19 +24,19 @@ import ( "golang.org/x/net/context" ) -func (s *Server) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } - if err := checkForJSON(r); err != nil { + if err := httputils.CheckForJSON(r); err != nil { return err } cname := r.Form.Get("container") - pause := boolValue(r, "pause") - version := versionFromContext(ctx) + pause := httputils.BoolValue(r, "pause") + version := httputils.VersionFromContext(ctx) if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") { pause = true } @@ -60,14 +61,14 @@ func (s *Server) postCommit(ctx context.Context, w http.ResponseWriter, r *http. return err } - return writeJSON(w, http.StatusCreated, &types.ContainerCommitResponse{ + return httputils.WriteJSON(w, http.StatusCreated, &types.ContainerCommitResponse{ ID: imgID, }) } // Creates an image from Pull or from Import -func (s *Server) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } @@ -142,7 +143,7 @@ func (s *Server) postImagesCreate(ctx context.Context, w http.ResponseWriter, r return nil } -func (s *Server) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -153,7 +154,7 @@ func (s *Server) postImagesPush(ctx context.Context, w http.ResponseWriter, r *h metaHeaders[k] = v } } - if err := parseForm(r); err != nil { + if err := httputils.ParseForm(r); err != nil { return err } authConfig := &cliconfig.AuthConfig{} @@ -194,11 +195,11 @@ func (s *Server) postImagesPush(ctx context.Context, w http.ResponseWriter, r *h return nil } -func (s *Server) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } - if err := parseForm(r); err != nil { + if err := httputils.ParseForm(r); err != nil { return err } @@ -222,12 +223,12 @@ func (s *Server) getImagesGet(ctx context.Context, w http.ResponseWriter, r *htt return nil } -func (s *Server) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { return s.daemon.Repositories().Load(r.Body, w) } -func (s *Server) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } if vars == nil { @@ -240,18 +241,18 @@ func (s *Server) deleteImages(ctx context.Context, w http.ResponseWriter, r *htt return fmt.Errorf("image name cannot be blank") } - force := boolValue(r, "force") - prune := !boolValue(r, "noprune") + force := httputils.BoolValue(r, "force") + prune := !httputils.BoolValue(r, "noprune") list, err := s.daemon.ImageDelete(name, force, prune) if err != nil { return err } - return writeJSON(w, http.StatusOK, list) + return httputils.WriteJSON(w, http.StatusOK, list) } -func (s *Server) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -261,10 +262,10 @@ func (s *Server) getImagesByName(ctx context.Context, w http.ResponseWriter, r * return err } - return writeJSON(w, http.StatusOK, imageInspect) + return httputils.WriteJSON(w, http.StatusOK, imageInspect) } -func (s *Server) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { var ( authConfigs = map[string]cliconfig.AuthConfig{} authConfigsEncoded = r.Header.Get("X-Registry-Config") @@ -282,15 +283,15 @@ func (s *Server) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R w.Header().Set("Content-Type", "application/json") - version := versionFromContext(ctx) - if boolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") { + version := httputils.VersionFromContext(ctx) + if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") { buildConfig.Remove = true } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { buildConfig.Remove = true } else { - buildConfig.Remove = boolValue(r, "rm") + buildConfig.Remove = httputils.BoolValue(r, "rm") } - if boolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") { + if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") { buildConfig.Pull = true } @@ -301,15 +302,15 @@ func (s *Server) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R buildConfig.RemoteURL = r.FormValue("remote") buildConfig.DockerfileName = r.FormValue("dockerfile") buildConfig.RepoName = r.FormValue("t") - buildConfig.SuppressOutput = boolValue(r, "q") - buildConfig.NoCache = boolValue(r, "nocache") - buildConfig.ForceRemove = boolValue(r, "forcerm") + buildConfig.SuppressOutput = httputils.BoolValue(r, "q") + buildConfig.NoCache = httputils.BoolValue(r, "nocache") + buildConfig.ForceRemove = httputils.BoolValue(r, "forcerm") buildConfig.AuthConfigs = authConfigs - buildConfig.MemorySwap = int64ValueOrZero(r, "memswap") - buildConfig.Memory = int64ValueOrZero(r, "memory") - buildConfig.CPUShares = int64ValueOrZero(r, "cpushares") - buildConfig.CPUPeriod = int64ValueOrZero(r, "cpuperiod") - buildConfig.CPUQuota = int64ValueOrZero(r, "cpuquota") + buildConfig.MemorySwap = httputils.Int64ValueOrZero(r, "memswap") + buildConfig.Memory = httputils.Int64ValueOrZero(r, "memory") + buildConfig.CPUShares = httputils.Int64ValueOrZero(r, "cpushares") + buildConfig.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod") + buildConfig.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota") buildConfig.CPUSetCpus = r.FormValue("cpusetcpus") buildConfig.CPUSetMems = r.FormValue("cpusetmems") buildConfig.CgroupParent = r.FormValue("cgroupparent") @@ -358,21 +359,21 @@ func (s *Server) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R return nil } -func (s *Server) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } // FIXME: The filter parameter could just be a match filter - images, err := s.daemon.Repositories().Images(r.Form.Get("filters"), r.Form.Get("filter"), boolValue(r, "all")) + images, err := s.daemon.Repositories().Images(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all")) if err != nil { return err } - return writeJSON(w, http.StatusOK, images) + return httputils.WriteJSON(w, http.StatusOK, images) } -func (s *Server) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -383,11 +384,11 @@ func (s *Server) getImagesHistory(ctx context.Context, w http.ResponseWriter, r return err } - return writeJSON(w, http.StatusOK, history) + return httputils.WriteJSON(w, http.StatusOK, history) } -func (s *Server) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } if vars == nil { @@ -396,7 +397,7 @@ func (s *Server) postImagesTag(ctx context.Context, w http.ResponseWriter, r *ht repo := r.Form.Get("repo") tag := r.Form.Get("tag") - force := boolValue(r, "force") + force := httputils.BoolValue(r, "force") name := vars["name"] if err := s.daemon.Repositories().Tag(repo, tag, name, force); err != nil { return err @@ -406,8 +407,8 @@ func (s *Server) postImagesTag(ctx context.Context, w http.ResponseWriter, r *ht return nil } -func (s *Server) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } var ( @@ -433,5 +434,5 @@ func (s *Server) getImagesSearch(ctx context.Context, w http.ResponseWriter, r * if err != nil { return err } - return writeJSON(w, http.StatusOK, query.Results) + return httputils.WriteJSON(w, http.StatusOK, query.Results) } diff --git a/api/server/daemon.go b/api/server/router/local/info.go similarity index 89% rename from api/server/daemon.go rename to api/server/router/local/info.go index 8582f56852..8b43b09c62 100644 --- a/api/server/daemon.go +++ b/api/server/router/local/info.go @@ -1,4 +1,4 @@ -package server +package local import ( "encoding/json" @@ -10,6 +10,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api" + "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/pkg/ioutils" @@ -20,7 +21,7 @@ import ( "golang.org/x/net/context" ) -func (s *Server) getVersion(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) getVersion(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { v := &types.Version{ Version: dockerversion.VERSION, APIVersion: api.Version, @@ -31,7 +32,7 @@ func (s *Server) getVersion(ctx context.Context, w http.ResponseWriter, r *http. BuildTime: dockerversion.BUILDTIME, } - version := versionFromContext(ctx) + version := httputils.VersionFromContext(ctx) if version.GreaterThanOrEqualTo("1.19") { v.Experimental = utils.ExperimentalBuild() @@ -41,20 +42,20 @@ func (s *Server) getVersion(ctx context.Context, w http.ResponseWriter, r *http. v.KernelVersion = kernelVersion.String() } - return writeJSON(w, http.StatusOK, v) + return httputils.WriteJSON(w, http.StatusOK, v) } -func (s *Server) getInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) getInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { info, err := s.daemon.SystemInfo() if err != nil { return err } - return writeJSON(w, http.StatusOK, info) + return httputils.WriteJSON(w, http.StatusOK, info) } -func (s *Server) getEvents(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) getEvents(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } var since int64 = -1 diff --git a/api/server/inspect.go b/api/server/router/local/inspect.go similarity index 72% rename from api/server/inspect.go rename to api/server/router/local/inspect.go index 3d7dc85d74..67200b1ff6 100644 --- a/api/server/inspect.go +++ b/api/server/router/local/inspect.go @@ -1,14 +1,15 @@ -package server +package local import ( "fmt" "net/http" + "github.com/docker/docker/api/server/httputils" "golang.org/x/net/context" ) // getContainersByName inspects containers configuration and serializes it as json. -func (s *Server) getContainersByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *router) getContainersByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -16,7 +17,7 @@ func (s *Server) getContainersByName(ctx context.Context, w http.ResponseWriter, var json interface{} var err error - version := versionFromContext(ctx) + version := httputils.VersionFromContext(ctx) switch { case version.LessThan("1.20"): @@ -31,5 +32,5 @@ func (s *Server) getContainersByName(ctx context.Context, w http.ResponseWriter, return err } - return writeJSON(w, http.StatusOK, json) + return httputils.WriteJSON(w, http.StatusOK, json) } diff --git a/api/server/router/local/local.go b/api/server/router/local/local.go new file mode 100644 index 0000000000..6cabf868b2 --- /dev/null +++ b/api/server/router/local/local.go @@ -0,0 +1,161 @@ +package local + +import ( + "net/http" + + "golang.org/x/net/context" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/server/httputils" + dkrouter "github.com/docker/docker/api/server/router" + "github.com/docker/docker/daemon" + "github.com/gorilla/mux" +) + +// router is a docker router that talks with the local docker daemon. +type router struct { + daemon *daemon.Daemon + routes []dkrouter.Route +} + +// localRoute defines an individual API route to connect with the docker daemon. +// It implements router.Route. +type localRoute struct { + method string + path string + handler httputils.APIFunc +} + +// Handler returns the APIFunc to let the server wrap it in middlewares +func (l localRoute) Handler() httputils.APIFunc { + return l.handler +} + +// Register adds the filtered handler to the mux. +func (l localRoute) Register(m *mux.Router, handler http.Handler) { + logrus.Debugf("Registering %s, %s", l.method, l.path) + m.Path(dkrouter.VersionMatcher + l.path).Methods(l.method).Handler(handler) + m.Path(l.path).Methods(l.method).Handler(handler) +} + +// NewRoute initialies a new local route for the reouter +func NewRoute(method, path string, handler httputils.APIFunc) dkrouter.Route { + return localRoute{method, path, handler} +} + +// NewGetRoute initializes a new route with the http method GET. +func NewGetRoute(path string, handler httputils.APIFunc) dkrouter.Route { + return NewRoute("GET", path, handler) +} + +// NewPostRoute initializes a new route with the http method POST. +func NewPostRoute(path string, handler httputils.APIFunc) dkrouter.Route { + return NewRoute("POST", path, handler) +} + +// NewPutRoute initializes a new route with the http method PUT. +func NewPutRoute(path string, handler httputils.APIFunc) dkrouter.Route { + return NewRoute("PUT", path, handler) +} + +// NewDeleteRoute initializes a new route with the http method DELETE. +func NewDeleteRoute(path string, handler httputils.APIFunc) dkrouter.Route { + return NewRoute("DELETE", path, handler) +} + +// NewOptionsRoute initializes a new route with the http method OPTIONS +func NewOptionsRoute(path string, handler httputils.APIFunc) dkrouter.Route { + return NewRoute("OPTIONS", path, handler) +} + +// NewHeadRoute initializes a new route with the http method HEAD. +func NewHeadRoute(path string, handler httputils.APIFunc) dkrouter.Route { + return NewRoute("HEAD", path, handler) +} + +// NewRouter initializes a local router with a new daemon. +func NewRouter(daemon *daemon.Daemon) dkrouter.Router { + r := &router{ + daemon: daemon, + } + r.initRoutes() + return r +} + +// Routes returns the list of routes registered in the router. +func (r *router) Routes() []dkrouter.Route { + return r.routes +} + +// initRoutes initializes the routes in this router +func (r *router) initRoutes() { + r.routes = []dkrouter.Route{ + // HEAD + NewHeadRoute("/containers/{name:.*}/archive", r.headContainersArchive), + // OPTIONS + NewOptionsRoute("/", optionsHandler), + // GET + NewGetRoute("/_ping", pingHandler), + NewGetRoute("/events", r.getEvents), + NewGetRoute("/info", r.getInfo), + NewGetRoute("/version", r.getVersion), + NewGetRoute("/images/json", r.getImagesJSON), + NewGetRoute("/images/search", r.getImagesSearch), + NewGetRoute("/images/get", r.getImagesGet), + NewGetRoute("/images/{name:.*}/get", r.getImagesGet), + NewGetRoute("/images/{name:.*}/history", r.getImagesHistory), + NewGetRoute("/images/{name:.*}/json", r.getImagesByName), + NewGetRoute("/containers/json", r.getContainersJSON), + NewGetRoute("/containers/{name:.*}/export", r.getContainersExport), + NewGetRoute("/containers/{name:.*}/changes", r.getContainersChanges), + NewGetRoute("/containers/{name:.*}/json", r.getContainersByName), + NewGetRoute("/containers/{name:.*}/top", r.getContainersTop), + NewGetRoute("/containers/{name:.*}/logs", r.getContainersLogs), + NewGetRoute("/containers/{name:.*}/stats", r.getContainersStats), + NewGetRoute("/containers/{name:.*}/attach/ws", r.wsContainersAttach), + NewGetRoute("/exec/{id:.*}/json", r.getExecByID), + NewGetRoute("/containers/{name:.*}/archive", r.getContainersArchive), + NewGetRoute("/volumes", r.getVolumesList), + NewGetRoute("/volumes/{name:.*}", r.getVolumeByName), + // POST + NewPostRoute("/auth", r.postAuth), + NewPostRoute("/commit", r.postCommit), + NewPostRoute("/build", r.postBuild), + NewPostRoute("/images/create", r.postImagesCreate), + NewPostRoute("/images/load", r.postImagesLoad), + NewPostRoute("/images/{name:.*}/push", r.postImagesPush), + NewPostRoute("/images/{name:.*}/tag", r.postImagesTag), + NewPostRoute("/containers/create", r.postContainersCreate), + NewPostRoute("/containers/{name:.*}/kill", r.postContainersKill), + NewPostRoute("/containers/{name:.*}/pause", r.postContainersPause), + NewPostRoute("/containers/{name:.*}/unpause", r.postContainersUnpause), + NewPostRoute("/containers/{name:.*}/restart", r.postContainersRestart), + NewPostRoute("/containers/{name:.*}/start", r.postContainersStart), + NewPostRoute("/containers/{name:.*}/stop", r.postContainersStop), + NewPostRoute("/containers/{name:.*}/wait", r.postContainersWait), + NewPostRoute("/containers/{name:.*}/resize", r.postContainersResize), + NewPostRoute("/containers/{name:.*}/attach", r.postContainersAttach), + NewPostRoute("/containers/{name:.*}/copy", r.postContainersCopy), + NewPostRoute("/containers/{name:.*}/exec", r.postContainerExecCreate), + NewPostRoute("/exec/{name:.*}/start", r.postContainerExecStart), + NewPostRoute("/exec/{name:.*}/resize", r.postContainerExecResize), + NewPostRoute("/containers/{name:.*}/rename", r.postContainerRename), + NewPostRoute("/volumes", r.postVolumesCreate), + // PUT + NewPutRoute("/containers/{name:.*}/archive", r.putContainersArchive), + // DELETE + NewDeleteRoute("/containers/{name:.*}", r.deleteContainers), + NewDeleteRoute("/images/{name:.*}", r.deleteImages), + NewDeleteRoute("/volumes/{name:.*}", r.deleteVolumes), + } +} + +func optionsHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + w.WriteHeader(http.StatusOK) + return nil +} + +func pingHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + _, err := w.Write([]byte{'O', 'K'}) + return err +} diff --git a/api/server/volume.go b/api/server/router/local/volume.go similarity index 55% rename from api/server/volume.go rename to api/server/router/local/volume.go index 19e7d54abf..614bec7448 100644 --- a/api/server/volume.go +++ b/api/server/router/local/volume.go @@ -1,15 +1,16 @@ -package server +package local import ( "encoding/json" "net/http" + "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/types" "golang.org/x/net/context" ) -func (s *Server) getVolumesList(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) getVolumesList(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } @@ -17,11 +18,11 @@ func (s *Server) getVolumesList(ctx context.Context, w http.ResponseWriter, r *h if err != nil { return err } - return writeJSON(w, http.StatusOK, &types.VolumesListResponse{Volumes: volumes}) + return httputils.WriteJSON(w, http.StatusOK, &types.VolumesListResponse{Volumes: volumes}) } -func (s *Server) getVolumeByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) getVolumeByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } @@ -29,15 +30,15 @@ func (s *Server) getVolumeByName(ctx context.Context, w http.ResponseWriter, r * if err != nil { return err } - return writeJSON(w, http.StatusOK, v) + return httputils.WriteJSON(w, http.StatusOK, v) } -func (s *Server) postVolumesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) postVolumesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } - if err := checkForJSON(r); err != nil { + if err := httputils.CheckForJSON(r); err != nil { return err } @@ -50,11 +51,11 @@ func (s *Server) postVolumesCreate(ctx context.Context, w http.ResponseWriter, r if err != nil { return err } - return writeJSON(w, http.StatusCreated, volume) + return httputils.WriteJSON(w, http.StatusCreated, volume) } -func (s *Server) deleteVolumes(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { +func (s *router) deleteVolumes(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := httputils.ParseForm(r); err != nil { return err } if err := s.daemon.VolumeRm(vars["name"]); err != nil { diff --git a/api/server/router/network/network.go b/api/server/router/network/network.go new file mode 100644 index 0000000000..190198564b --- /dev/null +++ b/api/server/router/network/network.go @@ -0,0 +1,26 @@ +package network + +import ( + "github.com/docker/docker/api/server/httputils" + "github.com/docker/docker/api/server/router" +) + +// networkRouter is a router to talk with the network controller +type networkRouter struct { + routes []router.Route +} + +// Routes returns the available routes to the network controller +func (n networkRouter) Routes() []router.Route { + return n.routes +} + +type networkRoute struct { + path string + handler httputils.APIFunc +} + +// Handler returns the APIFunc to let the server wrap it in middlewares +func (l networkRoute) Handler() httputils.APIFunc { + return l.handler +} diff --git a/api/server/router/network/network_experimental.go b/api/server/router/network/network_experimental.go new file mode 100644 index 0000000000..e5decfde10 --- /dev/null +++ b/api/server/router/network/network_experimental.go @@ -0,0 +1,51 @@ +// +build experimental + +package network + +import ( + "net/http" + + "golang.org/x/net/context" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/server/router" + "github.com/docker/docker/daemon" + "github.com/docker/libnetwork/api" + "github.com/gorilla/mux" +) + +var httpMethods = []string{"GET", "POST", "PUT", "DELETE"} + +// NewRouter initializes a new network router +func NewRouter(d *daemon.Daemon) router.Router { + c := d.NetworkController() + if c == nil { + return networkRouter{} + } + + var routes []router.Route + netHandler := api.NewHTTPHandler(c) + + // TODO: libnetwork should stop hijacking request/response. + // It should define API functions to add normally to the router. + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + netHandler(w, r) + return nil + } + + for _, path := range []string{"/networks", "/services", "/sandboxes"} { + routes = append(routes, networkRoute{path, handler}) + } + + return networkRouter{routes} +} + +// Register adds the filtered handler to the mux. +func (n networkRoute) Register(m *mux.Router, handler http.Handler) { + logrus.Debugf("Registering %s, %v", n.path, httpMethods) + subrouter := m.PathPrefix(router.VersionMatcher + n.path).Subrouter() + subrouter.Methods(httpMethods...).Handler(handler) + + subrouter = m.PathPrefix(n.path).Subrouter() + subrouter.Methods(httpMethods...).Handler(handler) +} diff --git a/api/server/router/network/network_stable.go b/api/server/router/network/network_stable.go new file mode 100644 index 0000000000..9a9b38b4a4 --- /dev/null +++ b/api/server/router/network/network_stable.go @@ -0,0 +1,20 @@ +// +build !experimental + +package network + +import ( + "net/http" + + "github.com/docker/docker/api/server/router" + "github.com/docker/docker/daemon" + "github.com/gorilla/mux" +) + +// NewRouter initializes a new network router +func NewRouter(d *daemon.Daemon) router.Router { + return networkRouter{} +} + +// Register adds the filtered handler to the mux. +func (n networkRoute) Register(m *mux.Router, handler http.Handler) { +} diff --git a/api/server/router/router.go b/api/server/router/router.go new file mode 100644 index 0000000000..85e81d2d62 --- /dev/null +++ b/api/server/router/router.go @@ -0,0 +1,25 @@ +package router + +import ( + "net/http" + + "github.com/docker/docker/api/server/httputils" + "github.com/gorilla/mux" +) + +// VersionMatcher defines a variable matcher to be parsed by the router +// when a request is about to be served. +const VersionMatcher = "/v{version:[0-9.]+}" + +// Router defines an interface to specify a group of routes to add the the docker server. +type Router interface { + Routes() []Route +} + +// Route defines an individual API route in the docker server. +type Route interface { + // Register adds the handler route to the docker mux. + Register(*mux.Router, http.Handler) + // Handler returns the raw function to create the http handler. + Handler() httputils.APIFunc +} diff --git a/api/server/server.go b/api/server/server.go index 84dfff65c5..4648de24aa 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -2,17 +2,17 @@ package server import ( "crypto/tls" - "encoding/json" "fmt" - "io" "net" "net/http" "os" "strings" "github.com/Sirupsen/logrus" - "github.com/docker/distribution/registry/api/errcode" - "github.com/docker/docker/api" + "github.com/docker/docker/api/server/httputils" + "github.com/docker/docker/api/server/router" + "github.com/docker/docker/api/server/router/local" + "github.com/docker/docker/api/server/router/network" "github.com/docker/docker/daemon" "github.com/docker/docker/pkg/sockets" "github.com/docker/docker/utils" @@ -32,21 +32,18 @@ type Config struct { // Server contains instance details for the server type Server struct { - daemon *daemon.Daemon cfg *Config - router *mux.Router start chan struct{} servers []serverCloser + routers []router.Router } // New returns a new instance of the server based on the specified configuration. func New(cfg *Config) *Server { - srv := &Server{ + return &Server{ cfg: cfg, start: make(chan struct{}), } - srv.router = createRouter(srv) - return srv } // Close closes servers and thus stop receiving requests @@ -118,152 +115,6 @@ 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) @@ -271,11 +122,6 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string 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 /!\\") @@ -289,10 +135,10 @@ func (s *Server) initTCPSocket(addr string) (l net.Listener, err error) { return } -func (s *Server) makeHTTPHandler(localMethod string, localRoute string, localHandler HTTPAPIFunc) http.HandlerFunc { +func (s *Server) makeHTTPHandler(handler httputils.APIFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - // log the handler generation - logrus.Debugf("Calling %s %s", localMethod, localRoute) + // log the handler call + logrus.Debugf("Calling %s %s", r.Method, r.URL.Path) // Define the context that we'll pass around to share info // like the docker-request-id. @@ -302,108 +148,53 @@ func (s *Server) makeHTTPHandler(localMethod string, localRoute string, localHan // immediate function being called should still be passed // as 'args' on the function call. ctx := context.Background() - handlerFunc := s.handleWithGlobalMiddlewares(localHandler) + handlerFunc := s.handleWithGlobalMiddlewares(handler) 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) + logrus.Errorf("Handler for %s %s returned error: %s", r.Method, r.URL.Path, utils.GetErrorMessage(err)) + httputils.WriteError(w, err) } } } -// createRouter initializes the main router the server uses. +// InitRouters initializes a list of routers for the server. +func (s *Server) InitRouters(d *daemon.Daemon) { + s.addRouter(local.NewRouter(d)) + s.addRouter(network.NewRouter(d)) +} + +// addRouter adds a new router to the server. +func (s *Server) addRouter(r router.Router) { + s.routers = append(s.routers, r) +} + +// CreateMux 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() +func (s *Server) CreateMux() *mux.Router { + m := 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, - }, + profilerSetup(m, "/debug/") } - 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) - } + logrus.Debugf("Registering routers") + for _, router := range s.routers { + for _, r := range router.Routes() { + f := s.makeHTTPHandler(r.Handler()) + r.Register(m, f) } } - return r + return m +} + +// AcceptConnections allows clients to connect to the API server. +// Referenced Daemon is notified about this server, and waits for the +// daemon acknowledgement before the incoming connections are accepted. +func (s *Server) AcceptConnections() { + // close the lock so the listeners start accepting connections + select { + case <-s.start: + default: + close(s.start) + } } diff --git a/api/server/server_experimental_unix.go b/api/server/server_experimental_unix.go deleted file mode 100644 index b02e849a7f..0000000000 --- a/api/server/server_experimental_unix.go +++ /dev/null @@ -1,22 +0,0 @@ -// +build experimental,!windows - -package server - -func (s *Server) registerSubRouter() { - httpHandler := s.daemon.NetworkAPIRouter() - - subrouter := s.router.PathPrefix("/v{version:[0-9.]+}/networks").Subrouter() - subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler) - subrouter = s.router.PathPrefix("/networks").Subrouter() - subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler) - - subrouter = s.router.PathPrefix("/v{version:[0-9.]+}/services").Subrouter() - subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler) - subrouter = s.router.PathPrefix("/services").Subrouter() - subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler) - - subrouter = s.router.PathPrefix("/v{version:[0-9.]+}/sandboxes").Subrouter() - subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler) - subrouter = s.router.PathPrefix("/sandboxes").Subrouter() - subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler) -} diff --git a/api/server/server_stub.go b/api/server/server_stub.go deleted file mode 100644 index cae2849383..0000000000 --- a/api/server/server_stub.go +++ /dev/null @@ -1,6 +0,0 @@ -// +build !experimental windows - -package server - -func (s *Server) registerSubRouter() { -} diff --git a/api/server/server_test.go b/api/server/server_test.go index 3dcd81f7aa..f3256c3160 100644 --- a/api/server/server_test.go +++ b/api/server/server_test.go @@ -5,6 +5,8 @@ import ( "net/http/httptest" "testing" + "github.com/docker/docker/api/server/httputils" + "golang.org/x/net/context" ) @@ -19,7 +21,7 @@ func TestMiddlewares(t *testing.T) { ctx := context.Background() localHandler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if versionFromContext(ctx) == "" { + if httputils.VersionFromContext(ctx) == "" { t.Fatalf("Expected version, got empty string") } return nil diff --git a/api/server/server_unix.go b/api/server/server_unix.go index 2c1621f9b3..31eebb37d4 100644 --- a/api/server/server_unix.go +++ b/api/server/server_unix.go @@ -8,12 +8,10 @@ import ( "net/http" "strconv" - "github.com/docker/docker/daemon" "github.com/docker/docker/pkg/sockets" "github.com/docker/libnetwork/portallocator" systemdActivation "github.com/coreos/go-systemd/activation" - systemdDaemon "github.com/coreos/go-systemd/daemon" ) // newServer sets up the required serverClosers and does protocol specific checking. @@ -52,7 +50,7 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) { res = append(res, &HTTPServer{ &http.Server{ Addr: addr, - Handler: s.router, + Handler: s.CreateMux(), }, l, }) @@ -60,22 +58,6 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) { return res, nil } -// AcceptConnections allows clients to connect to the API server. -// Referenced Daemon is notified about this server, and waits for the -// daemon acknowledgement before the incoming connections are accepted. -func (s *Server) AcceptConnections(d *daemon.Daemon) { - // Tell the init daemon we are accepting requests - s.daemon = d - s.registerSubRouter() - go systemdDaemon.SdNotify("READY=1") - // close the lock so the listeners start accepting connections - select { - case <-s.start: - default: - close(s.start) - } -} - func allocateDaemonPort(addr string) error { host, port, err := net.SplitHostPort(addr) if err != nil { diff --git a/api/server/server_windows.go b/api/server/server_windows.go index 8763ba04a8..3e183bc430 100644 --- a/api/server/server_windows.go +++ b/api/server/server_windows.go @@ -6,8 +6,6 @@ import ( "errors" "net" "net/http" - - "github.com/docker/docker/daemon" ) // NewServer sets up the required Server and does protocol specific checking. @@ -32,7 +30,7 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) { res = append(res, &HTTPServer{ &http.Server{ Addr: addr, - Handler: s.router, + Handler: s.CreateMux(), }, l, }) @@ -41,18 +39,6 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) { } -// AcceptConnections allows router to start listening for the incoming requests. -func (s *Server) AcceptConnections(d *daemon.Daemon) { - s.daemon = d - s.registerSubRouter() - // close the lock so the listeners start accepting connections - select { - case <-s.start: - default: - close(s.start) - } -} - func allocateDaemonPort(addr string) error { return nil } diff --git a/daemon/daemon.go b/daemon/daemon.go index 78754cb371..268a5ce00c 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1131,6 +1131,11 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig, return verifyPlatformContainerSettings(daemon, hostConfig, config) } +// NetworkController exposes the libnetwork interface to manage networks. +func (daemon *Daemon) NetworkController() libnetwork.NetworkController { + return daemon.netController +} + func configureVolumes(config *Config) (*store.VolumeStore, error) { volumesDriver, err := local.New(config.Root) if err != nil { diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index 72add8546f..1d82089941 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -5,7 +5,6 @@ package daemon import ( "fmt" "net" - "net/http" "os" "path/filepath" "strings" @@ -22,7 +21,6 @@ import ( "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" "github.com/docker/libnetwork" - nwapi "github.com/docker/libnetwork/api" nwconfig "github.com/docker/libnetwork/config" "github.com/docker/libnetwork/netlabel" "github.com/docker/libnetwork/options" @@ -489,12 +487,6 @@ func setupInitLayer(initLayer string) error { return nil } -// NetworkAPIRouter implements a feature for server-experimental, -// directly calling into libnetwork. -func (daemon *Daemon) NetworkAPIRouter() func(w http.ResponseWriter, req *http.Request) { - return nwapi.NewHTTPHandler(daemon.netController) -} - // registerLinks writes the links to a file. func (daemon *Daemon) registerLinks(container *Container, hostConfig *runconfig.HostConfig) error { if hostConfig == nil || hostConfig.Links == nil { diff --git a/docker/daemon.go b/docker/daemon.go index bd31447c64..422ba4b3ee 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -224,21 +224,6 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error { serverConfig.TLSConfig = tlsConfig } - api := apiserver.New(serverConfig) - - // The serve API routine never exits unless an error occurs - // We need to start it as a goroutine and wait on it so - // daemon doesn't exit - serveAPIWait := make(chan error) - go func() { - if err := api.ServeAPI(commonFlags.Hosts); err != nil { - logrus.Errorf("ServeAPI error: %v", err) - serveAPIWait <- err - return - } - serveAPIWait <- nil - }() - if err := migrateKey(); err != nil { logrus.Fatal(err) } @@ -264,6 +249,22 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error { "graphdriver": d.GraphDriver().String(), }).Info("Docker daemon") + api := apiserver.New(serverConfig) + api.InitRouters(d) + + // The serve API routine never exits unless an error occurs + // We need to start it as a goroutine and wait on it so + // daemon doesn't exit + serveAPIWait := make(chan error) + go func() { + if err := api.ServeAPI(commonFlags.Hosts); err != nil { + logrus.Errorf("ServeAPI error: %v", err) + serveAPIWait <- err + return + } + serveAPIWait <- nil + }() + signal.Trap(func() { api.Close() <-serveAPIWait @@ -277,7 +278,8 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error { // after the daemon is done setting up we can tell the api to start // accepting connections with specified daemon - api.AcceptConnections(d) + notifySystem() + api.AcceptConnections() // Daemon is fully initialized and handling API traffic // Wait for serve API to complete diff --git a/docker/daemon_freebsd.go b/docker/daemon_freebsd.go new file mode 100644 index 0000000000..013f0e910e --- /dev/null +++ b/docker/daemon_freebsd.go @@ -0,0 +1,7 @@ +// +build daemon + +package docker + +// notifySystem sends a message to the host when the server is ready to be used +func notifySystem() { +} diff --git a/docker/daemon_linux.go b/docker/daemon_linux.go index fd41fe74f8..4f4e172c6e 100644 --- a/docker/daemon_linux.go +++ b/docker/daemon_linux.go @@ -3,5 +3,12 @@ package main import ( + systemdDaemon "github.com/coreos/go-systemd/daemon" _ "github.com/docker/docker/daemon/execdriver/lxc" ) + +// notifySystem sends a message to the host when the server is ready to be used +func notifySystem() { + // Tell the init daemon we are accepting requests + go systemdDaemon.SdNotify("READY=1") +} diff --git a/docker/daemon_none.go b/docker/daemon_none.go index c829cc1ca2..995f6a5b6d 100644 --- a/docker/daemon_none.go +++ b/docker/daemon_none.go @@ -10,3 +10,7 @@ var daemonCli cli.Handler // TODO: remove once `-d` is retired func handleGlobalDaemonFlag() {} + +// notifySystem sends a message to the host when the server is ready to be used +func notifySystem() { +} diff --git a/docker/daemon_windows.go b/docker/daemon_windows.go index 6e139b1948..a9301529c3 100644 --- a/docker/daemon_windows.go +++ b/docker/daemon_windows.go @@ -27,3 +27,7 @@ func setDefaultUmask() error { func getDaemonConfDir() string { return os.Getenv("PROGRAMDATA") + `\docker\config` } + +// notifySystem sends a message to the host when the server is ready to be used +func notifySystem() { +}