vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
Compress hosted site responses too
a76b2f38cb6692f7fe88c83e4552205d398f30df · Keenan Nemetz · 12d ago · parent 306f36fc
Compress hosted site responses too The gzip middleware covered the forge UI and nothing else: server.Handler dispatches a site host to server/pages before the forge middleware runs, so every hosted site was serving raw HTML, CSS and JavaScript. A site is a static file server, which is the best case gzip has. The compressor moves from server/web to internal/compress so both surfaces share one implementation of the negotiation and header rules, with two entry points: New for the forge, which needs the writer inside its own status recorder and panic handling, and Handler for the site dispatcher. Site responses keep their ETag and Last-Modified, so revalidation is still a 304 off the blob sha; Vary: Accept-Encoding names the representation; a 304 and a 301 carry no body to compress; and a client that does not ask for gzip still gets the exact bytes and the exact Content-Length. Measured on the site path, an HTML page with repeated markup goes from 1.5 KB to 83 bytes.
server/web/compress_test.go → internal/compress/compress_test.go +13−7
| @@ -1,4 +1,4 @@ | |||
| 1 | package web | ||
| 1 | package compress | ||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "bytes" | |
| @@ -15,7 +15,7 @@ import ( | |||
| 15 | 15 | func serveWithCompression(t *testing.T, r *http.Request, handler http.HandlerFunc) *httptest.ResponseRecorder { | |
| 16 | 16 | t.Helper() | |
| 17 | 17 | rec := httptest.NewRecorder() | |
| 18 | c := newCompressor(rec, r) | ||
| 18 | c := New(rec, r) | ||
| 19 | 19 | handler(c, r) | |
| 20 | 20 | c.Close() | |
| 21 | 21 | return rec | |
| @@ -144,7 +144,7 @@ func TestCompressionFlushes(t *testing.T) { | |||
| 144 | 144 | r := httptest.NewRequest(http.MethodGet, "/", nil) | |
| 145 | 145 | r.Header.Set("Accept-Encoding", "gzip") | |
| 146 | 146 | rec := httptest.NewRecorder() | |
| 147 | c := newCompressor(rec, r) | ||
| 147 | c := New(rec, r) | ||
| 148 | 148 | handler := http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { | |
| 149 | 149 | rw.Header().Set("Content-Type", "text/plain; charset=utf-8") | |
| 150 | 150 | _, _ = io.WriteString(rw, "first\n") | |
| @@ -169,14 +169,16 @@ func TestCompressionFlushes(t *testing.T) { | |||
| 169 | 169 | } | |
| 170 | 170 | } | |
| 171 | 171 | ||
| 172 | // TestCompressorUnwraps: http.ResponseController reaches the real writer | ||
| 172 | // TestWriterUnwraps: http.ResponseController reaches the real writer | ||
| 173 | 173 | // through Unwrap, so nothing in the chain needs its own accessor. | |
| 174 | func TestCompressorUnwraps(t *testing.T) { | ||
| 174 | func TestWriterUnwraps(t *testing.T) { | ||
| 175 | 175 | t.Parallel() | |
| 176 | 176 | r := httptest.NewRequest(http.MethodGet, "/", nil) | |
| 177 | 177 | rec := httptest.NewRecorder() | |
| 178 | inner := &statusWriter{ResponseWriter: rec} | ||
| 179 | c := newCompressor(inner, r) | ||
| 178 | // One transparent wrapper, as the forge UI puts a status recorder between the | ||
| 179 | // handler and the compressor. | ||
| 180 | inner := wrapped{rec} | ||
| 181 | c := New(inner, r) | ||
| 180 | 182 | if got := c.Unwrap(); got != http.ResponseWriter(inner) { | |
| 181 | 183 | t.Fatalf("Unwrap = %T, want the wrapped writer", got) | |
| 182 | 184 | } | |
| @@ -185,3 +187,7 @@ func TestCompressorUnwraps(t *testing.T) { | |||
| 185 | 187 | t.Fatalf("ResponseController.Flush through the compressor: %v", err) | |
| 186 | 188 | } | |
| 187 | 189 | } | |
| 190 | |||
| 191 | // wrapped is the shape of every writer between a handler and the compressor: | ||
| 192 | // a ResponseWriter that forwards Header, Write and WriteHeader. | ||
| 193 | type wrapped struct{ http.ResponseWriter } | ||