Skip to content
This repository has been archived by the owner on Mar 15, 2024. It is now read-only.

Commit

Permalink
🍭 Custom http handler (#20)
Browse files Browse the repository at this point in the history
* 🍭 Custom http handler

fix #19

- NewHTTPServer now exported type, and you can create http-server that will be embedded into multi-server
- removed ProfileHandlerModule and MetricHandlerModule not need anymore
- remove profileResult, metricResult not need anymore

From now, you can pass `profile_handler` and/or `metric_handler`, that will be embedded into common handler and will be available to call them

- describe new functionality in readme
- enhancement and simplify for tests (randomize ports)
  • Loading branch information
im-kulikov authored Apr 27, 2019
1 parent 617e4be commit fcc54f9
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 68 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 14 additions & 35 deletions web/servers.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,38 +45,16 @@ type (
Logger logger.StdLogger
}

profileResult struct {
dig.Out

Handler http.Handler `name:"profile_handler"`
}

metricParams struct {
dig.In

Handler http.Handler `name:"metric_handler" optional:"true"`
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},
Expand All @@ -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{}
Expand Down
119 changes: 86 additions & 33 deletions web/servers_test.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
package web

import (
"io/ioutil"
"net"
"net/http"
"testing"

"github.com/chapsuk/mserv"
"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) {
Expand All @@ -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)
}
}
})
}

0 comments on commit fcc54f9

Please sign in to comment.