diff --git a/config.go b/config.go index 4569c045..2e1d6df6 100644 --- a/config.go +++ b/config.go @@ -74,6 +74,7 @@ type BurrowConfig struct { Httpserver struct { Enable bool `gcfg:"server"` Port int `gcfg:"port"` + Listen []string `gcfg:"listen"` } Notify struct { Interval int64 `gcfg:"interval"` @@ -329,8 +330,16 @@ func ValidateConfig(app *ApplicationContext) error { // HTTP Server if app.Config.Httpserver.Enable { - if app.Config.Httpserver.Port == 0 { - errs = append(errs, "HTTP server port is not specified") + if len(app.Config.Httpserver.Listen) == 0 { + if app.Config.Httpserver.Port == 0 { + errs = append(errs, "HTTP server port is not specified") + } + listenPort := fmt.Sprintf(":%v", app.Config.Httpserver.Port) + app.Config.Httpserver.Listen = append(app.Config.Httpserver.Listen, listenPort) + } else { + if app.Config.Httpserver.Port != 0 { + errs = append(errs, "Either HTTP server port or listen can be specified, but not both") + } } } diff --git a/config/burrow.cfg b/config/burrow.cfg index 1518826a..e831a133 100644 --- a/config/burrow.cfg +++ b/config/burrow.cfg @@ -45,6 +45,9 @@ expire-group=604800 [httpserver] server=on port=8000 +; Alternatively, use listen (cannot be specified when port is) +; listen=host:port +; listen=host2:port2 [smtp] server=mailserver.example.com diff --git a/http_server.go b/http_server.go index 63e6f0dd..31f05ef7 100644 --- a/http_server.go +++ b/http_server.go @@ -12,12 +12,14 @@ package main import ( "encoding/json" - "fmt" "github.com/linkedin/Burrow/protocol" "io" "net/http" "os" "strings" + "net" + "time" + log "github.com/cihub/seelog" ) type HttpServer struct { @@ -30,6 +32,25 @@ type appHandler struct { handler func(*ApplicationContext, http.ResponseWriter, *http.Request) (int, string) } + +// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted +// connections. It's used by ListenAndServe and ListenAndServeTLS so +// dead TCP connections (e.g. closing laptop mid-download) eventually +// go away. +type tcpKeepAliveListener struct { + *net.TCPListener +} + +func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { + tc, err := ln.AcceptTCP() + if err != nil { + return + } + tc.SetKeepAlive(true) + tc.SetKeepAlivePeriod(3 * time.Minute) + return tc, nil +} + func NewHttpServer(app *ApplicationContext) (*HttpServer, error) { server := &HttpServer{ app: app, @@ -48,7 +69,27 @@ func NewHttpServer(app *ApplicationContext) (*HttpServer, error) { server.mux.Handle("/v2/zookeeper", appHandler{server.app, handleClusterList}) // server.mux.Handle("/v2/zookeeper/", appHandler{server.app, handleZookeeper}) - go http.ListenAndServe(fmt.Sprintf(":%v", server.app.Config.Httpserver.Port), server.mux) + listeners := make([]net.Listener, 0, len(server.app.Config.Httpserver.Listen)) + + for _, listenAddress := range server.app.Config.Httpserver.Listen { + ln, err := net.Listen("tcp", listenAddress) + if err != nil { + for _, listenerToClose := range listeners { + closeErr := listenerToClose.Close() + if closeErr != nil { + log.Errorf("Could not close listener: %v", closeErr) + } + } + return nil, err + } + listeners = append(listeners, tcpKeepAliveListener{ln.(*net.TCPListener)}) + } + + httpServer := &http.Server{Handler: server.mux} + for _, listener := range listeners { + go httpServer.Serve(listener) + } + return server, nil }