internal/httpd/compress_test.go

v1.36.0
gitbay/internal/httpd/compress_test.go history · blame · raw

110 lines · 4017 bytes

  1package httpd
  2
  3import (
  4	"bytes"
  5	"compress/gzip"
  6	"io"
  7	"net/http"
  8	"net/http/httptest"
  9	"strings"
 10	"testing"
 11
 12	"gitbay.org/gitbay/internal/config"
 13)
 14
 15func plainServer() *Server {
 16	cfg := config.Default()
 17	cfg.Server.SiteURL = "https://forge.test/"
 18	return &Server{cfg: cfg}
 19}
 20
 21func get(t *testing.T, h http.Handler, path string, hdr map[string]string) *httptest.ResponseRecorder {
 22	t.Helper()
 23	r := httptest.NewRequest("GET", path, nil)
 24	r.Host = "forge.test"
 25	for k, v := range hdr {
 26		r.Header.Set(k, v)
 27	}
 28	w := httptest.NewRecorder()
 29	h.ServeHTTP(w, r)
 30	return w
 31}
 32
 33// The stylesheet is gzipped for a client that accepts it and served as-is
 34// for one that does not; both bodies are the same bytes (#232).
 35func TestStylesheetIsCompressed(t *testing.T) {
 36	h := plainServer().Handler()
 37	plain := get(t, h, "/static/style.css", nil)
 38	if plain.Code != 200 || plain.Header().Get("Content-Encoding") != "" {
 39		t.Fatalf("identity: %d %q", plain.Code, plain.Header().Get("Content-Encoding"))
 40	}
 41	zipped := get(t, h, "/static/style.css", map[string]string{"Accept-Encoding": "gzip, br"})
 42	if zipped.Code != 200 || zipped.Header().Get("Content-Encoding") != "gzip" || zipped.Header().Get("Vary") != "Accept-Encoding" {
 43		t.Fatalf("gzip: %d %q vary=%q", zipped.Code, zipped.Header().Get("Content-Encoding"), zipped.Header().Get("Vary"))
 44	}
 45	if zipped.Body.Len() >= plain.Body.Len()/2 {
 46		t.Fatalf("gzip body %d bytes, plain %d", zipped.Body.Len(), plain.Body.Len())
 47	}
 48	zr, err := gzip.NewReader(zipped.Body)
 49	if err != nil {
 50		t.Fatal(err)
 51	}
 52	body, _ := io.ReadAll(zr)
 53	if !bytes.Equal(body, plain.Body.Bytes()) {
 54		t.Fatal("gunzipped body differs from the identity body")
 55	}
 56	if zipped.Header().Get("ETag") != plain.Header().Get("ETag") {
 57		t.Fatal("ETag changed with encoding")
 58	}
 59	// A 304 carries no body to compress and no encoding header.
 60	notMod := get(t, h, "/static/style.css", map[string]string{"Accept-Encoding": "gzip", "If-None-Match": plain.Header().Get("ETag")})
 61	if notMod.Code != 304 || notMod.Header().Get("Content-Encoding") != "" {
 62		t.Fatalf("304: %d %q", notMod.Code, notMod.Header().Get("Content-Encoding"))
 63	}
 64}
 65
 66// A binary type is not touched: the font route keeps its bytes and no
 67// encoding header.
 68func TestBinaryResponsesPassThrough(t *testing.T) {
 69	h := plainServer().Handler()
 70	w := get(t, h, "/favicon.svg", map[string]string{"Accept-Encoding": "gzip"})
 71	if w.Code != 200 || w.Header().Get("Content-Encoding") != "gzip" {
 72		t.Fatalf("svg is text and should gzip: %d %q", w.Code, w.Header().Get("Content-Encoding"))
 73	}
 74	rec := httptest.NewRecorder()
 75	compressed(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 76		w.Header().Set("Content-Type", "application/x-git-upload-pack-result")
 77		w.Write([]byte("0000"))
 78	})).ServeHTTP(rec, httptest.NewRequest("POST", "/x", nil))
 79	if rec.Header().Get("Content-Encoding") != "" || rec.Body.String() != "0000" {
 80		t.Fatalf("git transport touched: %q %q", rec.Header().Get("Content-Encoding"), rec.Body.String())
 81	}
 82}
 83
 84// A flush mid-response reaches the connection with what was written so
 85// far decodable, which is what lets a page stream through gzip.
 86func TestGzipWriterFlushes(t *testing.T) {
 87	rec := httptest.NewRecorder()
 88	h := compressed(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 89		w.Header().Set("Content-Type", "text/html; charset=utf-8")
 90		io.WriteString(w, "<p>first</p>")
 91		if err := http.NewResponseController(w).Flush(); err != nil {
 92			t.Fatalf("flush: %v", err)
 93		}
 94		if !rec.Flushed {
 95			t.Fatal("the flush did not reach the connection")
 96		}
 97		zr, err := gzip.NewReader(bytes.NewReader(rec.Body.Bytes()))
 98		if err != nil {
 99			t.Fatalf("gzip header: %v", err)
100		}
101		got, _ := io.ReadAll(zr) // no trailer yet: ends in ErrUnexpectedEOF
102		if !strings.Contains(string(got), "<p>first</p>") {
103			t.Fatalf("flushed body decodes to %q", got)
104		}
105		io.WriteString(w, "<p>second</p>")
106	}))
107	req := httptest.NewRequest("GET", "/", nil)
108	req.Header.Set("Accept-Encoding", "gzip")
109	h.ServeHTTP(rec, req)
110}