Skip to content

Rejecting a handshake raises a silenced AssertionError in the threading server #1722

Description

@scttbnsn

In the threading implementation, when process_request or process_response rejects the handshake by returning an HTTP response, handshake() returns normally; handshake_exc is only set for failures, not for deliberate rejections:

# self.protocol.handshake_exc is set when the connection is lost before
# receiving a request, when the request cannot be parsed, or when the
# handshake fails, including when process_request or process_response
# raises an exception.
# It isn't set when process_request or process_response sends an HTTP
# response that rejects the handshake.
if self.protocol.handshake_exc is not None:
raise self.protocol.handshake_exc

conn_handler then hits this assertion, since the state is CONNECTING or CLOSED after a rejection, never OPEN:

assert connection.protocol.state is OPEN

The AssertionError is swallowed by the except Exception below, so nothing is logged and the socket is closed without going through close_socket() and joining recv_events_thread. The # pragma: no cover there isn't accurate either; this branch runs on every rejection:

except Exception: # pragma: no cover
# Don't leak sockets on unexpected errors.
sock.close()

With python -O, the assertion is stripped instead and the handler runs on the rejected connection: start_keepalive() and handler(connection) execute with the connection already CLOSED, and every rejection logs "connection handler failed".

Repro:

import http
import threading

from websockets.sync.client import connect
from websockets.sync.server import serve


def reject(connection, request):
    return connection.respond(http.HTTPStatus.FORBIDDEN, "Forbidden")


def handler(connection):
    raise AssertionError("handler ran on a rejected connection")


with serve(handler, "localhost", 0, process_request=reject) as server:
    threading.Thread(target=server.serve_forever, daemon=True).start()
    host, port = server.socket.getsockname()
    try:
        connect(f"ws://{host}:{port}/")
    except Exception as exc:
        print(f"client: {exc!r}")
    server.shutdown()

Under python, the client gets the 403 and the server logs nothing; a trace hook shows the AssertionError firing at line 608 on every rejection. Under python -O, the server additionally logs "connection handler failed" with the handler's traceback.

Verified on main (ff4869b) with Python 3.13 on macOS.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions