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

ValueError raised by ws_ext_gen() when the 'compress' (window-bits) argument is outside the inclusive range 9-15. zlib does not support wbits=8 and values above 15 exceed the deflate spec, so the generator refuses to build an invalid Sec-WebSocket-Extensions offer. Unlike errors [1]/[2] this is a programming/config error on the local side, not a peer-handshake rejection.

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 c0ef574e29)

Solutions

  1. Pass a compress value in 9-15, or simply use the default 15 / compress=True.
  2. Clamp the value before generating: compress = max(9, min(15, requested)).
  3. Do not conflate 'compress' (permessage-deflate window bits) with zlib compression level (1-9); use the correct parameter for each.
  4. If you genuinely need no compression, pass compress=0 / compress=False rather than an out-of-range integer.

Example fix

# before
from aiohttp.http import ws_ext_gen
hdr = ws_ext_gen(compress=8)  # ValueError

# after
from aiohttp.http import ws_ext_gen
hdr = ws_ext_gen(compress=9)  # smallest legal window size
Defensive patterns

Strategy: validation

Validate before calling

from aiohttp.http import ws_ext_gen

def safe_ws_ext_gen(compress):
    if compress == 0:
        return ''
    if not (9 <= compress <= 15):
        raise ValueError(f'compress must be 9-15, got {compress}')
    return ws_ext_gen(compress=compress)

Prevention

When it happens

Trigger: Calling aiohttp.http.ws_ext_gen(compress=8) (or 7, 16, 0, negative) directly, or a code path that passes an out-of-range integer compress value into the extension-string generator. On the server, ws_ext_parse(isserver=True) clamps out-of-range client offers to 0 (so the public WebSocketResponse path normally shields you), so this is most often hit by direct ws_ext_gen callers or custom negotiation code.

Common situations: Custom WebSocket handshake code that forwards an unvalidated wbits value to ws_ext_gen; porting code that assumed wbits=8 was allowed; passing WebSocketResponse(compress=8) expecting it to mean 'compression level 8' (it is a window-bits value, not a level).

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/7bcfb15342f0363d.json. Report an issue: GitHub.