Bladeren bron

Make server middleware standalone functions.

Removing direct dependencies from the server configuration.

Signed-off-by: David Calavera <david.calavera@gmail.com>
David Calavera 9 jaren geleden
bovenliggende
commit
1ba44a832f

+ 16 - 170
api/server/middleware.go

@@ -1,195 +1,41 @@
 package server
 
 import (
-	"bufio"
-	"encoding/json"
-	"io"
-	"net/http"
-	"runtime"
-	"strings"
-
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/api/server/httputils"
+	"github.com/docker/docker/api/server/middleware"
 	"github.com/docker/docker/dockerversion"
-	"github.com/docker/docker/errors"
 	"github.com/docker/docker/pkg/authorization"
-	"github.com/docker/docker/pkg/ioutils"
-	"github.com/docker/docker/pkg/version"
-	"golang.org/x/net/context"
 )
 
-// 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 httputils.APIFunc) httputils.APIFunc
-
-// debugRequestMiddleware dumps the request to logger
-func debugRequestMiddleware(handler httputils.APIFunc) httputils.APIFunc {
-	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-		logrus.Debugf("%s %s", r.Method, r.RequestURI)
-
-		if r.Method != "POST" {
-			return handler(ctx, w, r, vars)
-		}
-		if err := httputils.CheckForJSON(r); err != nil {
-			return handler(ctx, w, r, vars)
-		}
-		maxBodySize := 4096 // 4KB
-		if r.ContentLength > int64(maxBodySize) {
-			return handler(ctx, w, r, vars)
-		}
-
-		body := r.Body
-		bufReader := bufio.NewReaderSize(body, maxBodySize)
-		r.Body = ioutils.NewReadCloserWrapper(bufReader, func() error { return body.Close() })
-
-		b, err := bufReader.Peek(maxBodySize)
-		if err != io.EOF {
-			// either there was an error reading, or the buffer is full (in which case the request is too large)
-			return handler(ctx, w, r, vars)
-		}
-
-		var postForm map[string]interface{}
-		if err := json.Unmarshal(b, &postForm); err == nil {
-			if _, exists := postForm["password"]; exists {
-				postForm["password"] = "*****"
-			}
-			formStr, errMarshal := json.Marshal(postForm)
-			if errMarshal == nil {
-				logrus.Debugf("form data: %s", string(formStr))
-			} else {
-				logrus.Debugf("form data: %q", postForm)
-			}
-		}
-
-		return handler(ctx, w, r, vars)
-	}
-}
-
-// authorizationMiddleware perform authorization on the request.
-func (s *Server) authorizationMiddleware(handler httputils.APIFunc) httputils.APIFunc {
-	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-		// FIXME: fill when authN gets in
-		// User and UserAuthNMethod are taken from AuthN plugins
-		// Currently tracked in https://github.com/docker/docker/pull/13994
-		user := ""
-		userAuthNMethod := ""
-		authCtx := authorization.NewCtx(s.authZPlugins, user, userAuthNMethod, r.Method, r.RequestURI)
-
-		if err := authCtx.AuthZRequest(w, r); err != nil {
-			logrus.Errorf("AuthZRequest for %s %s returned error: %s", r.Method, r.RequestURI, err)
-			return err
-		}
-
-		rw := authorization.NewResponseModifier(w)
-
-		if err := handler(ctx, rw, r, vars); err != nil {
-			logrus.Errorf("Handler for %s %s returned error: %s", r.Method, r.RequestURI, err)
-			return err
-		}
-
-		if err := authCtx.AuthZResponse(rw, r); err != nil {
-			logrus.Errorf("AuthZResponse for %s %s returned error: %s", r.Method, r.RequestURI, err)
-			return err
-		}
-		return nil
-	}
-}
-
-// userAgentMiddleware checks the User-Agent header looking for a valid docker client spec.
-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)
-
-			userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
-
-			// v1.20 onwards includes the GOOS of the client after the version
-			// such as Docker/1.7.0 (linux)
-			if len(userAgent) == 2 && strings.Contains(userAgent[1], " ") {
-				userAgent[1] = strings.Split(userAgent[1], " ")[0]
-			}
-
-			if len(userAgent) == 2 && !dockerVersion.Equal(version.Version(userAgent[1])) {
-				logrus.Debugf("Client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion)
-			}
-		}
-		return handler(ctx, w, r, vars)
-	}
-}
-
-// corsMiddleware sets the CORS header expectations in the server.
-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
-		corsHeaders := s.cfg.CorsHeaders
-		if corsHeaders == "" && s.cfg.EnableCors {
-			corsHeaders = "*"
-		}
-
-		if corsHeaders != "" {
-			writeCorsHeaders(w, r, corsHeaders)
-		}
-		return handler(ctx, w, r, vars)
-	}
-}
-
-// versionMiddleware checks the api version requirements before passing the request to the server handler.
-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 == "" {
-			apiVersion = api.DefaultVersion
-		}
-
-		if apiVersion.GreaterThan(api.DefaultVersion) {
-			return errors.ErrorCodeNewerClientVersion.WithArgs(apiVersion, api.DefaultVersion)
-		}
-		if apiVersion.LessThan(api.MinVersion) {
-			return errors.ErrorCodeOldClientVersion.WithArgs(apiVersion, api.MinVersion)
-		}
-
-		w.Header().Set("Server", "Docker/"+dockerversion.Version+" ("+runtime.GOOS+")")
-		ctx = context.WithValue(ctx, httputils.APIVersionKey, apiVersion)
-		return handler(ctx, w, r, vars)
-	}
-}
-
 // handleWithGlobalMiddlwares wraps the handler function for a request with
 // the server's global middlewares. The order of the middlewares is backwards,
 // meaning that the first in the list will be evaluated last.
