brotli.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. package fasthttp
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "sync"
  7. "github.com/andybalholm/brotli"
  8. "github.com/valyala/bytebufferpool"
  9. "github.com/valyala/fasthttp/stackless"
  10. )
  11. // Supported compression levels.
  12. const (
  13. CompressBrotliNoCompression = 0
  14. CompressBrotliBestSpeed = brotli.BestSpeed
  15. CompressBrotliBestCompression = brotli.BestCompression
  16. // Choose a default brotli compression level comparable to
  17. // CompressDefaultCompression (gzip 6)
  18. // See: https://github.com/valyala/fasthttp/issues/798#issuecomment-626293806
  19. CompressBrotliDefaultCompression = 4
  20. )
  21. func acquireBrotliReader(r io.Reader) (*brotli.Reader, error) {
  22. v := brotliReaderPool.Get()
  23. if v == nil {
  24. return brotli.NewReader(r), nil
  25. }
  26. zr := v.(*brotli.Reader)
  27. if err := zr.Reset(r); err != nil {
  28. return nil, err
  29. }
  30. return zr, nil
  31. }
  32. func releaseBrotliReader(zr *brotli.Reader) {
  33. brotliReaderPool.Put(zr)
  34. }
  35. var brotliReaderPool sync.Pool
  36. func acquireStacklessBrotliWriter(w io.Writer, level int) stackless.Writer {
  37. nLevel := normalizeBrotliCompressLevel(level)
  38. p := stacklessBrotliWriterPoolMap[nLevel]
  39. v := p.Get()
  40. if v == nil {
  41. return stackless.NewWriter(w, func(w io.Writer) stackless.Writer {
  42. return acquireRealBrotliWriter(w, level)
  43. })
  44. }
  45. sw := v.(stackless.Writer)
  46. sw.Reset(w)
  47. return sw
  48. }
  49. func releaseStacklessBrotliWriter(sw stackless.Writer, level int) {
  50. sw.Close()
  51. nLevel := normalizeBrotliCompressLevel(level)
  52. p := stacklessBrotliWriterPoolMap[nLevel]
  53. p.Put(sw)
  54. }
  55. func acquireRealBrotliWriter(w io.Writer, level int) *brotli.Writer {
  56. nLevel := normalizeBrotliCompressLevel(level)
  57. p := realBrotliWriterPoolMap[nLevel]
  58. v := p.Get()
  59. if v == nil {
  60. zw := brotli.NewWriterLevel(w, level)
  61. return zw
  62. }
  63. zw := v.(*brotli.Writer)
  64. zw.Reset(w)
  65. return zw
  66. }
  67. func releaseRealBrotliWriter(zw *brotli.Writer, level int) {
  68. zw.Close()
  69. nLevel := normalizeBrotliCompressLevel(level)
  70. p := realBrotliWriterPoolMap[nLevel]
  71. p.Put(zw)
  72. }
  73. var (
  74. stacklessBrotliWriterPoolMap = newCompressWriterPoolMap()
  75. realBrotliWriterPoolMap = newCompressWriterPoolMap()
  76. )
  77. // AppendBrotliBytesLevel appends brotlied src to dst using the given
  78. // compression level and returns the resulting dst.
  79. //
  80. // Supported compression levels are:
  81. //
  82. // - CompressBrotliNoCompression
  83. // - CompressBrotliBestSpeed
  84. // - CompressBrotliBestCompression
  85. // - CompressBrotliDefaultCompression
  86. func AppendBrotliBytesLevel(dst, src []byte, level int) []byte {
  87. w := &byteSliceWriter{dst}
  88. WriteBrotliLevel(w, src, level) //nolint:errcheck
  89. return w.b
  90. }
  91. // WriteBrotliLevel writes brotlied p to w using the given compression level
  92. // and returns the number of compressed bytes written to w.
  93. //
  94. // Supported compression levels are:
  95. //
  96. // - CompressBrotliNoCompression
  97. // - CompressBrotliBestSpeed
  98. // - CompressBrotliBestCompression
  99. // - CompressBrotliDefaultCompression
  100. func WriteBrotliLevel(w io.Writer, p []byte, level int) (int, error) {
  101. switch w.(type) {
  102. case *byteSliceWriter,
  103. *bytes.Buffer,
  104. *bytebufferpool.ByteBuffer:
  105. // These writers don't block, so we can just use stacklessWriteBrotli
  106. ctx := &compressCtx{
  107. w: w,
  108. p: p,
  109. level: level,
  110. }
  111. stacklessWriteBrotli(ctx)
  112. return len(p), nil
  113. default:
  114. zw := acquireStacklessBrotliWriter(w, level)
  115. n, err := zw.Write(p)
  116. releaseStacklessBrotliWriter(zw, level)
  117. return n, err
  118. }
  119. }
  120. var (
  121. stacklessWriteBrotliOnce sync.Once
  122. stacklessWriteBrotliFunc func(ctx any) bool
  123. )
  124. func stacklessWriteBrotli(ctx any) {
  125. stacklessWriteBrotliOnce.Do(func() {
  126. stacklessWriteBrotliFunc = stackless.NewFunc(nonblockingWriteBrotli)
  127. })
  128. stacklessWriteBrotliFunc(ctx)
  129. }
  130. func nonblockingWriteBrotli(ctxv any) {
  131. ctx := ctxv.(*compressCtx)
  132. zw := acquireRealBrotliWriter(ctx.w, ctx.level)
  133. zw.Write(ctx.p) //nolint:errcheck // no way to handle this error anyway
  134. releaseRealBrotliWriter(zw, ctx.level)
  135. }
  136. // WriteBrotli writes brotlied p to w and returns the number of compressed
  137. // bytes written to w.
  138. func WriteBrotli(w io.Writer, p []byte) (int, error) {
  139. return WriteBrotliLevel(w, p, CompressBrotliDefaultCompression)
  140. }
  141. // AppendBrotliBytes appends brotlied src to dst and returns the resulting dst.
  142. func AppendBrotliBytes(dst, src []byte) []byte {
  143. return AppendBrotliBytesLevel(dst, src, CompressBrotliDefaultCompression)
  144. }
  145. // WriteUnbrotli writes unbrotlied p to w and returns the number of uncompressed
  146. // bytes written to w.
  147. func WriteUnbrotli(w io.Writer, p []byte) (int, error) {
  148. r := &byteSliceReader{p}
  149. zr, err := acquireBrotliReader(r)
  150. if err != nil {
  151. return 0, err
  152. }
  153. n, err := copyZeroAlloc(w, zr)
  154. releaseBrotliReader(zr)
  155. nn := int(n)
  156. if int64(nn) != n {
  157. return 0, fmt.Errorf("too much data unbrotlied: %d", n)
  158. }
  159. return nn, err
  160. }
  161. // AppendUnbrotliBytes appends unbrotlied src to dst and returns the resulting dst.
  162. func AppendUnbrotliBytes(dst, src []byte) ([]byte, error) {
  163. w := &byteSliceWriter{dst}
  164. _, err := WriteUnbrotli(w, src)
  165. return w.b, err
  166. }
  167. // normalizes compression level into [0..11], so it could be used as an index
  168. // in *PoolMap.
  169. func normalizeBrotliCompressLevel(level int) int {
  170. // -2 is the lowest compression level - CompressHuffmanOnly
  171. // 9 is the highest compression level - CompressBestCompression
  172. if level < 0 || level > 11 {
  173. level = CompressBrotliDefaultCompression
  174. }
  175. return level
  176. }