compress.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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. func Compresses(exts []string) HandlerFunc{
  36. extsmap := make(map[string]bool)
  37. for _, ext := range exts {
  38. extsmap[strings.ToLower(ext)] = true
  39. }
  40. return func(ctx *Context) {
  41. ae := ctx.Req().Header.Get("Accept-Encoding")
  42. if ae == "" {
  43. ctx.Next()
  44. return
  45. }
  46. if len(extsmap) > 0 {
  47. ext := strings.ToLower(path.Ext(ctx.Req().URL.Path))
  48. if _, ok := extsmap[ext]; ok {
  49. compress(ctx, "auto")
  50. return
  51. }
  52. }
  53. if action := ctx.Action(); action != nil {
  54. if c, ok := action.(Compresser); ok {
  55. compress(ctx, c.CompressType())
  56. return
  57. }
  58. }
  59. // if blank, then no compress
  60. ctx.Next()
  61. }
  62. }
  63. func compress(ctx *Context, compressType string) {
  64. ae := ctx.Req().Header.Get("Accept-Encoding")
  65. acceptCompress := strings.SplitN(ae, ",", -1)
  66. var writer io.Writer
  67. var val string
  68. for _, val = range acceptCompress {
  69. val = strings.TrimSpace(val)
  70. if compressType == "auto" || val == compressType {
  71. if val == "gzip" {
  72. ctx.Header().Set("Content-Encoding", "gzip")
  73. writer = gzip.NewWriter(ctx.ResponseWriter)
  74. break
  75. } else if val == "deflate" {
  76. ctx.Header().Set("Content-Encoding", "deflate")
  77. writer, _ = flate.NewWriter(ctx.ResponseWriter, flate.BestSpeed)
  78. break
  79. }
  80. }
  81. }
  82. // not supported compress method, then ignore
  83. if writer == nil {
  84. ctx.Next()
  85. return
  86. }
  87. // for cache server
  88. ctx.Header().Add(HeaderVary, "Accept-Encoding")
  89. gzw := &compressWriter{writer, ctx.ResponseWriter}
  90. ctx.ResponseWriter = gzw
  91. ctx.Next()
  92. // delete content length after we know we have been written to
  93. gzw.Header().Del(HeaderContentLength)
  94. ctx.ResponseWriter = gzw.ResponseWriter
  95. switch writer.(type) {
  96. case *gzip.Writer:
  97. writer.(*gzip.Writer).Close()
  98. case *flate.Writer:
  99. writer.(*flate.Writer).Close()
  100. }
  101. }
  102. type compressWriter struct {
  103. w io.Writer
  104. ResponseWriter
  105. }
  106. func (grw *compressWriter) Write(p []byte) (int, error) {
  107. if len(grw.Header().Get(HeaderContentType)) == 0 {
  108. grw.Header().Set(HeaderContentType, http.DetectContentType(p))
  109. }
  110. return grw.w.Write(p)
  111. }
  112. func (grw *compressWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
  113. hijacker, ok := grw.ResponseWriter.(http.Hijacker)
  114. if !ok {
  115. return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface")
  116. }
  117. return hijacker.Hijack()
  118. }