From d0381781f5220c3543e5008862613ab991064be4 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 2 Nov 2023 17:06:04 +0200 Subject: [PATCH] fix EOF errors on http response with content and encoding headers Before this on a response which has a Content-Encoding value, but no body will error out with EOF as we still try to decode it. As the code and the semantics of network requests mean that we might not know if we are going to be able to read something and whether we got and EOF because of network error or because there was no body - we need to figure out if we should try to read at all. Unfortunately `Content-Encoding: chunked` exists which makes a more general solution much harder IMO. As such the current solution is to short circuit on known status codes that do not have content. Namely all 1xx and 204 (aptly named no content) and 304 (not modified). https://www.rfc-editor.org/rfc/rfc9110.html#section-6.4.1-8 Additionally a bare minimum tests was added reproducing what the user reported. https://community.grafana.com/t/error-decompressing-response-body-eof-despite-http-request-returning-204/106927 --- js/modules/k6/http/request_test.go | 23 +++++++++++++++++++++++ lib/netext/httpext/compression.go | 7 +++++++ 2 files changed, 30 insertions(+) diff --git a/js/modules/k6/http/request_test.go b/js/modules/k6/http/request_test.go index 2c55162c774..a7a11e091ec 100644 --- a/js/modules/k6/http/request_test.go +++ b/js/modules/k6/http/request_test.go @@ -2386,3 +2386,26 @@ func GetTestServerWithCertificate(t *testing.T, certPem, key []byte, suitesIds . s.Listener = tls.NewListener(s.Listener, s.TLS) return s, client } + +func TestGzipped204Response(t *testing.T) { + t.Parallel() + ts := newTestCase(t) + tb := ts.tb + rt := ts.runtime.VU.Runtime() + state := ts.runtime.VU.State() + state.Options.Throw = null.BoolFrom(true) + sr := tb.Replacer.Replace + // We should not try to decode it + tb.Mux.HandleFunc("/gzipempty", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Encoding", "gzip") + w.WriteHeader(http.StatusNoContent) + })) + + _, err := rt.RunString(sr(` + var res = http.get("HTTPBIN_URL/gzipempty"); + if (res.status != 204) { + throw new Error("unexpected status code: " + res.status) + } + `)) + assert.NoError(t, err) +} diff --git a/lib/netext/httpext/compression.go b/lib/netext/httpext/compression.go index 16303fa7818..ba3b2f9a216 100644 --- a/lib/netext/httpext/compression.go +++ b/lib/netext/httpext/compression.go @@ -140,6 +140,13 @@ func readResponseBody( _ = respBody.Close() }(resp.Body) + if (resp.StatusCode >= 100 && resp.StatusCode <= 199) || // 1xx + resp.StatusCode == http.StatusNoContent || resp.StatusCode == http.StatusNotModified { + // for all three of this status code there is always no content + // https://www.rfc-editor.org/rfc/rfc9110.html#section-6.4.1-8 + // this also prevents trying to read + return nil, nil //nolint:nilnil + } contentEncodings := strings.Split(resp.Header.Get("Content-Encoding"), ",") // Transparently decompress the body if it's has a content-encoding we // support. If not, simply return it as it is.