gzip_response_writer.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. package context
  2. import (
  3. "fmt"
  4. "io"
  5. "sync"
  6. "github.com/klauspost/compress/gzip"
  7. )
  8. // compressionPool is a wrapper of sync.Pool, to initialize a new compression writer pool
  9. type compressionPool struct {
  10. sync.Pool
  11. Level int
  12. }
  13. // +------------------------------------------------------------+
  14. // |GZIP raw io.writer, our gzip response writer will use that. |
  15. // +------------------------------------------------------------+
  16. // default writer pool with Compressor's level setted to -1
  17. var gzipPool = &compressionPool{Level: -1}
  18. // acquireGzipWriter prepares a gzip writer and returns it.
  19. //
  20. // see releaseGzipWriter too.
  21. func acquireGzipWriter(w io.Writer) *gzip.Writer {
  22. v := gzipPool.Get()
  23. if v == nil {
  24. gzipWriter, err := gzip.NewWriterLevel(w, gzipPool.Level)
  25. if err != nil {
  26. return nil
  27. }
  28. return gzipWriter
  29. }
  30. gzipWriter := v.(*gzip.Writer)
  31. gzipWriter.Reset(w)
  32. return gzipWriter
  33. }
  34. // releaseGzipWriter called when flush/close and put the gzip writer back to the pool.
  35. //
  36. // see acquireGzipWriter too.
  37. func releaseGzipWriter(gzipWriter *gzip.Writer) {
  38. gzipWriter.Close()
  39. gzipPool.Put(gzipWriter)
  40. }
  41. // writeGzip writes a compressed form of p to the underlying io.Writer. The
  42. // compressed bytes are not necessarily flushed until the Writer is closed.
  43. func writeGzip(w io.Writer, b []byte) (int, error) {
  44. gzipWriter := acquireGzipWriter(w)
  45. n, err := gzipWriter.Write(b)
  46. if err != nil {
  47. releaseGzipWriter(gzipWriter)
  48. return -1, err
  49. }
  50. err = gzipWriter.Flush()
  51. releaseGzipWriter(gzipWriter)
  52. return n, err
  53. }
  54. var gzpool = sync.Pool{New: func() interface{} { return &GzipResponseWriter{} }}
  55. // AcquireGzipResponseWriter returns a new *GzipResponseWriter from the pool.
  56. // Releasing is done automatically when request and response is done.
  57. func AcquireGzipResponseWriter() *GzipResponseWriter {
  58. w := gzpool.Get().(*GzipResponseWriter)
  59. return w
  60. }
  61. func releaseGzipResponseWriter(w *GzipResponseWriter) {
  62. gzpool.Put(w)
  63. }
  64. // GzipResponseWriter is an upgraded response writer which writes compressed data to the underline ResponseWriter.
  65. //
  66. // It's a separate response writer because iris gives you the ability to "fallback" and "roll-back" the gzip encoding if something
  67. // went wrong with the response, and write http errors in plain form instead.
  68. type GzipResponseWriter struct {
  69. ResponseWriter
  70. chunks []byte
  71. disabled bool
  72. }
  73. var _ ResponseWriter = (*GzipResponseWriter)(nil)
  74. // BeginGzipResponse accepts a ResponseWriter
  75. // and prepares the new gzip response writer.
  76. // It's being called per-handler, when caller decide
  77. // to change the response writer type.
  78. func (w *GzipResponseWriter) BeginGzipResponse(underline ResponseWriter) {
  79. w.ResponseWriter = underline
  80. w.chunks = w.chunks[0:0]
  81. w.disabled = false
  82. }
  83. // EndResponse called right before the contents of this
  84. // response writer are flushed to the client.
  85. func (w *GzipResponseWriter) EndResponse() {
  86. releaseGzipResponseWriter(w)
  87. w.ResponseWriter.EndResponse()
  88. }
  89. // Write prepares the data write to the gzip writer and finally to its
  90. // underline response writer, returns the uncompressed len(contents).
  91. func (w *GzipResponseWriter) Write(contents []byte) (int, error) {
  92. // save the contents to serve them (only gzip data here)
  93. w.chunks = append(w.chunks, contents...)
  94. return len(contents), nil
  95. }
  96. // Writef formats according to a format specifier and writes to the response.
  97. //
  98. // Returns the number of bytes written and any write error encountered.
  99. func (w *GzipResponseWriter) Writef(format string, a ...interface{}) (n int, err error) {
  100. n, err = fmt.Fprintf(w, format, a...)
  101. if err == nil {
  102. if w.ResponseWriter.Header()[ContentTypeHeaderKey] == nil {
  103. w.ResponseWriter.Header().Set(ContentTypeHeaderKey, ContentTextHeaderValue)
  104. }
  105. }
  106. return
  107. }
  108. // WriteString prepares the string data write to the gzip writer and finally to its
  109. // underline response writer, returns the uncompressed len(contents).
  110. func (w *GzipResponseWriter) WriteString(s string) (n int, err error) {
  111. n, err = w.Write([]byte(s))
  112. if err == nil {
  113. if w.ResponseWriter.Header()[ContentTypeHeaderKey] == nil {
  114. w.ResponseWriter.Header().Set(ContentTypeHeaderKey, ContentTextHeaderValue)
  115. }
  116. }
  117. return
  118. }
  119. // WriteNow compresses and writes that data to the underline response writer,
  120. // returns the compressed written len.
  121. //
  122. // Use `WriteNow` instead of `Write`
  123. // when you need to know the compressed written size before
  124. // the `FlushResponse`, note that you can't post any new headers
  125. // after that, so that information is not closed to the handler anymore.
  126. func (w *GzipResponseWriter) WriteNow(contents []byte) (int, error) {
  127. if w.disabled {
  128. // type noOp struct{}
  129. //
  130. // func (n noOp) Write([]byte) (int, error) {
  131. // return 0, nil
  132. // }
  133. //
  134. // var noop = noOp{}
  135. // problem solved with w.gzipWriter.Reset(noop):
  136. //
  137. // the below Write called multiple times but not from here,
  138. // the gzip writer does something to the writer, even if we don't call the
  139. // w.gzipWriter.Write it does call the underline http.ResponseWriter
  140. // multiple times, and therefore it changes the content-length
  141. // the problem that results to the #723.
  142. //
  143. // Or a better idea, acquire and adapt the gzip writer on-time when is not disabled.
  144. // So that is not needed any more:
  145. // w.gzipWriter.Reset(noop)
  146. return w.ResponseWriter.Write(contents)
  147. }
  148. AddGzipHeaders(w.ResponseWriter)
  149. // if not `WriteNow` but "Content-Length" header
  150. // is exists, then delete it before `.Write`
  151. // Content-Length should not be there.
  152. // no, for now at least: w.ResponseWriter.Header().Del(contentLengthHeaderKey)
  153. return writeGzip(w.ResponseWriter, contents)
  154. }
  155. // AddGzipHeaders just adds the headers "Vary" to "Accept-Encoding"
  156. // and "Content-Encoding" to "gzip".
  157. func AddGzipHeaders(w ResponseWriter) {
  158. w.Header().Add(VaryHeaderKey, AcceptEncodingHeaderKey)
  159. w.Header().Add(ContentEncodingHeaderKey, GzipHeaderValue)
  160. }
  161. // FlushResponse validates the response headers in order to be compatible with the gzip written data
  162. // and writes the data to the underline ResponseWriter.
  163. func (w *GzipResponseWriter) FlushResponse() {
  164. w.WriteNow(w.chunks)
  165. w.ResponseWriter.FlushResponse()
  166. }
  167. // ResetBody resets the response body.
  168. func (w *GzipResponseWriter) ResetBody() {
  169. w.chunks = w.chunks[0:0]
  170. }
  171. // Disable turns off the gzip compression for the next .Write's data,
  172. // if called then the contents are being written in plain form.
  173. func (w *GzipResponseWriter) Disable() {
  174. w.disabled = true
  175. }