-//
-// Example: handleWithGlobalMiddlewares(s.getContainersName)
-//
-//	s.loggingMiddleware(
-//		s.userAgentMiddleware(
-//			s.corsMiddleware(
-//				versionMiddleware(s.getContainersName)
-//			)
-//		)
-//	)
-// )
 func (s *Server) handleWithGlobalMiddlewares(handler httputils.APIFunc) httputils.APIFunc {
-	middlewares := []middleware{
-		versionMiddleware,
-		s.corsMiddleware,
-		s.userAgentMiddleware,
+	next := handler
+
+	handleVersion := middleware.NewVersionMiddleware(dockerversion.Version, api.DefaultVersion, api.MinVersion)
+	next = handleVersion(next)
+
+	if s.cfg.EnableCors {
+		handleCORS := middleware.NewCORSMiddleware(s.cfg.CorsHeaders)
+		next = handleCORS(next)
 	}
 
+	handleUserAgent := middleware.NewUserAgentMiddleware(s.cfg.Version)
+	next = handleUserAgent(next)
+
 	// Only want this on debug level
 	if s.cfg.Logging && logrus.GetLevel() == logrus.DebugLevel {
-		middlewares = append(middlewares, debugRequestMiddleware)
+		next = middleware.DebugRequestMiddleware(next)
 	}
 
 	if len(s.cfg.AuthorizationPluginNames) > 0 {
 		s.authZPlugins = authorization.NewPlugins(s.cfg.AuthorizationPluginNames)
-		middlewares = append(middlewares, s.authorizationMiddleware)
+		handleAuthorization := middleware.NewAuthorizationMiddleware(s.authZPlugins)
+		next = handleAuthorization(next)
 	}
 
-	h := handler
-	for _, m := range middlewares {
-		h = m(h)
-	}
-	return h
+	return next
 }

+ 42 - 0
api/server/middleware/authorization.go

@@ -0,0 +1,42 @@
+package middleware
+
+import (
+	"net/http"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/api/server/httputils"
+	"github.com/docker/docker/pkg/authorization"
+	"golang.org/x/net/context"
+)
+
+// NewAuthorizationMiddleware creates a new Authorization middleware.
+func NewAuthorizationMiddleware(plugins []authorization.Plugin) Middleware {
+	return func(handler httputils.APIFunc) httputils.APIFunc {
+		return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+			// FIXME: fill when authN gets in
+			// User and UserAuthNMethod are taken from AuthN plugins
+			// Currently tracked in https://github.com/docker/docker/pull/13994
+			user := ""
+			userAuthNMethod := ""
+			authCtx := authorization.NewCtx(plugins, user, userAuthNMethod, r.Method, r.RequestURI)
+
+			if err := authCtx.AuthZRequest(w, r); err != nil {
+				logrus.Errorf("AuthZRequest for %s %s returned error: %s", r.Method, r.RequestURI, err)
+				return err
+			}
+
+			rw := authorization.NewResponseModifier(w)
+
+			if err := handler(ctx, rw, r, vars); err != nil {
+				logrus.Errorf("Handler for %s %s returned error: %s", r.Method, r.RequestURI, err)
+				return err
+			}
+
+			if err := authCtx.AuthZResponse(rw, r); err != nil {
+				logrus.Errorf("AuthZResponse for %s %s returned error: %s", r.Method, r.RequestURI, err)
+				return err
+			}
+			return nil
+		}
+	}
+}

