From 1ee3bc23e350642d1cf988d4b8cc21fb29cd0177 Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Fri, 15 May 2015 12:29:14 -0700 Subject: [PATCH] Added gzip middleware Signed-off-by: Vishal Rana --- README.md | 2 +- context.go | 8 ++-- echo.go | 2 + examples/crud/server.go | 2 +- examples/hello/server.go | 2 +- examples/middleware/server.go | 5 +- examples/web/server.go | 2 +- middleware/auth.go | 2 +- middleware/compress.go | 43 ++++++++++++++++++ middleware/compress_test.go | 42 +++++++++++++++++ middleware/logger.go | 48 +++++++++++--------- middleware/{middleware.go => logger_test.go} | 0 response.go | 14 +++--- website/docs/guide.md | 4 +- website/docs/index.md | 4 +- 15 files changed, 138 insertions(+), 42 deletions(-) create mode 100644 middleware/compress.go create mode 100644 middleware/compress_test.go rename middleware/{middleware.go => logger_test.go} (100%) diff --git a/README.md b/README.md index cb2f6aaf..c0a9fad7 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ func main() { e := echo.New() // Middleware - e.Use(mw.Logger) + e.Use(mw.Logger()) // Routes e.Get("/", hello) diff --git a/context.go b/context.go index 8921a688..7cd57156 100644 --- a/context.go +++ b/context.go @@ -53,8 +53,8 @@ func (c *Context) Param(name string) (value string) { // Bind binds the request body into specified type v. Default binder does it // based on Content-Type header. -func (c *Context) Bind(v interface{}) *HTTPError { - return c.echo.binder(c.Request, v) +func (c *Context) Bind(i interface{}) *HTTPError { + return c.echo.binder(c.Request, i) } // Render invokes the registered HTML template renderer and sends a text/html @@ -69,10 +69,10 @@ func (c *Context) Render(code int, name string, data interface{}) *HTTPError { } // JSON sends an application/json response with status code. -func (c *Context) JSON(code int, v interface{}) *HTTPError { +func (c *Context) JSON(code int, i interface{}) *HTTPError { c.Response.Header().Set(ContentType, ApplicationJSON+"; charset=utf-8") c.Response.WriteHeader(code) - if err := json.NewEncoder(c.Response).Encode(v); err != nil { + if err := json.NewEncoder(c.Response).Encode(i); err != nil { return &HTTPError{Error: err} } return nil diff --git a/echo.go b/echo.go index 6a16b514..38bad2d6 100644 --- a/echo.go +++ b/echo.go @@ -90,7 +90,9 @@ const ( //--------- Accept = "Accept" + AcceptEncoding = "Accept-Encoding" ContentDisposition = "Content-Disposition" + ContentEncoding = "Content-Encoding" ContentLength = "Content-Length" ContentType = "Content-Type" Authorization = "Authorization" diff --git a/examples/crud/server.go b/examples/crud/server.go index 7de1b275..3c26dbff 100644 --- a/examples/crud/server.go +++ b/examples/crud/server.go @@ -61,7 +61,7 @@ func main() { e := echo.New() // Middleware - e.Use(mw.Logger) + e.Use(mw.Logger()) // Routes e.Post("/users", createUser) diff --git a/examples/hello/server.go b/examples/hello/server.go index b704bbf1..15410f54 100644 --- a/examples/hello/server.go +++ b/examples/hello/server.go @@ -17,7 +17,7 @@ func main() { e := echo.New() // Middleware - e.Use(mw.Logger) + e.Use(mw.Logger()) // Routes e.Get("/", hello) diff --git a/examples/middleware/server.go b/examples/middleware/server.go index 48be4fc0..af51e095 100644 --- a/examples/middleware/server.go +++ b/examples/middleware/server.go @@ -21,7 +21,7 @@ func main() { //------------ // Logger - e.Use(mw.Logger) + e.Use(mw.Logger()) // Basic auth e.Use(mw.BasicAuth(func(u, p string) bool { @@ -41,6 +41,9 @@ func main() { // e.Use(mw.RedirectToSlash()) + // Gzip + e.Use(mw.Gzip()) + // Routes e.Get("/", hello) diff --git a/examples/web/server.go b/examples/web/server.go index ba321230..0331d058 100644 --- a/examples/web/server.go +++ b/examples/web/server.go @@ -65,7 +65,7 @@ func main() { e := echo.New() // Middleware - e.Use(mw.Logger) + e.Use(mw.Logger()) //------------------------ // Third-party middleware diff --git a/middleware/auth.go b/middleware/auth.go index 88858235..3d3f6697 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -14,7 +14,7 @@ const ( Basic = "Basic" ) -// BasicAuth provides HTTP basic authentication middleware. +// BasicAuth provides HTTP basic authentication. func BasicAuth(fn AuthFunc) echo.HandlerFunc { return func(c *echo.Context) (he *echo.HTTPError) { auth := c.Request.Header.Get(echo.Authorization) diff --git a/middleware/compress.go b/middleware/compress.go new file mode 100644 index 00000000..f73ceb81 --- /dev/null +++ b/middleware/compress.go @@ -0,0 +1,43 @@ +package middleware + +import ( + "compress/gzip" + "io" + "strings" + + "github.com/labstack/echo" +) + +type ( + gzipResponseWriter struct { + io.Writer + *echo.Response + } +) + +func (g gzipResponseWriter) Write(b []byte) (int, error) { + return g.Writer.Write(b) +} + +// Gzip compresses HTTP response using gzip compression scheme. +func Gzip() echo.MiddlewareFunc { + scheme := "gzip" + + return func(h echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) *echo.HTTPError { + if !strings.Contains(c.Request.Header.Get(echo.AcceptEncoding), scheme) { + return nil + } + + w := gzip.NewWriter(c.Response.Writer) + defer w.Close() + gw := gzipResponseWriter{Writer: w, Response: c.Response} + c.Response.Header().Set(echo.ContentEncoding, scheme) + c.Response = &echo.Response{Writer: gw} + if he := h(c); he != nil { + c.Error(he) + } + return nil + } + } +} diff --git a/middleware/compress_test.go b/middleware/compress_test.go new file mode 100644 index 00000000..53aac820 --- /dev/null +++ b/middleware/compress_test.go @@ -0,0 +1,42 @@ +package middleware + +import ( + "net/http" + "net/http/httptest" + "testing" + + "compress/gzip" + "github.com/labstack/echo" + "io/ioutil" +) + +func TestGzip(t *testing.T) { + req, _ := http.NewRequest(echo.GET, "/", nil) + req.Header.Set(echo.AcceptEncoding, "gzip") + w := httptest.NewRecorder() + res := &echo.Response{Writer: w} + c := echo.NewContext(req, res, echo.New()) + Gzip()(func(c *echo.Context) *echo.HTTPError { + return c.String(http.StatusOK, "test") + })(c) + + if w.Header().Get(echo.ContentEncoding) != "gzip" { + t.Errorf("expected Content-Encoding: gzip, got %d", w.Header().Get(echo.ContentEncoding)) + } + + r, err := gzip.NewReader(w.Body) + defer r.Close() + if err != nil { + t.Error(err) + } + + b, err := ioutil.ReadAll(r) + if err != nil { + t.Error(err) + } + s := string(b) + + if s != "test" { + t.Errorf(`expected "test", got "%s"`, s) + } +} diff --git a/middleware/logger.go b/middleware/logger.go index 04fa673f..7be738c5 100644 --- a/middleware/logger.go +++ b/middleware/logger.go @@ -8,28 +8,34 @@ import ( "github.com/labstack/gommon/color" ) -func Logger(h echo.HandlerFunc) echo.HandlerFunc { - return func(c *echo.Context) *echo.HTTPError { - start := time.Now() - if he := h(c); he != nil { - c.Error(he) - } - end := time.Now() - m := c.Request.Method - p := c.Request.URL.Path - n := c.Response.Status() - col := color.Green +func Logger() echo.MiddlewareFunc { + return func(h echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) *echo.HTTPError { + start := time.Now() + if he := h(c); he != nil { + c.Error(he) + } + end := time.Now() + method := c.Request.Method + path := c.Request.URL.Path + if path == "" { + path = "/" + } + size := c.Response.Size() - switch { - case n >= 500: - col = color.Red - case n >= 400: - col = color.Yellow - case n >= 300: - col = color.Cyan - } + n := c.Response.Status() + code := color.Green(n) + switch { + case n >= 500: + code = color.Red(n) + case n >= 400: + code = color.Yellow(n) + case n >= 300: + code = color.Cyan(n) + } - log.Printf("%s %s %s %s", m, p, col(n), end.Sub(start)) - return nil + log.Printf("%s %s %s %s %d", method, path, code, end.Sub(start), size) + return nil + } } } diff --git a/middleware/middleware.go b/middleware/logger_test.go similarity index 100% rename from middleware/middleware.go rename to middleware/logger_test.go diff --git a/response.go b/response.go index 0e446bf9..9a218c13 100644 --- a/response.go +++ b/response.go @@ -10,8 +10,8 @@ import ( type ( Response struct { Writer http.ResponseWriter - status int - size int + status int + size uint64 committed bool } ) @@ -20,20 +20,20 @@ func (r *Response) Header() http.Header { return r.Writer.Header() } -func (r *Response) WriteHeader(n int) { +func (r *Response) WriteHeader(code int) { if r.committed { // TODO: Warning log.Printf("echo: %s", color.Yellow("response already committed")) return } - r.status = n - r.Writer.WriteHeader(n) + r.status = code + r.Writer.WriteHeader(code) r.committed = true } func (r *Response) Write(b []byte) (n int, err error) { n, err = r.Writer.Write(b) - r.size += n + r.size += uint64(n) return n, err } @@ -41,7 +41,7 @@ func (r *Response) Status() int { return r.status } -func (r *Response) Size() int { +func (r *Response) Size() uint64 { return r.size } diff --git a/website/docs/guide.md b/website/docs/guide.md index a3626131..db92b4ae 100644 --- a/website/docs/guide.md +++ b/website/docs/guide.md @@ -163,9 +163,9 @@ h := func(*echo.Context) *HTTPError { e.Get("/users/:id", h) ``` -## (Middleware)[https://github.com/labstack/echo/tree/master/examples/middleware] +## Middleware -*WIP* +[*WIP*](https://github.com/labstack/echo/tree/master/examples/middleware) ## Response diff --git a/website/docs/index.md b/website/docs/index.md index 7baeb5ba..fb69d1b5 100644 --- a/website/docs/index.md +++ b/website/docs/index.md @@ -66,7 +66,7 @@ func main() { e := echo.New() // Middleware - e.Use(mw.Logger) + e.Use(mw.Logger()) // Routes e.Get("/", hello) @@ -78,7 +78,7 @@ func main() { `echo.New()` returns a new instance of Echo. -`e.Use(mw.Logger)` adds logging middleware to the chain. It logs every HTTP request +`e.Use(mw.Logger())` adds logging middleware to the chain. It logs every HTTP request made to the server, producing output ```sh