mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
ebcb7d6b40
Use strongly typed errors to set HTTP status codes. Error interfaces are defined in the api/errors package and errors returned from controllers are checked against these interfaces. Errors can be wraeped in a pkg/errors.Causer, as long as somewhere in the line of causes one of the interfaces is implemented. The special error interfaces take precedence over Causer, meaning if both Causer and one of the new error interfaces are implemented, the Causer is not traversed. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
206 lines
5.6 KiB
Go
206 lines
5.6 KiB
Go
package server
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/api/server/httputils"
|
|
"github.com/docker/docker/api/server/middleware"
|
|
"github.com/docker/docker/api/server/router"
|
|
"github.com/docker/docker/api/server/router/debug"
|
|
"github.com/docker/docker/dockerversion"
|
|
"github.com/gorilla/mux"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
// 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.]+}"
|
|
|
|
// Config provides the configuration for the API server
|
|
type Config struct {
|
|
Logging bool
|
|
EnableCors bool
|
|
CorsHeaders string
|
|
Version string
|
|
SocketGroup string
|
|
TLSConfig *tls.Config
|
|
}
|
|
|
|
// Server contains instance details for the server
|
|
type Server struct {
|
|
cfg *Config
|
|
servers []*HTTPServer
|
|
routers []router.Router
|
|
routerSwapper *routerSwapper
|
|
middlewares []middleware.Middleware
|
|
}
|
|
|
|
// New returns a new instance of the server based on the specified configuration.
|
|
// It allocates resources which will be needed for ServeAPI(ports, unix-sockets).
|
|
func New(cfg *Config) *Server {
|
|
return &Server{
|
|
cfg: cfg,
|
|
}
|
|
}
|
|
|
|
// UseMiddleware appends a new middleware to the request chain.
|
|
// This needs to be called before the API routes are configured.
|
|
func (s *Server) UseMiddleware(m middleware.Middleware) {
|
|
s.middlewares = append(s.middlewares, m)
|
|
}
|
|
|
|
// Accept sets a listener the server accepts connections into.
|
|
func (s *Server) Accept(addr string, listeners ...net.Listener) {
|
|
for _, listener := range listeners {
|
|
httpServer := &HTTPServer{
|
|
srv: &http.Server{
|
|
Addr: addr,
|
|
},
|
|
l: listener,
|
|
}
|
|
s.servers = append(s.servers, httpServer)
|
|
}
|
|
}
|
|
|
|
// Close closes servers and thus stop receiving requests
|
|
func (s *Server) Close() {
|
|
for _, srv := range s.servers {
|
|
if err := srv.Close(); err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// serveAPI loops through all initialized servers and spawns goroutine
|
|
// with Serve method for each. It sets createMux() as Handler also.
|
|
func (s *Server) serveAPI() error {
|
|
var chErrors = make(chan error, len(s.servers))
|
|
for _, srv := range s.servers {
|
|
srv.srv.Handler = s.routerSwapper
|
|
go func(srv *HTTPServer) {
|
|
var err error
|
|
logrus.Infof("API listen on %s", srv.l.Addr())
|
|
if err = srv.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") {
|
|
err = nil
|
|
}
|
|
chErrors <- err
|
|
}(srv)
|
|
}
|
|
|
|
for range s.servers {
|
|
err := <-chErrors
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// HTTPServer contains an instance of http server and the listener.
|
|
// srv *http.Server, contains configuration to create an http server and a mux router with all api end points.
|
|
// l net.Listener, is a TCP or Socket listener that dispatches incoming request to the router.
|
|
type HTTPServer struct {
|
|
srv *http.Server
|
|
l net.Listener
|
|
}
|
|
|
|
// Serve starts listening for inbound requests.
|
|
func (s *HTTPServer) Serve() error {
|
|
return s.srv.Serve(s.l)
|
|
}
|
|
|
|
// Close closes the HTTPServer from listening for the inbound requests.
|
|
func (s *HTTPServer) Close() error {
|
|
return s.l.Close()
|
|
}
|
|
|
|
func (s *Server) makeHTTPHandler(handler httputils.APIFunc) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
// Define the context that we'll pass around to share info
|
|
// like the docker-request-id.
|
|
//
|
|
// The 'context' will be used for global data that should
|
|
// apply to all requests. Data that is specific to the
|
|
// immediate function being called should still be passed
|
|
// as 'args' on the function call.
|
|
ctx := context.WithValue(context.Background(), dockerversion.UAStringKey, r.Header.Get("User-Agent"))
|
|
handlerFunc := s.handlerWithGlobalMiddlewares(handler)
|
|
|
|
vars := mux.Vars(r)
|
|
if vars == nil {
|
|
vars = make(map[string]string)
|
|
}
|
|
|
|
if err := handlerFunc(ctx, w, r, vars); err != nil {
|
|
statusCode := httputils.GetHTTPErrorStatusCode(err)
|
|
if statusCode >= 500 {
|
|
logrus.Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err)
|
|
}
|
|
httputils.MakeErrorHandler(err)(w, r)
|
|
}
|
|
}
|
|
}
|
|
|
|
// InitRouter initializes the list of routers for the server.
|
|
// This method also enables the Go profiler if enableProfiler is true.
|
|
func (s *Server) InitRouter(routers ...router.Router) {
|
|
s.routers = append(s.routers, routers...)
|
|
|
|
m := s.createMux()
|
|
s.routerSwapper = &routerSwapper{
|
|
router: m,
|
|
}
|
|
}
|
|
|
|
type pageNotFoundError struct{}
|
|
|
|
func (pageNotFoundError) Error() string {
|
|
return "page not found"
|
|
}
|
|
|
|
func (pageNotFoundError) NotFound() {}
|
|
|
|
// createMux initializes the main router the server uses.
|
|
func (s *Server) createMux() *mux.Router {
|
|
m := mux.NewRouter()
|
|
|
|
logrus.Debug("Registering routers")
|
|
for _, apiRouter := range s.routers {
|
|
for _, r := range apiRouter.Routes() {
|
|
f := s.makeHTTPHandler(r.Handler())
|
|
|
|
logrus.Debugf("Registering %s, %s", r.Method(), r.Path())
|
|
m.Path(versionMatcher + r.Path()).Methods(r.Method()).Handler(f)
|
|
m.Path(r.Path()).Methods(r.Method()).Handler(f)
|
|
}
|
|
}
|
|
|
|
debugRouter := debug.NewRouter()
|
|
s.routers = append(s.routers, debugRouter)
|
|
for _, r := range debugRouter.Routes() {
|
|
f := s.makeHTTPHandler(r.Handler())
|
|
m.Path("/debug" + r.Path()).Handler(f)
|
|
}
|
|
|
|
notFoundHandler := httputils.MakeErrorHandler(pageNotFoundError{})
|
|
m.HandleFunc(versionMatcher+"/{path:.*}", notFoundHandler)
|
|
m.NotFoundHandler = notFoundHandler
|
|
|
|
return m
|
|
}
|
|
|
|
// Wait blocks the server goroutine until it exits.
|
|
// It sends an error message if there is any error during
|
|
// the API execution.
|
|
func (s *Server) Wait(waitChan chan error) {
|
|
if err := s.serveAPI(); err != nil {
|
|
logrus.Errorf("ServeAPI error: %v", err)
|
|
waitChan <- err
|
|
return
|
|
}
|
|
waitChan <- nil
|
|
}
|