You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

120 lines
3.1KB

  1. package middleware
  2. import (
  3. "bytes"
  4. "compress/flate"
  5. "compress/gzip"
  6. "io"
  7. "net/http"
  8. "regexp"
  9. "strings"
  10. )
  11. var (
  12. regexAcceptEncoding = regexp.MustCompile(`(gzip|deflate|\*)(;q=(1(\.0)?|0(\.[0-9])?))?`)
  13. regexContentType = regexp.MustCompile(`((message|text)\/.+)|((application\/).*(javascript|json|xml))`)
  14. )
  15. type compression interface {
  16. io.WriteCloser
  17. }
  18. type compressor struct {
  19. http.ResponseWriter
  20. encodings []string
  21. }
  22. // AcceptEncodings fetches the requested encodings from client with priority.
  23. func (self *compressor) acceptEncodings(request *http.Request) (encodings []string) {
  24. // find all encodings supported by backend server.
  25. matches := regexAcceptEncoding.FindAllString(request.Header.Get("Accept-Encoding"), -1)
  26. for _, item := range matches {
  27. units := strings.SplitN(item, ";", 2)
  28. // top priority with q=1|q=1.0|Not Specified.
  29. if len(units) == 1 {
  30. encodings = append(encodings, units[0])
  31. } else {
  32. if strings.HasPrefix(units[1], "q=1") {
  33. // insert the specified top priority to the first.
  34. encodings = append([]string{units[0]}, encodings...)
  35. } else if strings.HasSuffix(units[1], "0") {
  36. // not acceptable at client side.
  37. continue
  38. } else {
  39. // lower priority encoding
  40. encodings = append(encodings, units[0])
  41. }
  42. }
  43. }
  44. return
  45. }
  46. func (self *compressor) filter(src []byte) ([]byte, string) {
  47. var mimetype = self.Header().Get("Content-Type")
  48. if mimetype == "" {
  49. mimetype = http.DetectContentType(src)
  50. self.Header().Set("Content-Type", mimetype)
  51. }
  52. if self.Header().Get("Content-Encoding") != "" {
  53. return src, ""
  54. }
  55. if !regexContentType.MatchString(strings.TrimSpace(strings.SplitN(mimetype, ";", 2)[0])) {
  56. return src, ""
  57. }
  58. // okay to start compressing.
  59. var e error
  60. var encoding string
  61. var writer compression
  62. var buffer *bytes.Buffer = new(bytes.Buffer)
  63. // try compress the data, if any error occrued, fallback to ResponseWriter.
  64. if self.encodings[0] == "deflate" {
  65. encoding = "deflate"
  66. writer, e = flate.NewWriter(buffer, flate.DefaultCompression)
  67. } else {
  68. encoding = "gzip"
  69. writer, e = gzip.NewWriterLevel(buffer, gzip.DefaultCompression)
  70. }
  71. _, e = writer.Write(src)
  72. writer.Close()
  73. if e == nil {
  74. return buffer.Bytes(), encoding
  75. }
  76. // fallback to standard http.ResponseWriter, nothing happened~ (~__~"")
  77. return src, ""
  78. }
  79. func (self *compressor) Write(data []byte) (size int, err error) {
  80. if bytes, encoding := self.filter(data); encoding != "" {
  81. self.Header().Set("Content-Encoding", encoding)
  82. self.Header().Add("Vary", "Accept-Encoding")
  83. self.Header().Del("Content-Length")
  84. return self.ResponseWriter.Write(bytes)
  85. }
  86. return self.ResponseWriter.Write(data)
  87. }
  88. // GZIP/Deflate compression supports.
  89. func Compress(next http.Handler) http.Handler {
  90. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  91. if r.Header.Get("Sec-WebSocket-Key") != "" || r.Method == "HEAD" {
  92. next.ServeHTTP(w, r)
  93. } else {
  94. compressor := new(compressor)
  95. compressor.ResponseWriter = w
  96. encodings := compressor.acceptEncodings(r)
  97. if len(encodings) == 0 {
  98. next.ServeHTTP(w, r)
  99. } else {
  100. compressor.encodings = encodings
  101. next.ServeHTTP(compressor, r)
  102. }
  103. }
  104. })
  105. }