1package middleware
2
3import (
4	"bufio"
5	"compress/gzip"
6	"fmt"
7	"net"
8	"net/http"
9	"strings"
10
11	"github.com/grafana/grafana/pkg/web"
12)
13
14type gzipResponseWriter struct {
15	w *gzip.Writer
16	web.ResponseWriter
17}
18
19func (grw *gzipResponseWriter) WriteHeader(c int) {
20	grw.Header().Del("Content-Length")
21	grw.ResponseWriter.WriteHeader(c)
22}
23
24func (grw gzipResponseWriter) Write(p []byte) (int, error) {
25	if grw.Header().Get("Content-Type") == "" {
26		grw.Header().Set("Content-Type", http.DetectContentType(p))
27	}
28	grw.Header().Del("Content-Length")
29	return grw.w.Write(p)
30}
31
32func (grw gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
33	if hijacker, ok := grw.ResponseWriter.(http.Hijacker); ok {
34		return hijacker.Hijack()
35	}
36	return nil, nil, fmt.Errorf("GZIP ResponseWriter doesn't implement the Hijacker interface")
37}
38
39type matcher func(s string) bool
40
41func prefix(p string) matcher { return func(s string) bool { return strings.HasPrefix(s, p) } }
42func substr(p string) matcher { return func(s string) bool { return strings.Contains(s, p) } }
43
44var gzipIgnoredPaths = []matcher{
45	prefix("/api/datasources"),
46	prefix("/api/plugins"),
47	prefix("/api/plugin-proxy/"),
48	prefix("/metrics"),
49	prefix("/api/live/ws"),   // WebSocket does not support gzip compression.
50	prefix("/api/live/push"), // WebSocket does not support gzip compression.
51	substr("/resources"),
52}
53
54func Gziper() func(http.Handler) http.Handler {
55	return func(next http.Handler) http.Handler {
56		return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
57			requestPath := req.URL.RequestURI()
58
59			for _, pathMatcher := range gzipIgnoredPaths {
60				if pathMatcher(requestPath) {
61					next.ServeHTTP(rw, req)
62					return
63				}
64			}
65
66			if !strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
67				next.ServeHTTP(rw, req)
68				return
69			}
70
71			grw := &gzipResponseWriter{gzip.NewWriter(rw), rw.(web.ResponseWriter)}
72			grw.Header().Set("Content-Encoding", "gzip")
73			grw.Header().Set("Vary", "Accept-Encoding")
74
75			next.ServeHTTP(grw, req)
76			// We can't really handle close errors at this point and we can't report them to the caller
77			_ = grw.w.Close()
78		})
79	}
80}
81