diff --git a/.github/workflows/go.yml b/.github/workflows/ci.yml similarity index 97% rename from .github/workflows/go.yml rename to .github/workflows/ci.yml index 148c551..f2516e2 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Go +name: CI on: [push] jobs: test: diff --git a/CHANGELOG.md b/CHANGELOG.md index b4f4170..6348082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- Gin compatible middleware. + ## [0.4.0] - 2019-03-27 ### Breaking changes diff --git a/Readme.md b/Readme.md index a04318a..554137d 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 - [Negroni][negroni-example] - [httprouter][httprouter-example] - [go-restful][gorestful-example] +- [Gin][gin-example] ## Getting Started @@ -202,7 +203,7 @@ BenchmarkMiddlewareHandler/benchmark_with_grouped_status_code.-4 1000000 BenchmarkMiddlewareHandler/benchmark_with_predefined_handler_ID-4 1000000 1258 ns/op 256 B/op 6 allocs/op ``` -[github-actions-image]: https://github.com/slok/go-http-metrics/workflows/Go/badge.svg +[github-actions-image]: https://github.com/slok/go-http-metrics/workflows/CI/badge.svg [github-actions-url]: https://github.com/slok/go-http-metrics/actions [goreport-image]: https://goreportcard.com/badge/github.com/slok/go-http-metrics [goreport-url]: https://goreportcard.com/report/github.com/slok/go-http-metrics @@ -217,5 +218,6 @@ BenchmarkMiddlewareHandler/benchmark_with_predefined_handler_ID-4 1000000 [negroni-example]: examples/negroni [httprouter-example]: examples/httprouter [gorestful-example]: examples/gorestful +[gin-example]: examples/gin [prometheus-recorder]: metrics/prometheus [opencensus-recorder]: metrics/opencensus diff --git a/examples/gin/main.go b/examples/gin/main.go new file mode 100644 index 0000000..7f2581b --- /dev/null +++ b/examples/gin/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "log" + "net/http" + "os" + "os/signal" + "syscall" + + "github.com/gin-gonic/gin" + "github.com/prometheus/client_golang/prometheus/promhttp" + metrics "github.com/slok/go-http-metrics/metrics/prometheus" + "github.com/slok/go-http-metrics/middleware" + ginmiddleware "github.com/slok/go-http-metrics/middleware/gin" +) + +const ( + srvAddr = ":8080" + metricsAddr = ":8081" +) + +func main() { + // Create our middleware. + mdlw := middleware.New(middleware.Config{ + Recorder: metrics.NewRecorder(metrics.Config{}), + }) + + // Create Gin engine and global middleware. + engine := gin.New() + engine.Use(ginmiddleware.Handler("", mdlw)) + + // Add our handler. + engine.GET("/", func(c *gin.Context) { + c.String(http.StatusOK, "Hello world") + }) + engine.GET("/wrong", func(c *gin.Context) { + c.String(http.StatusTooManyRequests, "oops") + }) + + // Serve our handler. + go func() { + log.Printf("server listening at %s", srvAddr) + if err := http.ListenAndServe(srvAddr, engine); 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/examples/gorestful/main.go b/examples/gorestful/main.go index 53f0dc9..6effb7f 100644 --- a/examples/gorestful/main.go +++ b/examples/gorestful/main.go @@ -46,7 +46,7 @@ func main() { // Serve our handler. go func() { log.Printf("server listening at %s", srvAddr) - if err := http.ListenAndServe(":8080", c); err != nil { + if err := http.ListenAndServe(srvAddr, c); err != nil { log.Panicf("error while serving: %s", err) } }() diff --git a/go.mod b/go.mod index bf25eca..fa4ac1a 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,12 @@ module github.com/slok/go-http-metrics require ( contrib.go.opencensus.io/exporter/prometheus v0.1.0 github.com/emicklei/go-restful v2.9.6+incompatible + github.com/gin-gonic/gin v1.5.0 github.com/julienschmidt/httprouter v1.2.0 github.com/prometheus/client_golang v1.0.0 - github.com/stretchr/testify v1.3.0 + github.com/stretchr/testify v1.4.0 github.com/urfave/negroni v1.0.0 go.opencensus.io v0.22.0 ) + +go 1.13 diff --git a/go.sum b/go.sum index a2534a6..07e6039 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful v2.9.6+incompatible h1:tfrHha8zJ01ywiOEC1miGY8st1/igzWB8OmvPgoYX7w= github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.5.0 h1:fi+bqFAx/oLK54somfCtEZs9HeH1LHVoEPUgARpTqyc= +github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -24,24 +32,34 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -71,6 +89,12 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg= @@ -105,6 +129,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd h1:r7DufRZuZbWB7j439YfAzP8RPDa9unLkpwQKUYbIMPI= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -121,6 +146,11 @@ google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiq gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc= +gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/middleware/gin/example_test.go b/middleware/gin/example_test.go new file mode 100644 index 0000000..df6e8d8 --- /dev/null +++ b/middleware/gin/example_test.go @@ -0,0 +1,41 @@ +package gin_test + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/prometheus/client_golang/prometheus/promhttp" + + metrics "github.com/slok/go-http-metrics/metrics/prometheus" + "github.com/slok/go-http-metrics/middleware" + ginmiddleware "github.com/slok/go-http-metrics/middleware/gin" +) + +// GinMiddleware shows how you would create a default middleware factory and use it +// to create a Gin compatible middleware. +func Example_ginMiddleware() { + // Create our middleware factory with the default settings. + mdlw := middleware.New(middleware.Config{ + Recorder: metrics.NewRecorder(metrics.Config{}), + }) + + // Create our gin instance. + engine := gin.New() + + // Add our handler and middleware + h := func(c *gin.Context) { + c.String(http.StatusOK, "Hello world") + } + engine.GET("/", ginmiddleware.Handler("", mdlw), h) + + // Serve metrics from the default prometheus registry. + log.Printf("serving metrics at: %s", ":8081") + go http.ListenAndServe(":8081", promhttp.Handler()) + + // Serve our handler. + log.Printf("listening at: %s", ":8080") + if err := http.ListenAndServe(":8080", engine); err != nil { + log.Panicf("error while serving: %s", err) + } +} diff --git a/middleware/gin/gin.go b/middleware/gin/gin.go new file mode 100644 index 0000000..059c77c --- /dev/null +++ b/middleware/gin/gin.go @@ -0,0 +1,52 @@ +// Package gin is a helper package to get a gin compatible +// handler/middleware from the standard net/http Middleware factory. +package gin + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/slok/go-http-metrics/middleware" +) + +// Handler returns a Gin compatible middleware from a Middleware factory instance. +// The first handlerID argument is the same argument passed on Middleware.Handler method. +func Handler(handlerID string, m middleware.Middleware) gin.HandlerFunc { + // Create a dummy handler to wrap the middleware chain of Gin, this way Middleware + // interface can wrap the Gin chain. + return func(c *gin.Context) { + h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c.Writer = &ginResponseWriter{ + ResponseWriter: c.Writer, + middlewareRW: w, + } + c.Next() + }) + m.Handler(handlerID, h).ServeHTTP(c.Writer, c.Request) + } +} + +// ginResponseWriter is a helper type that intercepts the middleware ResponseWriter +// interceptor. +// This is required because gin's context Writer (c.Writer) is a gin.ResponseWriter +// interface and we can't access to the internal object http.ResponseWriter, so +// we already know that our middleware intercepts the regular http.ResponseWriter, +// and doesn't change anything, just intercepts to read information. So in order to +// get this information on our interceptor we create a gin.ResponseWriter implementation +// that will call the real gin.Context.Writer and our interceptor. This way Gin gets the +// information and our interceptor also. +type ginResponseWriter struct { + middlewareRW http.ResponseWriter + gin.ResponseWriter +} + +func (w *ginResponseWriter) WriteHeader(statusCode int) { + w.middlewareRW.WriteHeader(statusCode) + w.ResponseWriter.WriteHeader(statusCode) +} + +func (w *ginResponseWriter) Write(p []byte) (int, error) { + w.middlewareRW.Write(p) + return w.ResponseWriter.Write(p) +} diff --git a/middleware/gin/gin_test.go b/middleware/gin/gin_test.go new file mode 100644 index 0000000..1f86314 --- /dev/null +++ b/middleware/gin/gin_test.go @@ -0,0 +1,71 @@ +package gin_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + mmetrics "github.com/slok/go-http-metrics/internal/mocks/metrics" + "github.com/slok/go-http-metrics/middleware" + ginmiddleware "github.com/slok/go-http-metrics/middleware/gin" +) + +func getTestHandler(statusCode int) gin.HandlerFunc { + return func(c *gin.Context) { + c.String(statusCode, "Hello world") + } +} + +func TestMiddlewareIntegration(t *testing.T) { + tests := []struct { + name string + handlerID string + statusCode int + req *http.Request + config middleware.Config + expHandlerID string + expMethod string + expStatusCode string + }{ + { + name: "A default HTTP middleware should call the recorder to measure.", + statusCode: http.StatusAccepted, + req: httptest.NewRequest(http.MethodPost, "/test", nil), + expHandlerID: "/test", + expMethod: http.MethodPost, + expStatusCode: "202", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert := assert.New(t) + + // Mocks. + mr := &mmetrics.Recorder{} + mr.On("ObserveHTTPRequestDuration", mock.Anything, test.expHandlerID, mock.Anything, test.expMethod, test.expStatusCode).Once() + mr.On("ObserveHTTPResponseSize", mock.Anything, test.expHandlerID, mock.Anything, test.expMethod, test.expStatusCode).Once() + mr.On("AddInflightRequests", mock.Anything, test.expHandlerID, 1).Once() + mr.On("AddInflightRequests", mock.Anything, test.expHandlerID, -1).Once() + + // Create our instance with the middleware. + mdlw := middleware.New(middleware.Config{Recorder: mr}) + engine := gin.New() + engine.POST("/test", + ginmiddleware.Handler("", mdlw), + getTestHandler(test.statusCode)) + + // Make the request. + resp := httptest.NewRecorder() + engine.ServeHTTP(resp, test.req) + + // Check. + mr.AssertExpectations(t) + assert.Equal(test.statusCode, resp.Result().StatusCode) + }) + } +}