github.com/hellobchain/third_party@v0.0.0-20230331131523-deb0478a2e52/go-chi/chi/middleware/compress.go (about) 1 package middleware 2 3 import ( 4 "bufio" 5 "compress/flate" 6 "compress/gzip" 7 "errors" 8 "io" 9 "net" 10 "strings" 11 12 "github.com/hellobchain/newcryptosm/http" 13 ) 14 15 type encoding int 16 17 const ( 18 encodingNone encoding = iota 19 encodingGzip 20 encodingDeflate 21 ) 22 23 var defaultContentTypes = map[string]struct{}{ 24 "text/html": struct{}{}, 25 "text/css": struct{}{}, 26 "text/plain": struct{}{}, 27 "text/javascript": struct{}{}, 28 "application/javascript": struct{}{}, 29 "application/x-javascript": struct{}{}, 30 "application/json": struct{}{}, 31 "application/atom+xml": struct{}{}, 32 "application/rss+xml": struct{}{}, 33 } 34 35 // DefaultCompress is a middleware that compresses response 36 // body of predefined content types to a data format based 37 // on Accept-Encoding request header. It uses a default 38 // compression level. 39 func DefaultCompress(next http.Handler) http.Handler { 40 return Compress(flate.DefaultCompression)(next) 41 } 42 43 // Compress is a middleware that compresses response 44 // body of a given content types to a data format based 45 // on Accept-Encoding request header. It uses a given 46 // compression level. 47 func Compress(level int, types ...string) func(next http.Handler) http.Handler { 48 contentTypes := defaultContentTypes 49 if len(types) > 0 { 50 contentTypes = make(map[string]struct{}, len(types)) 51 for _, t := range types { 52 contentTypes[t] = struct{}{} 53 } 54 } 55 56 return func(next http.Handler) http.Handler { 57 fn := func(w http.ResponseWriter, r *http.Request) { 58 mcw := &maybeCompressResponseWriter{ 59 ResponseWriter: w, 60 w: w, 61 contentTypes: contentTypes, 62 encoding: selectEncoding(r.Header), 63 level: level, 64 } 65 defer mcw.Close() 66 67 next.ServeHTTP(mcw, r) 68 } 69 70 return http.HandlerFunc(fn) 71 } 72 } 73 74 func selectEncoding(h http.Header) encoding { 75 enc := h.Get("Accept-Encoding") 76 77 switch { 78 // TODO: 79 // case "br": // Brotli, experimental. Firefox 2016, to-be-in Chromium. 80 // case "lzma": // Opera. 81 // case "sdch": // Chrome, Android. Gzip output + dictionary header. 82 83 case strings.Contains(enc, "gzip"): 84 // TODO: Exception for old MSIE browsers that can't handle non-HTML? 85 // https://zoompf.com/blog/2012/02/lose-the-wait-http-compression 86 return encodingGzip 87 88 case strings.Contains(enc, "deflate"): 89 // HTTP 1.1 "deflate" (RFC 2616) stands for DEFLATE data (RFC 1951) 90 // wrapped with zlib (RFC 1950). The zlib wrapper uses Adler-32 91 // checksum compared to CRC-32 used in "gzip" and thus is faster. 92 // 93 // But.. some old browsers (MSIE, Safari 5.1) incorrectly expect 94 // raw DEFLATE data only, without the mentioned zlib wrapper. 95 // Because of this major confusion, most modern browsers try it 96 // both ways, first looking for zlib headers. 97 // Quote by Mark Adler: http://stackoverflow.com/a/9186091/385548 98 // 99 // The list of browsers having problems is quite big, see: 100 // http://zoompf.com/blog/2012/02/lose-the-wait-http-compression 101 // https://web.archive.org/web/20120321182910/http://www.vervestudios.co/projects/compression-tests/results 102 // 103 // That's why we prefer gzip over deflate. It's just more reliable 104 // and not significantly slower than gzip. 105 return encodingDeflate 106 107 // NOTE: Not implemented, intentionally: 108 // case "compress": // LZW. Deprecated. 109 // case "bzip2": // Too slow on-the-fly. 110 // case "zopfli": // Too slow on-the-fly. 111 // case "xz": // Too slow on-the-fly. 112 } 113 114 return encodingNone 115 } 116 117 type maybeCompressResponseWriter struct { 118 http.ResponseWriter 119 w io.Writer 120 encoding encoding 121 contentTypes map[string]struct{} 122 level int 123 wroteHeader bool 124 } 125 126 func (w *maybeCompressResponseWriter) WriteHeader(code int) { 127 if w.wroteHeader { 128 return 129 } 130 w.wroteHeader = true 131 defer w.ResponseWriter.WriteHeader(code) 132 133 // Already compressed data? 134 if w.ResponseWriter.Header().Get("Content-Encoding") != "" { 135 return 136 } 137 // The content-length after compression is unknown 138 w.ResponseWriter.Header().Del("Content-Length") 139 140 // Parse the first part of the Content-Type response header. 141 contentType := "" 142 parts := strings.Split(w.ResponseWriter.Header().Get("Content-Type"), ";") 143 if len(parts) > 0 { 144 contentType = parts[0] 145 } 146 147 // Is the content type compressable? 148 if _, ok := w.contentTypes[contentType]; !ok { 149 return 150 } 151 152 // Select the compress writer. 153 switch w.encoding { 154 case encodingGzip: 155 gw, err := gzip.NewWriterLevel(w.ResponseWriter, w.level) 156 if err != nil { 157 w.w = w.ResponseWriter 158 return 159 } 160 w.w = gw 161 w.ResponseWriter.Header().Set("Content-Encoding", "gzip") 162 163 case encodingDeflate: 164 dw, err := flate.NewWriter(w.ResponseWriter, w.level) 165 if err != nil { 166 w.w = w.ResponseWriter 167 return 168 } 169 w.w = dw 170 w.ResponseWriter.Header().Set("Content-Encoding", "deflate") 171 } 172 } 173 174 func (w *maybeCompressResponseWriter) Write(p []byte) (int, error) { 175 if !w.wroteHeader { 176 w.WriteHeader(http.StatusOK) 177 } 178 179 return w.w.Write(p) 180 } 181 182 func (w *maybeCompressResponseWriter) Flush() { 183 if f, ok := w.w.(http.Flusher); ok { 184 f.Flush() 185 } 186 } 187 188 func (w *maybeCompressResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { 189 if hj, ok := w.w.(http.Hijacker); ok { 190 return hj.Hijack() 191 } 192 return nil, nil, errors.New("chi/middleware: http.Hijacker is unavailable on the writer") 193 } 194 195 func (w *maybeCompressResponseWriter) CloseNotify() <-chan bool { 196 if cn, ok := w.w.(http.CloseNotifier); ok { 197 return cn.CloseNotify() 198 } 199 200 // If the underlying writer does not implement http.CloseNotifier, return 201 // a channel that never receives a value. The semantics here is that the 202 // client never disconnnects before the request is processed by the 203 // http.Handler, which is close enough to the default behavior (when 204 // CloseNotify() is not even called). 205 return make(chan bool, 1) 206 } 207 208 func (w *maybeCompressResponseWriter) Close() error { 209 if c, ok := w.w.(io.WriteCloser); ok { 210 return c.Close() 211 } 212 return errors.New("chi/middleware: io.WriteCloser is unavailable on the writer") 213 }