http/response: add brotli compression support
This commit is contained in:
parent
2caabbe939
commit
2c4c845cd2
6 changed files with 45 additions and 8 deletions
2
go.mod
2
go.mod
|
@ -5,6 +5,7 @@ module miniflux.app/v2
|
||||||
require (
|
require (
|
||||||
github.com/PuerkitoBio/goquery v1.9.1
|
github.com/PuerkitoBio/goquery v1.9.1
|
||||||
github.com/abadojack/whatlanggo v1.0.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/coreos/go-oidc/v3 v3.10.0
|
||||||
github.com/go-webauthn/webauthn v0.10.2
|
github.com/go-webauthn/webauthn v0.10.2
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
|
@ -27,7 +28,6 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
|
||||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
|
|
@ -12,6 +12,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/andybalholm/brotli"
|
||||||
)
|
)
|
||||||
|
|
||||||
const compressionThreshold = 1024
|
const compressionThreshold = 1024
|
||||||
|
@ -110,8 +112,15 @@ func (b *Builder) writeHeaders() {
|
||||||
func (b *Builder) compress(data []byte) {
|
func (b *Builder) compress(data []byte) {
|
||||||
if b.enableCompression && len(data) > compressionThreshold {
|
if b.enableCompression && len(data) > compressionThreshold {
|
||||||
acceptEncoding := b.r.Header.Get("Accept-Encoding")
|
acceptEncoding := b.r.Header.Get("Accept-Encoding")
|
||||||
|
|
||||||
switch {
|
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"):
|
case strings.Contains(acceptEncoding, "gzip"):
|
||||||
b.headers["Content-Encoding"] = "gzip"
|
b.headers["Content-Encoding"] = "gzip"
|
||||||
b.writeHeaders()
|
b.writeHeaders()
|
||||||
|
|
|
@ -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)
|
body := strings.Repeat("a", compressionThreshold+1)
|
||||||
r, err := http.NewRequest("GET", "/", nil)
|
r, err := http.NewRequest("GET", "/", nil)
|
||||||
r.Header.Set("Accept-Encoding", "gzip, deflate, br")
|
r.Header.Set("Accept-Encoding", "gzip, deflate, br")
|
||||||
|
@ -245,6 +245,30 @@ func TestBuildResponseWithGzipCompression(t *testing.T) {
|
||||||
handler.ServeHTTP(w, r)
|
handler.ServeHTTP(w, r)
|
||||||
resp := w.Result()
|
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"
|
expected := "gzip"
|
||||||
actual := resp.Header.Get("Content-Encoding")
|
actual := resp.Header.Get("Content-Encoding")
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"miniflux.app/v2/internal/locale"
|
"miniflux.app/v2/internal/locale"
|
||||||
)
|
)
|
||||||
|
@ -73,15 +74,16 @@ func (r *ResponseHandler) Close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ResponseHandler) getReader(maxBodySize int64) io.ReadCloser {
|
func (r *ResponseHandler) getReader(maxBodySize int64) io.ReadCloser {
|
||||||
|
contentEncoding := strings.ToLower(r.httpResponse.Header.Get("Content-Encoding"))
|
||||||
slog.Debug("Request response",
|
slog.Debug("Request response",
|
||||||
slog.String("effective_url", r.EffectiveURL()),
|
slog.String("effective_url", r.EffectiveURL()),
|
||||||
slog.Int64("content_length", r.httpResponse.ContentLength),
|
slog.String("content_length", r.httpResponse.Header.Get("Content-Length")),
|
||||||
slog.String("content_encoding", r.httpResponse.Header.Get("Content-Encoding")),
|
slog.String("content_encoding", contentEncoding),
|
||||||
slog.String("content_type", r.httpResponse.Header.Get("Content-Type")),
|
slog.String("content_type", r.httpResponse.Header.Get("Content-Type")),
|
||||||
)
|
)
|
||||||
|
|
||||||
reader := r.httpResponse.Body
|
reader := r.httpResponse.Body
|
||||||
switch r.httpResponse.Header.Get("Content-Encoding") {
|
switch contentEncoding {
|
||||||
case "br":
|
case "br":
|
||||||
reader = NewBrotliReadCloser(r.httpResponse.Body)
|
reader = NewBrotliReadCloser(r.httpResponse.Body)
|
||||||
case "gzip":
|
case "gzip":
|
||||||
|
|
|
@ -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-Security-Policy", `default-src 'self'`)
|
||||||
b.WithHeader("Content-Type", icon.MimeType)
|
b.WithHeader("Content-Type", icon.MimeType)
|
||||||
b.WithBody(icon.Content)
|
b.WithBody(icon.Content)
|
||||||
|
if icon.MimeType != "image/svg+xml" {
|
||||||
b.WithoutCompression()
|
b.WithoutCompression()
|
||||||
|
}
|
||||||
b.Write()
|
b.Write()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,12 +31,12 @@ func (h *handler) showAppIcon(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
switch filepath.Ext(filename) {
|
switch filepath.Ext(filename) {
|
||||||
case ".png":
|
case ".png":
|
||||||
|
b.WithoutCompression()
|
||||||
b.WithHeader("Content-Type", "image/png")
|
b.WithHeader("Content-Type", "image/png")
|
||||||
case ".svg":
|
case ".svg":
|
||||||
b.WithHeader("Content-Type", "image/svg+xml")
|
b.WithHeader("Content-Type", "image/svg+xml")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.WithoutCompression()
|
|
||||||
b.WithBody(blob)
|
b.WithBody(blob)
|
||||||
b.Write()
|
b.Write()
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue