internal/httpd/compress_test.go
110 lines · 4017 bytes
5 symbols in this file
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}