From 4f6113695c34ce12eb557c210ce59208141dda26 Mon Sep 17 00:00:00 2001 From: Xabier Larrakoetxea Date: Wed, 3 Jun 2020 11:29:22 +0200 Subject: [PATCH] Add Goji support Signed-off-by: Xabier Larrakoetxea --- CHANGELOG.md | 4 ++ Readme.md | 2 + examples/goji/main.go | 58 ++++++++++++++++++ go.mod | 3 +- go.sum | 3 + middleware/goji/example_test.go | 47 ++++++++++++++ middleware/goji/goji.go | 16 +++++ middleware/goji/goji_test.go | 91 ++++++++++++++++++++++++++++ test/integration/integration_test.go | 34 ++++++++--- 9 files changed, 249 insertions(+), 9 deletions(-) create mode 100644 examples/goji/main.go create mode 100644 middleware/goji/example_test.go create mode 100644 middleware/goji/goji.go create mode 100644 middleware/goji/goji_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 35eb541..b85c7a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- New middleware helper for the Goji framework. + ## [0.7.0] - 2020-06-02 Breaking change: The library has been refactored to be more flexible when adding new framework/libraries. diff --git a/Readme.md b/Readme.md index 5a2a414..c10124e 100644 --- a/Readme.md +++ b/Readme.md @@ -41,6 +41,7 @@ The middleware is mainly focused to be compatible with Go std library using http - [go-restful][gorestful-example] - [Gin][gin-example] - [Echo][echo-example] +- [Goji][goji-example] ## Getting Started @@ -212,5 +213,6 @@ This Option is used to unregister the Recorder views before are being registered [gorestful-example]: examples/gorestful [gin-example]: examples/gin [echo-example]: examples/echo +[goji-example]: examples/goji [prometheus-recorder]: metrics/prometheus [opencensus-recorder]: metrics/opencensus diff --git a/examples/goji/main.go b/examples/goji/main.go new file mode 100644 index 0000000..9b7a7e5 --- /dev/null +++ b/examples/goji/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "log" + "net/http" + "os" + "os/signal" + "syscall" + + "github.com/prometheus/client_golang/prometheus/promhttp" + metrics "github.com/slok/go-http-metrics/metrics/prometheus" + "github.com/slok/go-http-metrics/middleware" + gojimiddleware "github.com/slok/go-http-metrics/middleware/goji" + "goji.io" + "goji.io/pat" +) + +const ( + srvAddr = ":8080" + metricsAddr = ":8081" +) + +func main() { + // Create our middleware. + mdlw := middleware.New(middleware.Config{ + Recorder: metrics.NewRecorder(metrics.Config{}), + }) + + // Create our router with the metrics middleware. + mux := goji.NewMux() + mux.Use(gojimiddleware.Handler("", mdlw)) + + mux.HandleFunc(pat.Get("/"), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Hello world")) + })) + + // Serve our handler. + go func() { + log.Printf("server listening at %s", srvAddr) + if err := http.ListenAndServe(srvAddr, mux); err != nil { + log.Panicf("error while serving: %s", err) + } + }() + + // Serve our metrics. + go func() { + log.Printf("metrics listening at %s", metricsAddr) + if err := http.ListenAndServe(metricsAddr, promhttp.Handler()); err != nil { + log.Panicf("error while serving metrics: %s", err) + } + }() + + // Wait until some signal is captured. + sigC := make(chan os.Signal, 1) + signal.Notify(sigC, syscall.SIGTERM, syscall.SIGINT) + <-sigC +} diff --git a/go.mod b/go.mod index ff8e37f..7071489 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/stretchr/testify v1.6.0 github.com/urfave/negroni v1.0.0 go.opencensus.io v0.22.3 + goji.io v2.0.2+incompatible ) -go 1.13 +go 1.14 diff --git a/go.sum b/go.sum index de0b2fc..f517650 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= contrib.go.opencensus.io/exporter/prometheus v0.1.0 h1:SByaIoWwNgMdPSgl5sMqM2KDE5H/ukPWBRo314xiDvg= contrib.go.opencensus.io/exporter/prometheus v0.1.0/go.mod h1:cGFniUXGZlKRjzOyuZJ6mgB+PgBcCIa79kEKR8YCW+A= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -161,6 +162,8 @@ go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +goji.io v2.0.2+incompatible h1:uIssv/elbKRLznFUy3Xj4+2Mz/qKhek/9aZQDUMae7c= +goji.io v2.0.2+incompatible/go.mod h1:sbqFwrtqZACxLBTQcdgVjFh54yGVCvwq8+w49MVMMIk= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw= diff --git a/middleware/goji/example_test.go b/middleware/goji/example_test.go new file mode 100644 index 0000000..a068f59 --- /dev/null +++ b/middleware/goji/example_test.go @@ -0,0 +1,47 @@ +package goji_test + +import ( + "log" + "net/http" + + "github.com/prometheus/client_golang/prometheus/promhttp" + "goji.io" + "goji.io/pat" + + metrics "github.com/slok/go-http-metrics/metrics/prometheus" + "github.com/slok/go-http-metrics/middleware" + gojimiddleware "github.com/slok/go-http-metrics/middleware/goji" +) + +// GojiMiddleware shows how you would create a default middleware factory and use it +// to create a Goji compatible middleware. +func Example_gojiMiddleware() { + // Create our middleware factory with the default settings. + mdlw := middleware.New(middleware.Config{ + Recorder: metrics.NewRecorder(metrics.Config{}), + }) + + // Create our Goji instance. + mux := goji.NewMux() + + // Add our middleware. + mux.Use(gojimiddleware.Handler("", mdlw)) + + // Add our handler. + mux.HandleFunc(pat.Get("/"), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Hello world")) + })) + + // Serve metrics from the default prometheus registry. + log.Printf("serving metrics at: %s", ":8081") + go func() { + _ = http.ListenAndServe(":8081", promhttp.Handler()) + }() + + // Serve our handler. + log.Printf("listening at: %s", ":8080") + if err := http.ListenAndServe(":8080", mux); err != nil { + log.Panicf("error while serving: %s", err) + } +} diff --git a/middleware/goji/goji.go b/middleware/goji/goji.go new file mode 100644 index 0000000..c5ec6f1 --- /dev/null +++ b/middleware/goji/goji.go @@ -0,0 +1,16 @@ +// Package goji is a helper package to get a goji compatible middleware. +package goji + +import ( + "net/http" + + "github.com/slok/go-http-metrics/middleware" + "github.com/slok/go-http-metrics/middleware/std" +) + +// Handler returns a Goji measuring middleware. +func Handler(handlerID string, m middleware.Middleware) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return std.Handler(handlerID, m, next) + } +} diff --git a/middleware/goji/goji_test.go b/middleware/goji/goji_test.go new file mode 100644 index 0000000..3c6e60c --- /dev/null +++ b/middleware/goji/goji_test.go @@ -0,0 +1,91 @@ +package goji_test + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "goji.io" + "goji.io/pat" + + mmetrics "github.com/slok/go-http-metrics/internal/mocks/metrics" + "github.com/slok/go-http-metrics/metrics" + "github.com/slok/go-http-metrics/middleware" + gojimiddleware "github.com/slok/go-http-metrics/middleware/goji" +) + +func TestMiddleware(t *testing.T) { + tests := map[string]struct { + handlerID string + config middleware.Config + req func() *http.Request + mock func(m *mmetrics.Recorder) + handler func() http.Handler + expRespCode int + expRespBody string + }{ + "A default HTTP middleware should call the recorder to measure.": { + req: func() *http.Request { + return httptest.NewRequest(http.MethodPost, "/test", nil) + }, + mock: func(m *mmetrics.Recorder) { + expHTTPReqProps := metrics.HTTPReqProperties{ + ID: "/test", + Service: "", + Method: "POST", + Code: "202", + } + m.On("ObserveHTTPRequestDuration", mock.Anything, expHTTPReqProps, mock.Anything).Once() + m.On("ObserveHTTPResponseSize", mock.Anything, expHTTPReqProps, int64(5)).Once() + + expHTTPProps := metrics.HTTPProperties{ + ID: "/test", + Service: "", + } + m.On("AddInflightRequests", mock.Anything, expHTTPProps, 1).Once() + m.On("AddInflightRequests", mock.Anything, expHTTPProps, -1).Once() + }, + handler: func() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(202) + w.Write([]byte("test1")) // nolint: errcheck + }) + }, + expRespCode: 202, + expRespBody: "test1", + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + // Mocks. + mr := &mmetrics.Recorder{} + test.mock(mr) + + // Create our instance with the middleware. + mdlw := middleware.New(middleware.Config{Recorder: mr}) + mux := goji.NewMux() + req := test.req() + mux.Handle(pat.NewWithMethods(req.URL.Path, req.Method), test.handler()) + mux.Use(gojimiddleware.Handler(test.handlerID, mdlw)) + + // Make the request. + resp := httptest.NewRecorder() + mux.ServeHTTP(resp, req) + + // Check. + mr.AssertExpectations(t) + assert.Equal(test.expRespCode, resp.Result().StatusCode) + gotBody, err := ioutil.ReadAll(resp.Result().Body) + require.NoError(err) + assert.Equal(test.expRespBody, string(gotBody)) + }) + } +} diff --git a/test/integration/integration_test.go b/test/integration/integration_test.go index 0bcd144..b8f0adc 100644 --- a/test/integration/integration_test.go +++ b/test/integration/integration_test.go @@ -18,11 +18,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/urfave/negroni" + "goji.io" + "goji.io/pat" metricsprometheus "github.com/slok/go-http-metrics/metrics/prometheus" "github.com/slok/go-http-metrics/middleware" echomiddleware "github.com/slok/go-http-metrics/middleware/echo" ginmiddleware "github.com/slok/go-http-metrics/middleware/gin" + gojimiddleware "github.com/slok/go-http-metrics/middleware/goji" gorestfulmiddleware "github.com/slok/go-http-metrics/middleware/gorestful" httproutermiddleware "github.com/slok/go-http-metrics/middleware/httprouter" negronimiddleware "github.com/slok/go-http-metrics/middleware/negroni" @@ -50,6 +53,7 @@ func TestMiddlewarePrometheus(t *testing.T) { "Gorestful": {handler: prepareHandlerGorestful}, "Gin": {handler: prepareHandlerGin}, "Echo": {handler: prepareHandlerEcho}, + "Goji": {handler: prepareHandlerGoji}, } for name, test := range tests { @@ -139,8 +143,7 @@ func prepareHandlerSTD(m middleware.Middleware, hc []handlerConfig) http.Handler time.Sleep(h.SleepDuration) w.WriteHeader(h.Code) - // nolint: errcheck - w.Write([]byte(h.ReturnData)) + w.Write([]byte(h.ReturnData)) // nolint: errcheck })) } @@ -163,8 +166,7 @@ func prepareHandlerNegroni(m middleware.Middleware, hc []handlerConfig) http.Han time.Sleep(h.SleepDuration) w.WriteHeader(h.Code) - // nolint: errcheck - w.Write([]byte(h.ReturnData)) + w.Write([]byte(h.ReturnData)) // nolint: errcheck })) } @@ -185,8 +187,7 @@ func prepareHandlerHTTPRouter(m middleware.Middleware, hc []handlerConfig) http. hr := func(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) { time.Sleep(h.SleepDuration) w.WriteHeader(h.Code) - // nolint: errcheck - w.Write([]byte(h.ReturnData)) + w.Write([]byte(h.ReturnData)) // nolint: errcheck } // Setup middleware on each of the routes. @@ -208,8 +209,7 @@ func prepareHandlerGorestful(m middleware.Middleware, hc []handlerConfig) http.H ws.Route(ws.Method(h.Method).Path(h.Path).To(func(_ *gorestful.Request, resp *gorestful.Response) { time.Sleep(h.SleepDuration) resp.WriteHeader(h.Code) - // nolint: errcheck - resp.Write([]byte(h.ReturnData)) + resp.Write([]byte(h.ReturnData)) // nolint: errcheck })) } c.Add(ws) @@ -250,3 +250,21 @@ func prepareHandlerEcho(m middleware.Middleware, hc []handlerConfig) http.Handle return e } + +func prepareHandlerGoji(m middleware.Middleware, hc []handlerConfig) http.Handler { + // Setup server and middleware. + mux := goji.NewMux() + mux.Use(gojimiddleware.Handler("", m)) + + // Setup handlers. + for _, h := range hc { + h := h + mux.HandleFunc(pat.NewWithMethods(h.Path, h.Method), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(h.SleepDuration) + w.WriteHeader(h.Code) + w.Write([]byte(h.ReturnData)) // nolint: errcheck + })) + } + + return mux +}