2015-04-01 02:00:17 -05:00
package middleware
import (
2021-08-10 06:29:46 -05:00
"bufio"
"compress/gzip"
"fmt"
"net"
"net/http"
2015-04-01 02:00:17 -05:00
"strings"
2021-10-11 07:30:59 -05:00
"github.com/grafana/grafana/pkg/web"
2015-04-01 02:00:17 -05:00
)
2021-08-10 06:29:46 -05:00
type gzipResponseWriter struct {
w * gzip . Writer
2021-10-11 07:30:59 -05:00
web . ResponseWriter
2021-08-10 06:29:46 -05:00
}
func ( grw * gzipResponseWriter ) WriteHeader ( c int ) {
grw . Header ( ) . Del ( "Content-Length" )
grw . ResponseWriter . WriteHeader ( c )
}
func ( grw gzipResponseWriter ) Write ( p [ ] byte ) ( int , error ) {
if grw . Header ( ) . Get ( "Content-Type" ) == "" {
grw . Header ( ) . Set ( "Content-Type" , http . DetectContentType ( p ) )
}
grw . Header ( ) . Del ( "Content-Length" )
return grw . w . Write ( p )
}
2020-03-05 12:44:07 -06:00
2021-08-10 06:29:46 -05:00
func ( grw gzipResponseWriter ) Hijack ( ) ( net . Conn , * bufio . ReadWriter , error ) {
if hijacker , ok := grw . ResponseWriter . ( http . Hijacker ) ; ok {
return hijacker . Hijack ( )
}
return nil , nil , fmt . Errorf ( "GZIP ResponseWriter doesn't implement the Hijacker interface" )
}
type matcher func ( s string ) bool
func prefix ( p string ) matcher { return func ( s string ) bool { return strings . HasPrefix ( s , p ) } }
func substr ( p string ) matcher { return func ( s string ) bool { return strings . Contains ( s , p ) } }
var gzipIgnoredPaths = [ ] matcher {
2024-08-21 15:47:58 -05:00
prefix ( "/apis" ) , // apiserver handles its own compression https://github.com/kubernetes/kubernetes/blob/b60e01f881aa8a74b44d0ac1000e4f67f854273b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go#L155-L158
2021-08-10 06:29:46 -05:00
prefix ( "/api/datasources" ) ,
prefix ( "/api/plugins" ) ,
prefix ( "/api/plugin-proxy/" ) ,
2023-07-13 01:51:25 -05:00
prefix ( "/api/gnet/" ) , // Already gzipped by grafana.com.
2021-08-10 06:29:46 -05:00
prefix ( "/metrics" ) ,
prefix ( "/api/live/ws" ) , // WebSocket does not support gzip compression.
prefix ( "/api/live/push" ) , // WebSocket does not support gzip compression.
substr ( "/resources" ) ,
2021-03-25 13:36:52 -05:00
}
2021-08-10 06:29:46 -05:00
func Gziper ( ) func ( http . Handler ) http . Handler {
return func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( rw http . ResponseWriter , req * http . Request ) {
requestPath := req . URL . RequestURI ( )
2015-04-01 02:00:17 -05:00
2021-08-10 06:29:46 -05:00
for _ , pathMatcher := range gzipIgnoredPaths {
if pathMatcher ( requestPath ) {
next . ServeHTTP ( rw , req )
return
}
}
2015-04-01 02:00:17 -05:00
2021-08-10 06:29:46 -05:00
if ! strings . Contains ( req . Header . Get ( "Accept-Encoding" ) , "gzip" ) {
next . ServeHTTP ( rw , req )
2021-03-25 13:36:52 -05:00
return
}
2017-10-23 02:35:46 -05:00
2021-10-11 07:30:59 -05:00
grw := & gzipResponseWriter { gzip . NewWriter ( rw ) , rw . ( web . ResponseWriter ) }
2021-08-10 06:29:46 -05:00
grw . Header ( ) . Set ( "Content-Encoding" , "gzip" )
grw . Header ( ) . Set ( "Vary" , "Accept-Encoding" )
2020-03-05 12:44:07 -06:00
2021-08-10 06:29:46 -05:00
next . ServeHTTP ( grw , req )
// We can't really handle close errors at this point and we can't report them to the caller
_ = grw . w . Close ( )
} )
2015-04-01 02:00:17 -05:00
}
}