Preserve partial-response byte ranges

This commit is contained in:
rcourtman 2026-08-16 18:41:43 +01:00
parent 91737ee207
commit 37d6e29e84
3 changed files with 39 additions and 7 deletions

View file

@ -204,9 +204,11 @@ change may globally weaken the Task 03 lifecycle-state idempotency invariant.
exclusions, and bypass token-valid WebSocket upgrades before wrapping the
response writer. Informational responses must leave negotiation open for
the final response, body-forbidden statuses must not gain compression
framing, and once a body-permitted successful response advertises gzip, an
explicit empty body must still emit a valid empty gzip member. Regression
proof lives in `pkg/server/gzip_middleware_test.go`.
framing, and partial responses must preserve the identity representation
whose byte positions `Content-Range` describes. Once a body-permitted full
response advertises gzip, an explicit empty body must still emit a valid
empty gzip member. Regression proof lives in
`pkg/server/gzip_middleware_test.go`.
WebSocket state refreshes on monitor, agent-report, and alert mutation hot
paths must enqueue coalesced current-state invalidations instead of building
full frontend-state payloads at every signal. The hub owns delayed
@ -1016,9 +1018,10 @@ supports wildcard acceptance, applies strict three-digit qvalue parsing, and
lets explicit gzip exclusions override wildcards. WebSocket token lists and
range requests bypass the wrapper. Explicit empty 200 responses carry a valid
empty gzip stream whenever `Content-Encoding: gzip` is committed, while
informational responses defer negotiation to the final response and 204/205/304,
SSE, already encoded, small declared, and binary responses stay on their
identity paths.
informational responses defer negotiation to the final response and
204/205/206/304, SSE, already encoded, small declared, and binary responses
stay on their identity paths. Partial content retains the identity bytes that
its `Content-Range` describes.
### Libvirt workload projection stays bounded and allocation-local

View file

@ -180,7 +180,8 @@ func (w *gzipResponseWriter) decide(status int) {
header := w.Header()
header.Add("Vary", "Accept-Encoding")
if status == http.StatusNoContent || status == http.StatusResetContent || status == http.StatusNotModified {
switch status {
case http.StatusNoContent, http.StatusResetContent, http.StatusPartialContent, http.StatusNotModified:
return
}
if header.Get("Content-Encoding") != "" {

View file

@ -358,6 +358,34 @@ func TestWithGzipPreservesStatusAndEmptyBodies(t *testing.T) {
}
}
func TestWithGzipPreservesPartialContentRepresentation(t *testing.T) {
payload := strings.Repeat("x", 1024)
handler := withGzip(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Range", "bytes 0-1023/2048")
w.WriteHeader(http.StatusPartialContent)
_, _ = w.Write([]byte(payload))
}))
req := httptest.NewRequest(http.MethodGet, "/api/export?segment=first", nil)
req.Header.Set("Accept-Encoding", "gzip")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusPartialContent {
t.Fatalf("status = %d, want 206", rec.Code)
}
if got := rec.Header().Get("Content-Encoding"); got != "" {
t.Fatalf("Content-Encoding = %q, want identity for Content-Range semantics", got)
}
if got := rec.Header().Get("Content-Range"); got != "bytes 0-1023/2048" {
t.Fatalf("Content-Range = %q, want original byte range", got)
}
if rec.Body.String() != payload {
t.Fatalf("partial response body was transformed")
}
}
func TestWithGzipDefersDecisionPastInformationalResponse(t *testing.T) {
payload := strings.Repeat(`{"status":"ready"}`, 256)
handler := withGzip(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {