compress.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. package middleware
  2. import (
  3. "bufio"
  4. "compress/gzip"
  5. "io"
  6. "io/ioutil"
  7. "net"
  8. "net/http"
  9. "strings"
  10. "github.com/labstack/echo"
  11. )
  12. type (
  13. // GzipConfig defines the config for Gzip middleware.
  14. GzipConfig struct {
  15. // Skipper defines a function to skip middleware.
  16. Skipper Skipper
  17. // Gzip compression level.
  18. // Optional. Default value -1.
  19. Level int `yaml:"level"`
  20. }
  21. gzipResponseWriter struct {
  22. io.Writer
  23. http.ResponseWriter
  24. }
  25. )
  26. const (
  27. gzipScheme = "gzip"
  28. )
  29. var (
  30. // DefaultGzipConfig is the default Gzip middleware config.
  31. DefaultGzipConfig = GzipConfig{
  32. Skipper: DefaultSkipper,
  33. Level: -1,
  34. }
  35. )
  36. // Gzip returns a middleware which compresses HTTP response using gzip compression
  37. // scheme.
  38. func Gzip() echo.MiddlewareFunc {
  39. return GzipWithConfig(DefaultGzipConfig)
  40. }
  41. // GzipWithConfig return Gzip middleware with config.
  42. // See: `Gzip()`.
  43. func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
  44. // Defaults
  45. if config.Skipper == nil {
  46. config.Skipper = DefaultGzipConfig.Skipper
  47. }
  48. if config.Level == 0 {
  49. config.Level = DefaultGzipConfig.Level
  50. }
  51. return func(next echo.HandlerFunc) echo.HandlerFunc {
  52. return func(c echo.Context) error {
  53. if config.Skipper(c) {
  54. return next(c)
  55. }
  56. res := c.Response()
  57. res.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding)
  58. if strings.Contains(c.Request().Header.Get(echo.HeaderAcceptEncoding), gzipScheme) {
  59. res.Header().Set(echo.HeaderContentEncoding, gzipScheme) // Issue #806
  60. rw := res.Writer
  61. w, err := gzip.NewWriterLevel(rw, config.Level)
  62. if err != nil {
  63. return err
  64. }
  65. defer func() {
  66. if res.Size == 0 {
  67. if res.Header().Get(echo.HeaderContentEncoding) == gzipScheme {
  68. res.Header().Del(echo.HeaderContentEncoding)
  69. }
  70. // We have to reset response to it's pristine state when
  71. // nothing is written to body or error is returned.
  72. // See issue #424, #407.
  73. res.Writer = rw
  74. w.Reset(ioutil.Discard)
  75. }
  76. w.Close()
  77. }()
  78. grw := &gzipResponseWriter{Writer: w, ResponseWriter: rw}
  79. res.Writer = grw
  80. }
  81. return next(c)
  82. }
  83. }
  84. }
  85. func (w *gzipResponseWriter) WriteHeader(code int) {
  86. if code == http.StatusNoContent { // Issue #489
  87. w.ResponseWriter.Header().Del(echo.HeaderContentEncoding)
  88. }
  89. w.Header().Del(echo.HeaderContentLength) // Issue #444
  90. w.ResponseWriter.WriteHeader(code)
  91. }
  92. func (w *gzipResponseWriter) Write(b []byte) (int, error) {
  93. if w.Header().Get(echo.HeaderContentType) == "" {
  94. w.Header().Set(echo.HeaderContentType, http.DetectContentType(b))
  95. }
  96. return w.Writer.Write(b)
  97. }
  98. func (w *gzipResponseWriter) Flush() {
  99. w.Writer.(*gzip.Writer).Flush()
  100. }
  101. func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
  102. return w.ResponseWriter.(http.Hijacker).Hijack()
  103. }
  104. func (w *gzipResponseWriter) CloseNotify() <-chan bool {
  105. return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
  106. }