├── .travis.yml ├── LICENSE ├── README.md ├── wraphh.go └── wraphh_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.5 5 | - 1.6 6 | - tip 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Timothy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WrapHH [![GoDoc](https://godoc.org/github.com/turtlemonvh/gin-wraphh?status.svg)](https://godoc.org/github.com/turtlemonvh/gin-wraphh) [![Build Status](https://travis-ci.org/turtlemonvh/gin-wraphh.png?branch=master)](https://travis-ci.org/turtlemonvh/gin-wraphh) 2 | 3 | Use this to wrap middleware that accepts and returns `http.Handler` objects for use in gin. 4 | 5 | Created to look like these helper methods in gin: 6 | 7 | * https://godoc.org/github.com/gin-gonic/gin#WrapF 8 | * https://godoc.org/github.com/gin-gonic/gin#WrapH 9 | 10 | ## Examples 11 | 12 | See the test code. I have examples wrapping [the NYT gzip library](https://github.com/NYTimes/gziphandler) and [NoSurf](https://github.com/justinas/nosurf), a popular CSRF protection middleware for golang. 13 | 14 | ## About 15 | 16 | Based on this gist: https://gist.github.com/turtlemonvh/6cd23ef13e1e290717ef 17 | 18 | ## License 19 | 20 | MIT 21 | -------------------------------------------------------------------------------- /wraphh.go: -------------------------------------------------------------------------------- 1 | package wraphh 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | ) 7 | 8 | // A wrapper that turns a http.ResponseWriter into a gin.ResponseWriter, given an existing gin.ResponseWriter 9 | // Needed if the middleware you are using modifies the writer it passes downstream 10 | // FIXME: Wrap more methods: https://golang.org/pkg/net/http/#ResponseWriter 11 | type wrappedResponseWriter struct { 12 | gin.ResponseWriter 13 | writer http.ResponseWriter 14 | } 15 | 16 | func (w *wrappedResponseWriter) Write(data []byte) (int, error) { 17 | return w.writer.Write(data) 18 | } 19 | 20 | func (w *wrappedResponseWriter) WriteString(s string) (n int, err error) { 21 | return w.writer.Write([]byte(s)) 22 | } 23 | 24 | // An http.Handler that passes on calls to downstream middlewares 25 | type nextRequestHandler struct { 26 | c *gin.Context 27 | } 28 | 29 | // Run the next request in the middleware chain and return 30 | func (h *nextRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 31 | h.c.Writer = &wrappedResponseWriter{h.c.Writer, w} 32 | h.c.Next() 33 | } 34 | 35 | // Wrap something that accepts an http.Handler, returns an http.Handler 36 | func WrapHH(hh func(h http.Handler) http.Handler) gin.HandlerFunc { 37 | // Steps: 38 | // - create an http handler to pass `hh` 39 | // - call `hh` with the http handler, which returns a function 40 | // - call the ServeHTTP method of the resulting function to run the rest of the middleware chain 41 | 42 | return func(c *gin.Context) { 43 | hh(&nextRequestHandler{c}).ServeHTTP(c.Writer, c.Request) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /wraphh_test.go: -------------------------------------------------------------------------------- 1 | package wraphh 2 | 3 | import ( 4 | "compress/gzip" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "net/http/httptest" 9 | "strconv" 10 | "testing" 11 | 12 | "github.com/gin-gonic/gin" 13 | "github.com/stretchr/testify/assert" 14 | 15 | // Example complex middlewares 16 | gzipmiddle "github.com/NYTimes/gziphandler" 17 | "github.com/justinas/nosurf" 18 | ) 19 | 20 | const ( 21 | testResponse = "cat cat cat cat cat cat cat cat " 22 | ) 23 | 24 | var middlewareOptions = make(map[string]func(http.Handler) http.Handler) 25 | 26 | func init() { 27 | middlewareOptions["gzip"] = gzipmiddle.GzipHandler 28 | middlewareOptions["nosurf"] = nosurf.NewPure 29 | } 30 | 31 | func newServer(mo string) *gin.Engine { 32 | router := gin.Default() 33 | 34 | if middlewareOptions[mo] != nil { 35 | router.Use(WrapHH(middlewareOptions[mo])) 36 | } 37 | 38 | router.GET("/", func(c *gin.Context) { 39 | c.Header("Content-Length", strconv.Itoa(len(testResponse))) 40 | c.String(200, testResponse) 41 | }) 42 | 43 | router.POST("/", func(c *gin.Context) { 44 | c.Header("Content-Length", strconv.Itoa(len(testResponse))) 45 | c.String(200, testResponse) 46 | }) 47 | 48 | return router 49 | } 50 | 51 | // Based off: 52 | // https://github.com/gin-gonic/contrib/blob/master/gzip/gzip_test.go 53 | func TestNYTGzip(t *testing.T) { 54 | req, _ := http.NewRequest("GET", "/", nil) 55 | req.Header.Add("Accept-Encoding", "gzip") 56 | 57 | w := httptest.NewRecorder() 58 | r := newServer("gzip") 59 | r.ServeHTTP(w, req) 60 | 61 | assert.Equal(t, w.Code, 200) 62 | assert.Equal(t, w.Header().Get("Content-Encoding"), "gzip") 63 | assert.Equal(t, w.Header().Get("Vary"), "Accept-Encoding") 64 | assert.Equal(t, w.Header().Get("Content-Length"), "32") 65 | assert.NotEqual(t, w.Body.Len(), 32) 66 | assert.True(t, w.Body.Len() < 32, fmt.Sprintf("body length is %d, not <32", w.Body.Len())) 67 | 68 | gr, err := gzip.NewReader(w.Body) 69 | assert.NoError(t, err) 70 | defer gr.Close() 71 | 72 | body, _ := ioutil.ReadAll(gr) 73 | assert.Equal(t, string(body), testResponse) 74 | } 75 | 76 | // Should return a 400 because CSRF token is mising for POST request 77 | func TestNoSurf(t *testing.T) { 78 | req, _ := http.NewRequest("POST", "/", nil) 79 | 80 | w := httptest.NewRecorder() 81 | r := newServer("nosurf") 82 | r.ServeHTTP(w, req) 83 | 84 | assert.Equal(t, w.Code, nosurf.FailureCode) 85 | } 86 | 87 | // Should return a 200 88 | func TestNotNoSurf(t *testing.T) { 89 | req, _ := http.NewRequest("POST", "/", nil) 90 | 91 | w := httptest.NewRecorder() 92 | r := newServer("") 93 | r.ServeHTTP(w, req) 94 | 95 | assert.Equal(t, w.Code, 200) 96 | } 97 | --------------------------------------------------------------------------------