valyala/fasthttp · error

cannot gunzip request body: %w

Error message

cannot gunzip request body: %w

What it means

When reading the request body (Request.Body/StreamBody path), fasthttp transparently decompresses a body whose Content-Encoding is gzip via gzip.NewReader. If the gzip stream is corrupt or truncated, the reader construction fails and the error is wrapped as 'cannot gunzip request body'. Any other non-empty Content-Encoding is rejected as unsupported.

Source

Thrown at http.go:1164

func (req *Request) MultipartFormWithLimit(maxBodySize int) (*multipart.Form, error) {
	if req.multipartForm != nil {
		return req.multipartForm, nil
	}

	req.multipartFormBoundary = string(req.Header.MultipartFormBoundary())
	if req.multipartFormBoundary == "" {
		return nil, ErrNoMultipartForm
	}

	var err error
	ce := req.Header.peek(strContentEncoding)

	if req.bodyStream != nil {
		bodyStream := req.bodyStream
		var lr *io.LimitedReader
		if bytes.Equal(ce, strGzip) {
			if bodyStream, err = gzip.NewReader(bodyStream); err != nil {
				return nil, fmt.Errorf("cannot gunzip request body: %w", err)
			}
		} else if len(ce) > 0 {
			return nil, fmt.Errorf("unsupported content-encoding: %q", ce)
		}
		if maxBodySize > 0 {
			lr = &io.LimitedReader{
				R: bodyStream,
				N: int64(maxBodySize) + 1,
			}
			bodyStream = lr
		}

		mr := multipart.NewReader(bodyStream, req.multipartFormBoundary)
		req.multipartForm, err = mr.ReadForm(8 * 1024)
		if err != nil {
			if lr != nil && lr.N <= 0 {
				return nil, fmt.Errorf("cannot read multipart/form-data body: %w", ErrBodyTooLarge)
			}

View on GitHub (pinned to c96f600972)

Solutions

  1. Verify the client actually gzips the body (gzip.Writer with Close/Flush called before send).
  2. Remove Content-Encoding: gzip if the body is plain, or switch to the encoding fasthttp supports.
  3. If you need deflate/brotli, handle decompression manually: read the raw body stream yourself instead of relying on auto-gunzip.
  4. Check for proxies that re-compress or truncate the body in transit.

Example fix

// before (body not actually gzip)
req.Header.Set("Content-Encoding", "gzip")
req.SetBodyString("hello")
// after
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
gz.Write([]byte("hello"))
gz.Close()
req.Header.Set("Content-Encoding", "gzip")
req.SetBodyRaw(buf.Bytes())
Defensive patterns

Strategy: try-catch

Validate before calling

func isRealGzip(body []byte) bool {
    return len(body) > 2 && body[0] == 0x1f && body[1] == 0x8b
} // only set Content-Encoding: gzip if this passes

Try / catch

body, err := req.Body() // or StreamBody
if err != nil {
    var gze *gzip.HeaderError
    if strings.Contains(err.Error(), "cannot gunzip request body") || errors.As(err, &gze) {
        // read raw body instead: req.BodyRaw(), or respond 400
    }
}

Prevention

When it happens

Trigger: Client sends 'Content-Encoding: gzip' but the body is not valid gzip data (plain text, partially sent, or doubly-compressed), or uses an unsupported encoding like deflate/br.

Common situations: Clients gzipping bodies with broken streaming code; double compression through a proxy; test scripts sending fake gzip; clients using brotli/deflate expecting server support.

Related errors


AI-assisted analysis of valyala/fasthttp@c96f600972 (2026-08-31). Data as JSON: /api/errors/18866f78a4731dfa. Report an issue: GitHub.