Parcourir la source

http/response: add brotli compression support

Frédéric Guillot il y a 1 an
Parent
commit
2c4c845cd2

+ 1 - 1
go.mod

@@ -5,6 +5,7 @@ module miniflux.app/v2
 require (
 	github.com/PuerkitoBio/goquery v1.9.1
 	github.com/abadojack/whatlanggo v1.0.1
+	github.com/andybalholm/brotli v1.1.0
 	github.com/coreos/go-oidc/v3 v3.10.0
 	github.com/go-webauthn/webauthn v0.10.2
 	github.com/gorilla/mux v1.8.1
@@ -27,7 +28,6 @@ require (
 )
 
 require (
-	github.com/andybalholm/brotli v1.1.0 // indirect
 	github.com/andybalholm/cascadia v1.3.2 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect

+ 10 - 1
internal/http/response/builder.go

@@ -12,6 +12,8 @@ import (
 	"net/http"
 	"strings"
 	"time"
+
+	"github.com/andybalholm/brotli"
 )
 
 const compressionThreshold = 1024
@@ -110,8 +112,15 @@ func (b *Builder) writeHeaders() {
 func (b *Builder) compress(data []byte) {
 	if b.enableCompression && len(data) > compressionThreshold {
 		acceptEncoding := b.r.Header.Get("Accept-Encoding")
-
 		switch {
+		case strings.Contains(acceptEncoding, "br"):
+			b.headers["Content-Encoding"] = "br"
+			b.writeHeaders()
+
+			brotliWriter := brotli.NewWriterV2(b.w, brotli.DefaultCompression)
+			defer brotliWriter.Close()
+			brotliWriter.Write(data)
+			return
 		case strings.Contains(acceptEncoding, "gzip"):
 			b.headers["Content-Encoding"] = "gzip"
 			b.writeHeaders()

+ 25 - 1
internal/http/response/builder_test.go

@@ -228,7 +228,7 @@ func TestBuildResponseWithCachingAndEtag(t *testing.T) {
 	}
 }
 
-func TestBuildResponseWithGzipCompression(t *testing.T) {
+func TestBuildResponseWithBrotliCompression(t *testing.T) {
 	body := strings.Repeat("a", compressionThreshold+1)
 	r, err := http.NewRequest("GET", "/", nil)
 	r.Header.Set("Accept-Encoding", "gzip, deflate, br")
@@ -245,6 +245,30 @@ func TestBuildResponseWithGzipCompression(t *testing.T) {
 	handler.ServeHTTP(w, r)
 	resp := w.Result()
 
+	expected := "br"
+	actual := resp.Header.Get("Content-Encoding")
+	if actual != expected {
+		t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
+	}
+}
+
+func TestBuildResponseWithGzipCompression(t *testing.T) {
+	body := strings.Repeat("a", compressionThreshold+1)
+	r, err := http.NewRequest("GET", "/", nil)
+	r.Header.Set("Accept-Encoding", "gzip, deflate")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		New(w, r).WithBody(body).Write()
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
 	expected := "gzip"
 	actual := resp.Header.Get("Content-Encoding")
 	if actual != expected {

+ 5 - 3
internal/reader/fetcher/response_handler.go

@@ -13,6 +13,7 @@ import (
 	"net/http"
 	"net/url"
 	"os"
+	"strings"
 
 	"miniflux.app/v2/internal/locale"
 )
@@ -73,15 +74,16 @@ func (r *ResponseHandler) Close() {
 }
 
 func (r *ResponseHandler) getReader(maxBodySize int64) io.ReadCloser {
+	contentEncoding := strings.ToLower(r.httpResponse.Header.Get("Content-Encoding"))
 	slog.Debug("Request response",
 		slog.String("effective_url", r.EffectiveURL()),
-		slog.Int64("content_length", r.httpResponse.ContentLength),
-		slog.String("content_encoding", r.httpResponse.Header.Get("Content-Encoding")),
+		slog.String("content_length", r.httpResponse.Header.Get("Content-Length")),
+		slog.String("content_encoding", contentEncoding),
 		slog.String("content_type", r.httpResponse.Header.Get("Content-Type")),
 	)
 
 	reader := r.httpResponse.Body
-	switch r.httpResponse.Header.Get("Content-Encoding") {
+	switch contentEncoding {
 	case "br":
 		reader = NewBrotliReadCloser(r.httpResponse.Body)
 	case "gzip":

+ 3 - 1
internal/ui/feed_icon.go

@@ -29,7 +29,9 @@ func (h *handler) showIcon(w http.ResponseWriter, r *http.Request) {
 		b.WithHeader("Content-Security-Policy", `default-src 'self'`)
 		b.WithHeader("Content-Type", icon.MimeType)
 		b.WithBody(icon.Content)
-		b.WithoutCompression()
+		if icon.MimeType != "image/svg+xml" {
+			b.WithoutCompression()
+		}
 		b.Write()
 	})
 }

+ 1 - 1
internal/ui/static_app_icon.go

@@ -31,12 +31,12 @@ func (h *handler) showAppIcon(w http.ResponseWriter, r *http.Request) {
 
 		switch filepath.Ext(filename) {
 		case ".png":
+			b.WithoutCompression()
 			b.WithHeader("Content-Type", "image/png")
 		case ".svg":
 			b.WithHeader("Content-Type", "image/svg+xml")
 		}
 
-		b.WithoutCompression()
 		b.WithBody(blob)
 		b.Write()
 	})