+ 33 - 0
api/server/middleware/cors.go

@@ -0,0 +1,33 @@
+package middleware
+
+import (
+	"net/http"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/api/server/httputils"
+	"golang.org/x/net/context"
+)
+
+// NewCORSMiddleware creates a new CORS middleware.
+func NewCORSMiddleware(defaultHeaders string) Middleware {
+	return func(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
+			corsHeaders := defaultHeaders
+			if corsHeaders == "" {
+				corsHeaders = "*"
+			}
+
+			writeCorsHeaders(w, r, corsHeaders)
+			return handler(ctx, w, r, vars)
+		}
+	}
+}
+
+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")
+}

+ 56 - 0
api/server/middleware/debug.go

@@ -0,0 +1,56 @@
+package middleware
+
+import (
+	"bufio"
+	"encoding/json"
+	"io"
+	"net/http"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/api/server/httputils"
+	"github.com/docker/docker/pkg/ioutils"
+	"golang.org/x/net/context"
+)
+
+// DebugRequestMiddleware dumps the request to logger
+func DebugRequestMiddleware(handler httputils.APIFunc) httputils.APIFunc {
+	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+		logrus.Debugf("%s %s", r.Method, r.RequestURI)
+
+		if r.Method != "POST" {
+			return handler(ctx, w, r, vars)
+		}
+		if err := httputils.CheckForJSON(r); err != nil {
+			return handler(ctx, w, r, vars)
+		}
+		maxBodySize := 4096 // 4KB
+		if r.ContentLength > int64(maxBodySize) {
+			return handler(ctx, w, r, vars)
+		}
+
+		body := r.Body
+		bufReader := bufio.NewReaderSize(body, maxBodySize)
+		r.Body = ioutils.NewReadCloserWrapper(bufReader, func() error { return body.Close() })
+
+		b, err := bufReader.Peek(maxBodySize)
+		if err != io.EOF {
+			// either there was an error reading, or the buffer is full (in which case the request is too large)
+			return handler(ctx, w, r, vars)
+		}
+
+		var postForm map[string]interface{}
+		if err := json.Unmarshal(b, &postForm); err == nil {
+			if _, exists := postForm["password"]; exists {
+				postForm["password"] = "*****"
+			}
+			formStr, errMarshal := json.Marshal(postForm)
+			if errMarshal == nil {
+				logrus.Debugf("form data: %s", string(formStr))
+			} else {
+				logrus.Debugf("form data: %q", postForm)
+			}
+		}
+
+		return handler(ctx, w, r, vars)
+	}
+}

+ 7 - 0
api/server/middleware/middleware.go

@@ -0,0 +1,7 @@
+package middleware
+
+import "github.com/docker/docker/api/server/httputils"
+
+// 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 httputils.APIFunc) httputils.APIFunc

+ 35 - 0
api/server/middleware/user_agent.go

@@ -0,0 +1,35 @@
+package middleware
+
+import (
+	"net/http"
+	"strings"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/api/server/httputils"
+	"github.com/docker/docker/pkg/version"
+	"golang.org/x/net/context"
+)
+
+// NewUserAgentMiddleware creates a new UserAgent middleware.
+func NewUserAgentMiddleware(versionCheck string) Middleware {
+	serverVersion := version.Version(versionCheck)
+
+	return func(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/") {
+				userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
+
+				// v1.20 onwards includes the GOOS of the client after the version
+				// such as Docker/1.7.0 (linux)
+				if len(userAgent) == 2 && strings.Contains(userAgent[1], " ") {
+					userAgent[1] = strings.Split(userAgent[1], " ")[0]
+				}
+
+				if len(userAgent) == 2 && !serverVersion.Equal(version.Version(userAgent[1])) {
+					logrus.Debugf("Client and server don't have the same version (client: %s, server: %s)", userAgent[1], serverVersion)
+				}
+			}
+			return handler(ctx, w, r, vars)
+		}
+	}
+}

