diff --git a/README.md b/README.md index 5870abe..954d88b 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,8 @@ Viper is a prioritized configuration registry. It maintains a set of configurati - `ServersModule` puts into container [multi-server](https://github.com/chapsuk/mserv): - [pprof](https://golang.org/pkg/net/http/pprof/) endpoint - [metrics](https://github.com/prometheus/client_golang) enpoint (by Prometheus) + - You can pass `profile_handler` and/or `metric_handler`, that will be embedded into common handler, + and will be available to call them - **api** endpoint by passing http.Handler from DI - [`echo.Module`](https://github.com/go-helium/echo) boilerplate that preconfigures echo.Engine for you - with custom Binder / Logger / Validator / ErrorHandler diff --git a/web/servers.go b/web/servers.go index 3b4008d..92552b3 100644 --- a/web/servers.go +++ b/web/servers.go @@ -45,12 +45,6 @@ type ( Logger logger.StdLogger } - profileResult struct { - dig.Out - - Handler http.Handler `name:"profile_handler"` - } - metricParams struct { dig.In @@ -58,25 +52,9 @@ type ( Viper *viper.Viper Logger logger.StdLogger } - - metricResult struct { - dig.Out - - Handler http.Handler `name:"metric_handler"` - } ) var ( - // ProfileHandlerModule that provides default profile handler - ProfileHandlerModule = module.Module{ - {Constructor: newProfileHandler}, - } - - // MetricHandlerModule that provides default metric handler - MetricHandlerModule = module.Module{ - {Constructor: newMetricHandler}, - } - // ServersModule of web base structs ServersModule = module.Module{ {Constructor: newProfileServer}, @@ -92,34 +70,35 @@ func NewMultiServer(params MultiServerParams) mserv.Server { return mserv.New(params.Servers...) } -func newProfileHandler() profileResult { +func newProfileServer(p profileParams) ServerResult { mux := http.NewServeMux() mux.HandleFunc("/debug/pprof/", pprof.Index) mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) mux.HandleFunc("/debug/pprof/profile", pprof.Profile) mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) mux.HandleFunc("/debug/pprof/trace", pprof.Trace) - return profileResult{Handler: mux} -} - -func newProfileServer(p profileParams) ServerResult { - return newHTTPServer(p.Viper, "pprof", p.Handler, p.Logger) -} - -func newMetricHandler() metricResult { - return metricResult{Handler: promhttp.Handler()} + if p.Handler != nil { + mux.Handle("/", p.Handler) + } + return NewHTTPServer(p.Viper, "pprof", mux, p.Logger) } func newMetricServer(p metricParams) ServerResult { - return newHTTPServer(p.Viper, "metrics", p.Handler, p.Logger) + mux := http.NewServeMux() + mux.Handle("/metrics", promhttp.Handler()) + if p.Handler != nil { + mux.Handle("/", p.Handler) + } + return NewHTTPServer(p.Viper, "metrics", mux, p.Logger) } // NewAPIServer creates api server by http.Handler from DI container func NewAPIServer(v *viper.Viper, l logger.StdLogger, h http.Handler) ServerResult { - return newHTTPServer(v, "api", h, l) + return NewHTTPServer(v, "api", h, l) } -func newHTTPServer(v *viper.Viper, key string, h http.Handler, l logger.StdLogger) ServerResult { +// NewHTTPServer creates http-server that will be embedded into multi-server +func NewHTTPServer(v *viper.Viper, key string, h http.Handler, l logger.StdLogger) ServerResult { if h == nil { l.Printf("Empty handler for %s server, skip", key) return ServerResult{} diff --git a/web/servers_test.go b/web/servers_test.go index c802c9f..2cd3e86 100644 --- a/web/servers_test.go +++ b/web/servers_test.go @@ -1,6 +1,8 @@ package web import ( + "io/ioutil" + "net" "net/http" "testing" @@ -8,13 +10,20 @@ import ( "github.com/im-kulikov/helium/logger" "github.com/im-kulikov/helium/module" "github.com/spf13/viper" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.uber.org/dig" "go.uber.org/zap" ) -func testHTTPHandler() http.Handler { - return http.NewServeMux() +var expectResult = []byte("OK") + +func testHTTPHandler(assert *require.Assertions) http.Handler { + mux := http.NewServeMux() + mux.HandleFunc("/test", func(w http.ResponseWriter, _ *http.Request) { + _, err := w.Write(expectResult) + assert.NoError(err) + }) + return mux } func TestServers(t *testing.T) { @@ -28,88 +37,132 @@ func TestServers(t *testing.T) { t.Run("check pprof server", func(t *testing.T) { t.Run("without config", func(t *testing.T) { params := profileParams{ - Viper: v, - Logger: l, - Handler: newProfileHandler().Handler, + Viper: v, + Logger: l, } serve := newProfileServer(params) - assert.Nil(t, serve.Server) + require.Nil(t, serve.Server) }) t.Run("with config", func(t *testing.T) { v.SetDefault("pprof.address", ":6090") params := profileParams{ - Viper: v, - Logger: l, - Handler: newProfileHandler().Handler, + Viper: v, + Logger: l, } serve := newProfileServer(params) - assert.NotNil(t, serve.Server) + require.NotNil(t, serve.Server) + require.IsType(t, &mserv.HTTPServer{}, serve.Server) }) }) t.Run("check metrics server", func(t *testing.T) { t.Run("without config", func(t *testing.T) { params := metricParams{ - Viper: v, - Logger: l, - Handler: newMetricHandler().Handler, + Viper: v, + Logger: l, } serve := newMetricServer(params) - assert.Nil(t, serve.Server) + require.Nil(t, serve.Server) }) t.Run("with config", func(t *testing.T) { v.SetDefault("metrics.address", ":8090") params := metricParams{ - Viper: v, - Logger: l, - Handler: newMetricHandler().Handler, + Viper: v, + Logger: l, } serve := newMetricServer(params) - assert.NotNil(t, serve.Server) + require.NotNil(t, serve.Server) + require.IsType(t, &mserv.HTTPServer{}, serve.Server) }) }) t.Run("check api server", func(t *testing.T) { t.Run("without config", func(t *testing.T) { serve := NewAPIServer(v, l, nil) - assert.Nil(t, serve.Server) + require.Nil(t, serve.Server) }) t.Run("without handler", func(t *testing.T) { v.SetDefault("api.address", ":8090") serve := NewAPIServer(v, l, nil) - assert.Nil(t, serve.Server) + require.Nil(t, serve.Server) }) t.Run("should be ok", func(t *testing.T) { + assert := require.New(t) v.SetDefault("api.address", ":8090") - serve := NewAPIServer(v, l, testHTTPHandler()) - assert.NotNil(t, serve.Server) + serve := NewAPIServer(v, l, testHTTPHandler(assert)) + assert.NotNil(serve.Server) + assert.IsType(&mserv.HTTPServer{}, serve.Server) }) }) t.Run("check multi server", func(t *testing.T) { - v.SetDefault("pprof.address", ":6090") - v.SetDefault("metrics.address", ":8090") - v.SetDefault("api.address", ":8090") + var ( + err error + assert = require.New(t) + servers = map[string]net.Listener{ + "pprof.address": nil, + "metrics.address": nil, + "api.address": nil, + } + ) + + // Randomize ports: + for name := range servers { + servers[name], err = net.Listen("tcp", ":0") + assert.NoError(err) + assert.NoError(servers[name].Close()) + + v.SetDefault(name, servers[name].Addr().String()) + } mod := module.Module{ {Constructor: func() *viper.Viper { return v }}, {Constructor: func() logger.StdLogger { return l }}, - {Constructor: func() http.Handler { return testHTTPHandler() }}, + {Constructor: func() http.Handler { return testHTTPHandler(assert) }}, + + { + Constructor: func() http.Handler { return testHTTPHandler(assert) }, + Options: []dig.ProvideOption{dig.Name("metric_handler")}, + }, + + { + Constructor: func() http.Handler { return testHTTPHandler(assert) }, + Options: []dig.ProvideOption{dig.Name("profile_handler")}, + }, }.Append( ServersModule, - ProfileHandlerModule, - MetricHandlerModule, ) - err := module.Provide(di, mod) - assert.NoError(t, err) + assert.NoError(module.Provide(di, mod)) + err = di.Invoke(func(serve mserv.Server) { - assert.IsType(t, &mserv.MultiServer{}, serve) + assert.IsType(&mserv.MultiServer{}, serve) + + serve.Start() }) - assert.NoError(t, err) + assert.NoError(err) + + for name, lis := range servers { + { + t.Logf("check for %q on %q", name, lis.Addr()) + + resp, err := http.Get("http://" + lis.Addr().String() + "/test") + assert.NoError(err) + + defer func() { + err := resp.Body.Close() + assert.NoError(err) + }() + + data, err := ioutil.ReadAll(resp.Body) + assert.NoError(err) + + assert.Equal(expectResult, data) + } + } }) }