aio-libs/aiohttp · error · ValueError

Compress wbits must between 9 and 15, zlib does not support…

Error message

Compress wbits must between 9 and 15, zlib does not support wbits=8

What it means

`ws_ext_gen` builds the `Sec-WebSocket-Extensions` offer string and requires `compress` (the window-bits argument) to be 9-15, because zlib does not support wbits=8. Passing any other value is a programmer error that raises `ValueError` immediately — it is never produced by network input.

Solutions

  1. Pass `compress` in the range 9-15 (15 is the default and most common).
  2. Clamp/validate the value before calling: `compress = max(9, min(15, compress))` when it is config-driven.
  3. To disable compression, do not call `ws_ext_gen` — set `compress=0`/`compress=False` on `ws_connect`/`WebSocketResponse`.

Example fix

# before
hdr = aiohttp.ws_ext_gen(compress=8)   # ValueError
# after
hdr = aiohttp.ws_ext_gen(compress=15)   # valid
Defensive patterns

Strategy: validation

Validate before calling

if not (9 <= compress <= 15):
    raise ValueError(f"compress must be 9-15, got {compress}")

Prevention

When it happens

Trigger: Calling `ws_ext_gen(compress=8)`, `ws_ext_gen(compress=16)`, or `ws_ext_gen(compress=0)` directly; or forwarding an unchecked config/env value into an API that reaches `ws_ext_gen` (note: aiohttp's own server path only passes 0 or 9-15 from `ws_ext_parse`, so this fires for direct/custom callers).

Common situations: App reads a compression level from configuration and forwards it without bounds-checking; developer confuses zlib's general wbits (which permits 8 in raw mode) with the WS deflate range; passing 0 meaning 'no compression' into `ws_ext_gen` instead of disabling compression at a higher level.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/7bcfb15342f0363d. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/_websocket/helpers.py:135

                        raise WSHandshakeError("Invalid window size")
                if match.group(2):
                    notakeover = True
                # Ignore regex group 5 & 6 for client_max_window_bits
                break
        # Return Fail if client side and not match
        elif not isserver:
            raise WSHandshakeError("Extension for deflate not supported" + ext.group(1))

    return compress, notakeover


def ws_ext_gen(
    compress: int = 15, isserver: bool = False, server_notakeover: bool = False
) -> str:
    # client_notakeover=False not used for server
    # compress wbit 8 does not support in zlib
    if compress < 9 or compress > 15:
        raise ValueError(
            "Compress wbits must between 9 and 15, zlib does not support wbits=8"
        )
    enabledext = ["permessage-deflate"]
    if not isserver:
        enabledext.append("client_max_window_bits")

    if compress < 15:
        enabledext.append("server_max_window_bits=" + str(compress))
    if server_notakeover:
        enabledext.append("server_no_context_takeover")
    # if client_notakeover:
    #     enabledext.append('client_no_context_takeover')
    return "; ".join(enabledext)

View on GitHub (pinned to d041d4d0fd)