+ 38 - 0
api/server/middleware/version.go

@@ -0,0 +1,38 @@
+package middleware
+
+import (
+	"fmt"
+	"net/http"
+	"runtime"
+
+	"github.com/docker/docker/api/server/httputils"
+	"github.com/docker/docker/errors"
+	"github.com/docker/docker/pkg/version"
+	"golang.org/x/net/context"
+)
+
+// NewVersionMiddleware creates a new Version middleware.
+func NewVersionMiddleware(versionCheck string, defaultVersion, minVersion version.Version) Middleware {
+	serverVersion := version.Version(versionCheck)
+
+	return func(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 == "" {
+				apiVersion = defaultVersion
+			}
+
+			if apiVersion.GreaterThan(defaultVersion) {
+				return errors.ErrorCodeNewerClientVersion.WithArgs(apiVersion, defaultVersion)
+			}
+			if apiVersion.LessThan(minVersion) {
+				return errors.ErrorCodeOldClientVersion.WithArgs(apiVersion, minVersion)
+			}
+
+			header := fmt.Sprintf("Docker/%s (%s)", serverVersion, runtime.GOOS)
+			w.Header().Set("Server", header)
+			ctx = context.WithValue(ctx, httputils.APIVersionKey, apiVersion)
+			return handler(ctx, w, r, vars)
+		}
+	}
+}

+ 14 - 7
api/server/middleware_test.go → api/server/middleware/version_test.go

@@ -1,13 +1,13 @@
-package server
+package middleware
 
 import (
 	"net/http"
 	"net/http/httptest"
+	"strings"
 	"testing"
 
-	"github.com/docker/distribution/registry/api/errcode"
 	"github.com/docker/docker/api/server/httputils"
-	"github.com/docker/docker/errors"
+	"github.com/docker/docker/pkg/version"
 	"golang.org/x/net/context"
 )
 
@@ -19,7 +19,10 @@ func TestVersionMiddleware(t *testing.T) {
 		return nil
 	}
 
-	h := versionMiddleware(handler)
+	defaultVersion := version.Version("1.10.0")
+	minVersion := version.Version("1.2.0")
+	m := NewVersionMiddleware(defaultVersion.String(), defaultVersion, minVersion)
+	h := m(handler)
 
 	req, _ := http.NewRequest("GET", "/containers/json", nil)
 	resp := httptest.NewRecorder()
@@ -37,7 +40,10 @@ func TestVersionMiddlewareWithErrors(t *testing.T) {
 		return nil
 	}
 
-	h := versionMiddleware(handler)
+	defaultVersion := version.Version("1.10.0")
+	minVersion := version.Version("1.2.0")
+	m := NewVersionMiddleware(defaultVersion.String(), defaultVersion, minVersion)
+	h := m(handler)
 
 	req, _ := http.NewRequest("GET", "/containers/json", nil)
 	resp := httptest.NewRecorder()
@@ -45,13 +51,14 @@ func TestVersionMiddlewareWithErrors(t *testing.T) {
 
 	vars := map[string]string{"version": "0.1"}
 	err := h(ctx, resp, req, vars)
-	if derr, ok := err.(errcode.Error); !ok || derr.ErrorCode() != errors.ErrorCodeOldClientVersion {
+
+	if !strings.Contains(err.Error(), "client version 0.1 is too old. Minimum supported API version is 1.2.0") {
 		t.Fatalf("Expected ErrorCodeOldClientVersion, got %v", err)
 	}
 
 	vars["version"] = "100000"
 	err = h(ctx, resp, req, vars)
-	if derr, ok := err.(errcode.Error); !ok || derr.ErrorCode() != errors.ErrorCodeNewerClientVersion {
+	if !strings.Contains(err.Error(), "client is newer than server") {
 		t.Fatalf("Expected ErrorCodeNewerClientVersion, got %v", err)
 	}
 }

+ 0 - 7
api/server/server.go

@@ -113,13 +113,6 @@ func (s *HTTPServer) Close() error {
 	return s.l.Close()
 }
 
-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) makeHTTPHandler(handler httputils.APIFunc) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		// log the handler call