From 4042938556778b057a31431757990ea15447da0e Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Fri, 4 Mar 2016 11:32:23 +0100 Subject: [PATCH] add handler switcher instead of Manners Signed-off-by: Emile Vauge --- .gitignore | 1 + README.md | 3 ++- middlewares/handlerSwitcher.go | 40 ++++++++++++++++++++++++++++++++++ server.go | 39 +++++++++++++++++---------------- 4 files changed, 63 insertions(+), 20 deletions(-) create mode 100644 middlewares/handlerSwitcher.go diff --git a/.gitignore b/.gitignore index 22922934f..190fab662 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ traefik.toml *.test vendor/ static/ +glide.lock \ No newline at end of file diff --git a/README.md b/README.md index fb1f930ea..1f9e1b905 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,14 @@ It supports several backends ([Docker :whale:](https://www.docker.com/), [Mesos/ ## Features +- It's fast - No dependency hell, single binary made with go - Simple json Rest API - Simple TOML file configuration - Multiple backends supported: Docker, Mesos/Marathon, Consul, Etcd, and more to come - Watchers for backends, can listen change in backends to apply a new configuration automatically - Hot-reloading of configuration. No need to restart the process -- Graceful shutdown http connections during hot-reloads +- Graceful shutdown http connections - Circuit breakers on backends - Round Robin, rebalancer load-balancers - Rest Metrics diff --git a/middlewares/handlerSwitcher.go b/middlewares/handlerSwitcher.go new file mode 100644 index 000000000..9865cc0c6 --- /dev/null +++ b/middlewares/handlerSwitcher.go @@ -0,0 +1,40 @@ +package middlewares + +import ( + "github.com/gorilla/mux" + "net/http" + "sync" +) + +// HandlerSwitcher allows hot switching of http.ServeMux +type HandlerSwitcher struct { + handler *mux.Router + handlerLock *sync.Mutex +} + +// NewHandlerSwitcher builds a new instance of HandlerSwitcher +func NewHandlerSwitcher(newHandler *mux.Router) (hs *HandlerSwitcher) { + return &HandlerSwitcher{ + handler: newHandler, + handlerLock: &sync.Mutex{}, + } +} + +func (hs *HandlerSwitcher) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + hs.handlerLock.Lock() + handlerBackup := hs.handler + hs.handlerLock.Unlock() + handlerBackup.ServeHTTP(rw, r) +} + +// GetHandler returns the current http.ServeMux +func (hs *HandlerSwitcher) GetHandler() (newHandler *mux.Router) { + return hs.handler +} + +// UpdateHandler safely updates the current http.ServeMux with a new one +func (hs *HandlerSwitcher) UpdateHandler(newHandler *mux.Router) { + hs.handlerLock.Lock() + hs.handler = newHandler + defer hs.handlerLock.Unlock() +} diff --git a/server.go b/server.go index 02be9c5c5..a3e2e68d0 100644 --- a/server.go +++ b/server.go @@ -48,7 +48,7 @@ type Server struct { type serverEntryPoint struct { httpServer *manners.GracefulServer - httpRouter *mux.Router + httpRouter *middlewares.HandlerSwitcher } // NewServer returns an initialized Server. @@ -82,7 +82,7 @@ func (server *Server) Start() { // Stop stops the server func (server *Server) Stop() { for _, serverEntryPoint := range server.serverEntryPoints { - serverEntryPoint.httpServer.Close() + serverEntryPoint.httpServer.BlockingClose() } server.stopChan <- true } @@ -142,22 +142,23 @@ func (server *Server) listenConfigurations() { server.serverLock.Lock() for newServerEntryPointName, newServerEntryPoint := range newServerEntryPoints { currentServerEntryPoint := server.serverEntryPoints[newServerEntryPointName] - server.currentConfigurations = newConfigurations - currentServerEntryPoint.httpRouter = newServerEntryPoint.httpRouter - oldServer := currentServerEntryPoint.httpServer - newsrv, err := server.prepareServer(currentServerEntryPoint.httpRouter, server.globalConfiguration.EntryPoints[newServerEntryPointName], oldServer, server.loggerMiddleware, metrics) - if err != nil { - log.Fatal("Error preparing server: ", err) - } - go server.startServer(newsrv, server.globalConfiguration) - currentServerEntryPoint.httpServer = newsrv - server.serverEntryPoints[newServerEntryPointName] = currentServerEntryPoint - time.Sleep(1 * time.Second) - if oldServer != nil { - log.Info("Stopping old server") - oldServer.Close() + if currentServerEntryPoint.httpServer == nil { + newsrv, err := server.prepareServer(newServerEntryPoint.httpRouter, server.globalConfiguration.EntryPoints[newServerEntryPointName], nil, server.loggerMiddleware, metrics) + if err != nil { + log.Fatal("Error preparing server: ", err) + } + go server.startServer(newsrv, server.globalConfiguration) + currentServerEntryPoint.httpServer = newsrv + currentServerEntryPoint.httpRouter = newServerEntryPoint.httpRouter + server.serverEntryPoints[newServerEntryPointName] = currentServerEntryPoint + log.Infof("Created new Handler: %p", newServerEntryPoint.httpRouter.GetHandler()) + } else { + handlerSwitcher := currentServerEntryPoint.httpRouter + handlerSwitcher.UpdateHandler(newServerEntryPoint.httpRouter.GetHandler()) + log.Infof("Created new Handler: %p", newServerEntryPoint.httpRouter.GetHandler()) } } + server.currentConfigurations = newConfigurations server.serverLock.Unlock() } else { log.Error("Error loading new configuration, aborted ", err) @@ -264,7 +265,7 @@ func (server *Server) startServer(srv *manners.GracefulServer, globalConfigurati log.Info("Server stopped") } -func (server *Server) prepareServer(router *mux.Router, entryPoint *EntryPoint, oldServer *manners.GracefulServer, middlewares ...negroni.Handler) (*manners.GracefulServer, error) { +func (server *Server) prepareServer(router http.Handler, entryPoint *EntryPoint, oldServer *manners.GracefulServer, middlewares ...negroni.Handler) (*manners.GracefulServer, error) { log.Info("Preparing server") // middlewares var negroni = negroni.New() @@ -303,7 +304,7 @@ func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration) for entryPointName := range globalConfiguration.EntryPoints { router := server.buildDefaultHTTPRouter() serverEntryPoints[entryPointName] = serverEntryPoint{ - httpRouter: router, + httpRouter: middlewares.NewHandlerSwitcher(router), } } return serverEntryPoints @@ -332,7 +333,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo if _, ok := serverEntryPoints[entryPointName]; !ok { return nil, errors.New("Undefined entrypoint: " + entryPointName) } - newRoute := serverEntryPoints[entryPointName].httpRouter.NewRoute().Name(frontendName) + newRoute := serverEntryPoints[entryPointName].httpRouter.GetHandler().NewRoute().Name(frontendName) for routeName, route := range frontend.Routes { log.Debugf("Creating route %s %s:%s", routeName, route.Rule, route.Value) route, err := getRoute(newRoute, route.Rule, route.Value)