compress.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. package tango
  2. import (
  3. "bufio"
  4. "compress/flate"
  5. "compress/gzip"
  6. "fmt"
  7. "io"
  8. "net"
  9. "net/http"
  10. "path"
  11. "strings"
  12. )
  13. const (
  14. HeaderAcceptEncoding = "Accept-Encoding"
  15. HeaderContentEncoding = "Content-Encoding"
  16. HeaderContentLength = "Content-Length"
  17. HeaderContentType = "Content-Type"
  18. HeaderVary = "Vary"
  19. )
  20. type Compresser interface {
  21. CompressType() string
  22. }
  23. type GZip struct {}
  24. func (GZip) CompressType() string {
  25. return "gzip"
  26. }
  27. type Deflate struct {}
  28. func (Deflate) CompressType() string {
  29. return "deflate"
  30. }
  31. type Compress struct {}
  32. func (Compress) CompressType() string {
  33. return "auto"
  34. }
  35. type Compresses struct {
  36. exts map[string]bool
  37. }
  38. func NewCompress(exts []string) *Compresses {
  39. compress := &Compresses{make(map[string]bool)}
  40. for _, ext := range exts {
  41. compress.exts[strings.ToLower(ext)] = true
  42. }
  43. return compress
  44. }
  45. func compress(ctx *Context, compressType string) {
  46. ae := ctx.Req().Header.Get("Accept-Encoding")
  47. acceptCompress := strings.SplitN(ae, ",", -1)
  48. var writer io.Writer
  49. var val string
  50. for _, val = range acceptCompress {
  51. val = strings.TrimSpace(val)
  52. if compressType == "auto" || val == compressType {
  53. if val == "gzip" {
  54. ctx.Header().Set("Content-Encoding", "gzip")
  55. writer = gzip.NewWriter(ctx.ResponseWriter)
  56. break
  57. } else if val == "deflate" {
  58. ctx.Header().Set("Content-Encoding", "deflate")
  59. writer, _ = flate.NewWriter(ctx.ResponseWriter, flate.BestSpeed)
  60. break
  61. }
  62. }
  63. }
  64. // not supported compress method, then ignore
  65. if writer == nil {
  66. ctx.Next()
  67. return
  68. }
  69. // for cache server
  70. ctx.Header().Add(HeaderVary, "Accept-Encoding")
  71. gzw := &compressWriter{writer, ctx.ResponseWriter}
  72. ctx.ResponseWriter = gzw
  73. ctx.Next()
  74. // delete content length after we know we have been written to
  75. gzw.Header().Del(HeaderContentLength)
  76. ctx.ResponseWriter = gzw.ResponseWriter
  77. switch writer.(type) {
  78. case *gzip.Writer:
  79. writer.(*gzip.Writer).Close()
  80. case *flate.Writer:
  81. writer.(*flate.Writer).Close()
  82. }
  83. }
  84. func (c *Compresses) Handle(ctx *Context) {
  85. ae := ctx.Req().Header.Get("Accept-Encoding")
  86. if ae == "" {
  87. ctx.Next()
  88. return
  89. }
  90. if len(c.exts) > 0 {
  91. ext := strings.ToLower(path.Ext(ctx.Req().URL.Path))
  92. if _, ok := c.exts[ext]; ok {
  93. compress(ctx, "auto")
  94. return
  95. }
  96. }
  97. if action := ctx.Action(); action != nil {
  98. if c, ok := action.(Compresser); ok {
  99. compress(ctx, c.CompressType())
  100. return
  101. }
  102. }
  103. // if blank, then no compress
  104. ctx.Next()
  105. }
  106. type compressWriter struct {
  107. w io.Writer
  108. ResponseWriter
  109. }
  110. func (grw *compressWriter) Write(p []byte) (int, error) {
  111. if len(grw.Header().Get(HeaderContentType)) == 0 {
  112. grw.Header().Set(HeaderContentType, http.DetectContentType(p))
  113. }
  114. return grw.w.Write(p)
  115. }
  116. func (grw *compressWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
  117. hijacker, ok := grw.ResponseWriter.(http.Hijacker)
  118. if !ok {
  119. return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface")
  120. }
  121. return hijacker.Hijack()
  122. }