Selaa lähdekoodia

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 <david.calavera@gmail.com>
David Calavera 10 vuotta sitten
vanhempi
commit
da982cf551

+ 0 - 57
api/server/form.go

@@ -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
-}

+ 63 - 0
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
+}

+ 5 - 5
api/server/form_test.go → 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)
 		}

+ 180 - 0
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)
+}

+ 9 - 23
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)
-}

+ 3 - 2
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

+ 4 - 3
api/server/auth.go → 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,
 	})
 }

+ 64 - 63
api/server/container.go → 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 {

+ 14 - 13
api/server/copy.go → 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)
 }

+ 14 - 13
api/server/exec.go → 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 {

+ 47 - 46
api/server/image.go → 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)
 }

+ 9 - 8
api/server/daemon.go → 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

+ 5 - 4
api/server/inspect.go → 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)
 }

+ 161 - 0
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
+}

+ 14 - 13
api/server/volume.go → 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 {

+ 26 - 0
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
+}

+ 51 - 0
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)
+}

+ 20 - 0
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) {
+}

+ 25 - 0
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
+}

+ 45 - 254
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/")
+		profilerSetup(m, "/debug/")
 	}
-	m := map[string]map[string]HTTPAPIFunc{
-		"HEAD": {
-			"/containers/{name:.*}/archive": s.headContainersArchive,
-		},
-		"GET": {
-			"/_ping":                          s.ping,
-			"/events":                         s.getEvents,
-			"/info":                           s.getInfo,
-			"/version":                        s.getVersion,
-			"/images/json":                    s.getImagesJSON,
-			"/images/search":                  s.getImagesSearch,
-			"/images/get":                     s.getImagesGet,
-			"/images/{name:.*}/get":           s.getImagesGet,
-			"/images/{name:.*}/history":       s.getImagesHistory,
-			"/images/{name:.*}/json":          s.getImagesByName,
-			"/containers/json":                s.getContainersJSON,
-			"/containers/{name:.*}/export":    s.getContainersExport,
-			"/containers/{name:.*}/changes":   s.getContainersChanges,
-			"/containers/{name:.*}/json":      s.getContainersByName,
-			"/containers/{name:.*}/top":       s.getContainersTop,
-			"/containers/{name:.*}/logs":      s.getContainersLogs,
-			"/containers/{name:.*}/stats":     s.getContainersStats,
-			"/containers/{name:.*}/attach/ws": s.wsContainersAttach,
-			"/exec/{id:.*}/json":              s.getExecByID,
-			"/containers/{name:.*}/archive":   s.getContainersArchive,
-			"/volumes":                        s.getVolumesList,
-			"/volumes/{name:.*}":              s.getVolumeByName,
-		},
-		"POST": {
-			"/auth":                         s.postAuth,
-			"/commit":                       s.postCommit,
-			"/build":                        s.postBuild,
-			"/images/create":                s.postImagesCreate,
-			"/images/load":                  s.postImagesLoad,
-			"/images/{name:.*}/push":        s.postImagesPush,
-			"/images/{name:.*}/tag":         s.postImagesTag,
-			"/containers/create":            s.postContainersCreate,
-			"/containers/{name:.*}/kill":    s.postContainersKill,
-			"/containers/{name:.*}/pause":   s.postContainersPause,
-			"/containers/{name:.*}/unpause": s.postContainersUnpause,
-			"/containers/{name:.*}/restart": s.postContainersRestart,
-			"/containers/{name:.*}/start":   s.postContainersStart,
-			"/containers/{name:.*}/stop":    s.postContainersStop,
-			"/containers/{name:.*}/wait":    s.postContainersWait,
-			"/containers/{name:.*}/resize":  s.postContainersResize,
-			"/containers/{name:.*}/attach":  s.postContainersAttach,
-			"/containers/{name:.*}/copy":    s.postContainersCopy,
-			"/containers/{name:.*}/exec":    s.postContainerExecCreate,
-			"/exec/{name:.*}/start":         s.postContainerExecStart,
-			"/exec/{name:.*}/resize":        s.postContainerExecResize,
-			"/containers/{name:.*}/rename":  s.postContainerRename,
-			"/volumes":                      s.postVolumesCreate,
-		},
-		"PUT": {
-			"/containers/{name:.*}/archive": s.putContainersArchive,
-		},
-		"DELETE": {
-			"/containers/{name:.*}": s.deleteContainers,
-			"/images/{name:.*}":     s.deleteImages,
-			"/volumes/{name:.*}":    s.deleteVolumes,
-		},
-		"OPTIONS": {
-			"": s.optionsHandler,
-		},
-	}
-
-	for method, routes := range m {
-		for route, fct := range routes {
-			logrus.Debugf("Registering %s, %s", method, route)
-			// NOTE: scope issue, make sure the variables are local and won't be changed
-			localRoute := route
-			localFct := fct
-			localMethod := method
 
-			// build the handler function
-			f := s.makeHTTPHandler(localMethod, localRoute, localFct)
-
-			// add the new route
-			if localRoute == "" {
-				r.Methods(localMethod).HandlerFunc(f)
-			} else {
-				r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
-				r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
-			}
+	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)
+	}
 }

+ 0 - 22
api/server/server_experimental_unix.go

@@ -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)
-}

+ 0 - 6
api/server/server_stub.go

@@ -1,6 +0,0 @@
-// +build !experimental windows
-
-package server
-
-func (s *Server) registerSubRouter() {
-}

+ 3 - 1
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

+ 1 - 19
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 {

+ 1 - 15
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
 }

+ 5 - 0
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 {

+ 0 - 8
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 {

+ 18 - 16
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

+ 7 - 0
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() {
+}

+ 7 - 0
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")
+}

+ 4 - 0
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() {
+}

+ 4 - 0
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() {
+}