-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Gin compatible middleware
- Loading branch information
Showing
10 changed files
with
267 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
name: Go | ||
name: CI | ||
on: [push] | ||
jobs: | ||
test: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,10 @@ | |
|
||
## [Unreleased] | ||
|
||
### Added | ||
|
||
- Gin compatible middleware. | ||
|
||
## [0.4.0] - 2019-03-27 | ||
|
||
### Breaking changes | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
}) | ||
} | ||
} |