Skip to content

WebSockets & Server-Sent Events

The WebSocket connection object and the server-sent-event response types.

WebSocket

WebSocket connection handler.

Usage::

from veloce import Veloce, WebSocket

app = Veloce()

@app.websocket("/ws")
async def chat(ws: WebSocket):
    async with ws:
        await ws.accept()
        async for message in ws.iter_text():
            await ws.send_text(message)

Using async with ws: closes the connection on a clean exit with a normal-closure 1000. If the block exits via an exception, __aexit__ leaves the close to the dispatcher's error handling, which sends the mapped close code (e.g. 1008 for a policy violation, 1011 for an unhandled error) before the exception propagates.

Pass idle_timeout=<seconds> (default None -> disabled) to bound how long a blocking receive (receive/receive_text/ receive_bytes/receive_json and the iter_* loops) waits for the next message. When no message arrives within idle_timeout seconds the connection performs a clean RFC 6455 close with 1001 Going Away and the receive raises WebSocketDisconnect, so the handler loop unwinds exactly as it would on a peer-initiated close. A per-call timeout still applies; whichever deadline is smaller wins. Set it at construction via from_asgi(idle_timeout=...) or from inside the handler with set_idle_timeout. The window bounds each complete message (in production, ASGI delivers complete messages and owns ping/pong; the raw-transport path measures it the same way).

Pass heartbeat=<seconds> (raw-transport mode only, default None -> disabled) to proactively probe a silent peer. After accept() a timer sends an application PING carrying a token every heartbeat seconds; the peer must answer with a PONG (or send any other frame) before the next tick, otherwise the connection is dropped with a 1006 close code recorded on ws.close_code. Any inbound byte defers the next probe, so busy connections send no needless pings. In ASGI mode the server owns ping/pong, so the value is accepted for API symmetry but never starts a timer.

query_params property

query_params: Any

Parsed query string of the WebSocket handshake URL.

Read it as ws.query_params["token"]. Backed by QueryParams (multi-value, getlist-aware). Empty when the scope carries no query_string.

url property

url: str

The WebSocket handshake URL path - ASGI-style shape.

Returns path plus ?query when a query string is present.

client property

client: Any

The connecting peer as an Address(host, port).

Reads scope["client"] (the ASGI (host, port) pair). Returns None when the scope carries no client info.

state property

state: Any

Per-connection scratch namespace.

Lazily-created State (a dict subclass) supporting both ws.state.user = ... attribute access and ws.state["user"].

cookies property

cookies: dict[str, str]

Cookies sent with the WebSocket handshake.

Parses the handshake Cookie header into {name: value}. Empty when no cookie header was present.

application_state property

application_state: WebSocketState

Server-side state of the WebSocket.

  • CONNECTING before the app sends accept().
  • CONNECTED after accept() and until close() is sent.
  • DISCONNECTED after close() (locally) or after the peer half-closes (observed via WebSocketDisconnect).

client_state property

client_state: WebSocketState

Client-side state of the WebSocket.

Veloce does not distinguish the two halves at the protocol level beyond the close flag, so this mirrors application_state once the peer disconnects and otherwise stays CONNECTED once the handshake completes.

origin property

origin: str | None

The client-supplied Origin header, or None if absent.

WebSocket handshakes carry Origin per RFC 6455 Sec. 10.2 / Sec. 4.1. Browsers always send it; non-browser clients may omit it. The header is the application's primary defence against Cross-Site WebSocket Hijacking - CSWSH bypasses CORS because the handshake is plain HTTP/1.1 and Same-Origin Policy does not apply to it. Pair this accessor with check_origin(allowed) before accept().

requested_subprotocols property

requested_subprotocols: list[str]

Subprotocols the client offered in Sec-WebSocket-Protocol.

Returns them in client preference order (RFC 6455 Sec. 1.9). Empty list when the header is absent. Whitespace around each token is stripped; the comparison the negotiator performs is case-sensitive per the spec.

from_asgi classmethod

from_asgi(scope: dict[str, Any], receive: Any, send: Any, idle_timeout: float | None = None, heartbeat: float | None = None) -> WebSocket

Construct an ASGI-driven WebSocket (no asyncio.Transport).

Used by Veloce.__call__ for scope["type"] == "websocket". Headers come from scope["headers"] (list of (bytes, bytes)), decoded latin-1 per ASGI. accept/send_*/receive_*/close all dispatch through send/receive instead of the raw frame writer used by the asyncio.Transport mode.

idle_timeout (default None -> disabled) bounds how long a blocking receive waits for the next frame before performing a clean 1001 Going Away close; see the class docstring. heartbeat is accepted for signature symmetry with the raw-transport constructor but is inert here - the ASGI server owns ping/pong.

from_transport classmethod

from_transport(transport: Transport, headers: dict[str, str], scope: dict[str, Any], *, path_params: dict[str, Any] | None = None, idle_timeout: float | None = None, recv_queue_maxsize: int | None = None) -> WebSocket

Construct a raw-transport WebSocket whose 101 was already sent.

Used by the native HttpProtocol upgrade path. The protocol writes the RFC 6455 Sec. 4.2.2 101 response synchronously (to switch the byte stream) before building this object, so _handshake_sent is set: a later accept() validates state but does not emit a second handshake. The connection is otherwise a normal raw-mode WebSocket - transport is set, _asgi_send stays None (so _is_asgi is False), and inbound bytes flow through feed_data/_parse_frame exactly as for a directly constructed instance.

headers are the lowercased handshake headers (latin-1 decoded by the protocol). scope mirrors the ASGI websocket scope shape so the same path/query_params/client/cookies accessors work unchanged.

check_origin

check_origin(allowed: str | Iterable[str]) -> bool

Return True when the handshake's Origin is in allowed.

Pass a single origin string or an iterable of allowed origins (e.g. ["https://app.example.com", "https://admin.example.com"]). Normalisation matches WebSocketOriginMiddleware: each side is lowercased and has any trailing slash stripped, so allow-lists written for one API are interchangeable with the other.

  • Wildcard. "*" in allowed accepts any origin and is the opt-in "I have my own check elsewhere" escape hatch - the symmetric behaviour to WebSocketOriginMiddleware's allowed_origins=["*"].
  • Missing Origin (no header at all, or a literal Origin: null from a sandboxed iframe / file:// page) is a non-match and returns False. Non-browser clients legitimately omit the header - if you want to allow them, branch on ws.origin is None explicitly. The WebSocketOriginMiddleware middleware path also offers an allow_missing=True switch; this in-handler helper is deliberately strict-by-default.
Usage

@app.websocket("/ws") async def chat(ws: WebSocket): if not ws.check_origin("https://app.example.com"): await ws.close(code=WS_1008_POLICY_VIOLATION) # policy violation return await ws.accept() ...

For the middleware-style check (registered once, runs before the handler) reach for veloce.SecurityHeadersMiddleware's sibling WebSocketOriginMiddleware.

negotiate_subprotocol

negotiate_subprotocol(supported: list[str]) -> str | None

Pick the first client-offered subprotocol that the server supports.

Per RFC 6455 Sec. 4.1, the server picks ONE protocol from the client's list. Most servers prefer to honour the client's preference order (first match wins), which is what we do.

accept async

accept(subprotocol: str | None = None, headers: dict[str, str] | None = None) -> None

Complete the WebSocket handshake.

Records the chosen subprotocol on accepted_subprotocol.

Raises:

Type Description
RuntimeError

if the connection is already accepted or already closed, or if a subprotocol/headers argument is passed on the native (Veloce.run) upgrade path, where the 101 response has already been sent and cannot be renegotiated.

send_text async

send_text(data: str) -> None

Send a text frame.

send_json async

send_json(data: Any, mode: str = 'text') -> None

Send JSON data.

mode="text" (default) wraps the JSON in a text frame (opcode 0x1). mode="binary" sends the raw JSON bytes as a binary frame (0x2).

send_bytes async

send_bytes(data: bytes) -> None

Send a binary frame.

receive async

receive() -> dict[str, Any]

Receive a raw ASGI WebSocket message.

Returns the message dict as the ASGI server delivered it ({"type": "websocket.receive", "text"/"bytes": ...}). A websocket.disconnect message raises WebSocketDisconnect. ASGI-mode only - raw asyncio-transport connections don't carry ASGI message envelopes.

The same handshake state machine the typed receive_* helpers enforce: the raw escape hatch must not be a way around receive-before-accept or receive-after-close (which would consume the websocket.connect envelope and corrupt the next accept()).

send async

send(message: dict[str, Any]) -> None

Send a raw ASGI WebSocket message.

message is forwarded straight to the ASGI send callable, e.g. {"type": "websocket.send", "text": "..."}.

set_send_drain

set_send_drain(drain: Any) -> None

Install the native write-side backpressure hook (raw transport only).

drain is an awaitable-returning callable (HttpProtocol.drain) that blocks while the transport's outgoing buffer is over its high-water mark. The async send_* wrappers await it before writing each frame, so a slow-reading client suspends the producing handler instead of letting the transport buffer grow without bound. The native upgrade path (HttpProtocol) calls this once; ASGI mode leaves it unset.

set_idle_timeout

set_idle_timeout(idle_timeout: float | None) -> None

Set the idle-receive timeout in seconds (None disables it).

Applies to every subsequent blocking receive on this connection. Call it inside the handler (typically right after accept()) to enable or adjust the window; passing idle_timeout= to WebSocket.from_asgi sets the same value at construction.

receive_text async

receive_text(timeout: float | None = None) -> str

Receive a text message. Raises asyncio.TimeoutError if timeout exceeded.

When idle_timeout is configured, a wait longer than the idle window closes the connection with 1001 Going Away and raises WebSocketDisconnect instead of asyncio.TimeoutError.

receive_json async

receive_json(timeout: float | None = None) -> Any

Receive and parse JSON.

receive_bytes async

receive_bytes(timeout: float | None = None) -> bytes

Receive binary data. Raises asyncio.TimeoutError if timeout exceeded.

When idle_timeout is configured, a wait longer than the idle window closes the connection with 1001 Going Away and raises WebSocketDisconnect instead of asyncio.TimeoutError.

iter_text async

iter_text() -> Any

Async-iterate over incoming text frames until the peer closes.

Usage

async for msg in ws.iter_text(): ...

Terminates cleanly on WebSocketDisconnect. Other exceptions propagate.

iter_bytes async

iter_bytes() -> Any

Async-iterate over incoming binary frames until the peer closes.

iter_json async

iter_json() -> Any

Async-iterate over incoming JSON-decoded frames until peer closes.

close async

close(code: int = WS_1000_NORMAL_CLOSURE, reason: str = '') -> None

Send a close frame and complete the RFC 6455 close handshake.

Per RFC 6455 Sec. 5.5.1 the close-frame payload is a 2-byte big-endian status code optionally followed by a UTF-8 reason of at most 123 bytes (so the whole payload fits in the 125-byte control-frame budget). Reasons longer than 123 bytes are truncated to a clean UTF-8 boundary.

On the raw-transport path the close is a full handshake (Sec. 5.5.1, Sec. 7.1.1): the close frame is sent, then a server-initiated close waits for the peer's reply close frame (bounded by CLOSE_HANDSHAKE_TIMEOUT) before dropping the TCP connection. A peer-initiated close already carries the peer's frame, so the reply is sent and the transport closed without waiting.

feed_data

feed_data(data: bytes) -> None

Feed raw bytes from the transport (called by the protocol).

The transport delivers byte runs that need not align with frame boundaries: a single frame may be split across two reads, and one read may carry several frames. Bytes are appended to a persistent receive buffer and complete frames are parsed off the front in a loop - partial frames are kept for the next call.

Handles fragmented messages (RFC 6455 Sec. 5.4): a data frame with FIN=0 opens a message that subsequent continuation frames (opcode 0x0) extend, and the FIN=1 continuation completes it. Control frames (close / ping / pong) are never fragmented and may be interleaved within a fragmented message without disturbing the reassembly buffer.

start_heartbeat

start_heartbeat() -> None

Arm the heartbeat timer for a raw-transport connection.

Idempotent and a no-op in ASGI mode or when heartbeat was not configured. accept() calls this automatically once the raw handshake completes, so handlers rarely call it directly; it is public so a handler that builds a WebSocket by hand can start the probe after wiring its own transport.

EventSourceResponse

Bases: Response

SSE streaming response - sends events over a long-lived connection.

Usage::

@app.get("/events")
async def events(request: Request):
    async def generate():
        for i in range(10):
            yield ServerSentEvent(data=f"Event {i}")
            await asyncio.sleep(1)
    return EventSourceResponse(generate())

Pass ping=<seconds> to emit a keep-alive comment frame whenever no event is produced within that interval - useful for holding idle connections open through proxies that close silent sockets.

body property writable

body: bytes

Return the response body bytes.

media_type property writable

media_type: str

Return the full Content-Type including parameters.

is_json property

is_json: bool

True when Content-Type is JSON.

Matches application/json and any application/*+json structured suffix (RFC 6839 Sec. 3.1).

mimetype property writable

mimetype: str

The bare media type - Content-Type without parameters.

text/html; charset=utf-8 -> text/html. Lower-cased and stripped per RFC 9110 Sec. 8.3 (media types are case-insensitive).

status property writable

status: str

Full HTTP status line, e.g. "200 OK".

Assignable: accepts an int (200), a bare numeric string ("200"), or a full status line ("200 OK" / "404 Not Found"). The leading integer is parsed into status_code.

content_length property

content_length: int

Length of the response body in bytes.

Always derived from len(body). Streaming responses (which don't materialise the body) return 0 here; see is_streamed.

is_streamed property

is_streamed: bool

True when the response body is a streaming iterator.

charset property writable

charset: str

Response charset from Content-Type.

Falls back to "utf-8" when no charset parameter is present. Assignable: setting it rewrites the charset= parameter on the existing Content-Type (the bare media type is preserved).

mimetype_params property

mimetype_params: dict[str, str]

Parameters of the Content-Type header.

Everything after the bare media type, as a dict of lower-cased parameter names to their (unquoted) values. For text/html; charset=utf-8 this is {"charset": "utf-8"}. Returns an empty dict when no parameters are present.

last_modified property writable

last_modified: Any

Parsed Last-Modified header -> UTC datetime or None.

Accepts the three RFC 9110 Sec. 5.6.7 HTTP-date forms. Returns None on missing/unparseable.

expires property writable

expires: Any

Parsed Expires header -> UTC datetime or None (RFC 9111 Sec. 5.3).

cookies property

cookies: dict[str, str]

Parsed cookie jar from this response's Set-Cookie header(s).

Walks every Set-Cookie entry (Q44 separator \r\nSet-Cookie: respected) and returns {name: value}. Values are percent-decoded, so a cookie reads back as the string set_cookie() was given rather than its wire form. Multiple cookies with the same name resolve to the last set - matches the wire behaviour where the client also keeps the most-recent value. Caller introspection only; mutation goes through set_cookie().

headerlist property

headerlist: list[tuple[str, str]]

Headers flattened to the (name, value) tuple list the wire emit sends.

Each Set-Cookie (Q44 multi-cookie join) expands to its own tuple, so the caller gets the per-cookie view ASGI requires. Two spellings of one field name collapse to a single entry carrying the last name and value seen, at the position of the first (RFC 9110 Sec. 5.1 makes field names case-insensitive), and a CR/LF/NUL anywhere in a name or value raises ValueError rather than being handed back - both matching the emit paths. Response.headers remains the raw, unfolded view.

data property writable

data: bytes

Body bytes alias for Response.body.

Read returns the current body; writing through the setter replaces the body, invalidates any cached HTTP/1.1 encoded bytes (_encoded), and updates Content-Length on the headers if it was previously set.

vary property writable

vary: Any

The Vary header as a HeaderSet.

Returns a fresh HeaderSet parsed from the current header. Assign a HeaderSet, iterable of strings, or a comma-separated string to replace it. Mutating the returned object does not write back - call add_vary(...) or reassign for that.

allow property writable

allow: Any

The Allow header as a HeaderSet.

Lists the HTTP methods the resource supports (RFC 9110 Sec. 10.2.1). Assign a HeaderSet, iterable, or comma-separated string.

www_authenticate property writable

www_authenticate: str | None

The WWW-Authenticate challenge header - RFC 9110 Sec. 11.6.1.

Sent on 401 Unauthorized to tell the client which auth scheme(s) to use. None when unset.

content_encoding property writable

content_encoding: str | None

The Content-Encoding header - RFC 9110 Sec. 8.4. None when unset.

content_language property writable

content_language: str | None

The Content-Language header - RFC 9110 Sec. 8.5. None when unset.

accept_ranges property writable

accept_ranges: str | None

The Accept-Ranges header - RFC 9110 Sec. 14.3.

Typically bytes (range requests supported) or none (explicitly unsupported). None when the header is unset.

content_range property

content_range: str | None

The raw Content-Range header - RFC 9110 Sec. 14.4. None if unset.

date property writable

date: Any

The Date header as a tz-aware UTC datetime - RFC 9110 Sec. 6.6.1.

Returns None when unset or unparseable. Assign a datetime or POSIX timestamp to set it; assign None to remove it.

location property writable

location: str | None

The Location header - RFC 9110 Sec. 10.2.2. None when unset.

content_location property writable

content_location: str | None

The Content-Location header - RFC 9110 Sec. 8.7. None when unset.

retry_after property writable

retry_after: Any

The Retry-After header - RFC 9110 Sec. 10.2.3.

Returns an int (delay in seconds) when the header is numeric, a tz-aware datetime when it's an HTTP-date, or None when unset. Assign an int / timedelta / datetime to set it; assign None to remove it.

age property writable

age: int | None

The Age header in seconds - RFC 9110 Sec. 5.1. None when unset.

cache_control property

cache_control: Any

Parsed Cache-Control header (read-only view).

For setting directives, prefer set_cache_control(...) which writes the header directly. This property is convenient for introspection: resp.cache_control.max_age, resp.cache_control.no_store, etc.

get_json

get_json() -> Any

Parse the response body as JSON.

Returns None for an empty body. Useful in tests to inspect a JSON response without re-decoding body by hand. Raises if the body is non-empty and not valid JSON.

encode

encode() -> bytes

Encode to raw HTTP/1.1 bytes - called once, cached.

set_cookie(key: str, value: str, max_age: Any = None, expires: Any = None, path: str = '/', domain: str | None = None, secure: bool = False, httponly: bool = False, samesite: str | None = 'Lax', partitioned: bool = False, prefix: Literal['host', 'secure'] | None = None) -> None

Build a Set-Cookie header per RFC 6265.

The cookie name must be a valid RFC 6265 token (no spaces, separators, or control characters) and must not collide with a cookie-attribute keyword (Path, Max-Age, ...); a violation raises ValueError.

samesite defaults to "Lax" - a CSRF-resistant default that matches modern browser behaviour. Pass samesite="None" (with secure=True) for a cookie that must travel on cross-site requests, or samesite=None/"" to omit the attribute.

expires= accepts a datetime, a Unix timestamp int|float, or an already-formatted IMF-fixdate str. When both max_age and expires are set, both are emitted (RFC 6265 Sec. 5.2.2: clients prefer Max-Age when supported, falling back to Expires on legacy IE).

partitioned=True adds the CHIPS Partitioned attribute (Cookies Having Independent Partitioned State) - a partitioned cookie is keyed to the top-level site, so embedded third-party contexts each get an isolated jar. Partitioned requires Secure, so it is only emitted when secure=True.

prefix="host" / prefix="secure" add the RFC 6265bis Sec. 4.1.3 name prefix (__Host- / __Secure-) and enforce its invariants: "secure" requires secure=True; "host" also requires path="/" and no domain. A violation raises ValueError.

The cookie name and value are rejected if they contain CR, LF, or NUL - untrusted data must not be able to inject additional cookies or response headers. dump_cookie performs that CRLF check on all five fields (name, value, domain, path, samesite), so set_cookie does not repeat it.

calculate_content_length

calculate_content_length() -> int

Set Content-Length from len(body) and return the value.

Useful when a caller mutates body directly and wants the header to follow. The ASGI emit path computes Content-Length from body on the fly anyway; this helper is for callers that want it locked into self.headers ahead of time.

set_data

set_data(value: bytes | str) -> None

Replace the response body.

Accepts bytes or str (UTF-8 encoded). Invalidates the cached HTTP/1.1 encode so the new body wire-out on the next emit. Refreshes Content-Length when previously set on the headers.

set_cache_control

set_cache_control(max_age: int | None = None, public: bool = False, private: bool = False, no_cache: bool = False, no_store: bool = False, must_revalidate: bool = False, immutable: bool = False, s_maxage: int | None = None) -> str

Build and set the Cache-Control header - RFC 9111 Sec. 5.2.

Combines the standard directives in the order RFC 9111 Sec. 5.2 documents. Values that are False / None are omitted, so a plain resp.set_cache_control(max_age=3600, public=True) produces Cache-Control: public, max-age=3600. Returns the value set.

add_vary

add_vary(*header_names: str) -> str

Append header names to the Vary response header - RFC 9110 Sec. 12.5.5.

Merges with any existing Vary value (de-duplicates, case-insensitive). Returns the resulting header value. Useful when middleware wants to communicate "this response depends on the named request headers" without clobbering existing entries.

set_basic_auth_challenge

set_basic_auth_challenge(realm: str = 'Authentication Required') -> str

Write a Basic WWW-Authenticate challenge - RFC 7617.

Convenience for the common 401 case: WWW-Authenticate: Basic realm="<realm>", charset="UTF-8". Returns the header value written.

set_content_range

set_content_range(start: int | None, stop: int | None, length: int | None, unit: str = HEADER_VALUE_BYTES) -> str

Write a Content-Range header - RFC 9110 Sec. 14.4.

  • set_content_range(0, 499, 1234) -> bytes 0-499/1234.
  • start/stop both None -> an unsatisfied-range response: bytes */1234 (length required in that form).
  • length None -> unknown total: bytes 0-499/*.

Returns the header value written.

set_etag

set_etag(etag: str, weak: bool = False) -> None

Set the ETag header from an explicit value.

Quotes the value if the caller passed it bare. Prepends W/ when weak=True. Use add_etag() for body-derived MD5 ETags; set_etag is for callers that already have an authoritative tag (DB revision, commit hash, version counter).

get_etag

get_etag() -> tuple[str | None, bool]

Return (etag, is_weak) parsed from the ETag header.

(None, False) when unset. Returned tag keeps its quotes so it compares directly with If-None-Match values.

freeze

freeze() -> None

Pre-compute the cached HTTP/1.1 encode.

For buffered responses, populates _encoded so subsequent access pays no encode cost. For streaming responses, no-op. Used by response caching layers that want immutable bytes.

iter_encoded

iter_encoded() -> Any

Yield the response body.

Return type is mode-dependent and the two modes are NOT interchangeable:

  • Buffered response (is_streamed is False) -> returns a synchronous iterator yielding bytes. Drain with for.
  • Streaming response (is_streamed is True) -> returns the underlying async iterator (AsyncIterator[bytes]). Drain with async for.

Callers must branch on response.is_streamed (or use inspect.isasyncgen / hasattr(it, "__aiter__")) to pick the right loop, e.g.:

it = response.iter_encoded()
if response.is_streamed:
    async for chunk in it:
        ...
else:
    for chunk in it:
        ...

The return shape is mode-dependent: a buffered response yields a synchronous iterator of bytes, a streaming response yields the underlying AsyncIterator[bytes]. Branch on response.is_streamed to drain with the right loop.

iter_chunked

iter_chunked(size: int) -> Any

Yield the response body in fixed-size chunks.

Return type is mode-dependent and the two modes are NOT interchangeable:

  • Buffered response (is_streamed is False) -> returns a synchronous generator yielding bytes slices of length size (the final slice may be shorter). Drain with for.
  • Streaming response (is_streamed is True) -> returns the underlying async iterator unchanged (AsyncIterator[bytes]); size is ignored because chunk boundaries are controlled by the source generator, not the caller. Drain with async for.

Pick the loop based on response.is_streamed:

it = response.iter_chunked(4096)
if response.is_streamed:
    async for chunk in it:
        ...
else:
    for chunk in it:
        ...

size must be positive. The return shape is mode-dependent: branch on response.is_streamed to drain with the right loop.

add_etag

add_etag(weak: bool = False) -> str

Compute and attach an ETag derived from the body.

Uses MD5 of the response body, opaque-quoted per RFC 9110 Sec. 8.8.3. weak=True prepends W/ so the validator is treated as a weak match (matching content but possibly different byte-for-byte). Sets ETag even if one was already set; pass the explicit ETag in __init__(headers=...) to skip this. Returns the value set.

make_conditional

make_conditional(request: Any) -> Response

Downgrade this response to 304 when the request's preconditions match the response's ETag / Last-Modified.

Checks If-None-Match first (per RFC 9110 Sec. 13.2 precedence), then If-Modified-Since. On a match, mutates self to status 304 with no body. Returns self so callers can use it inline: return resp.make_conditional(request).

Handles If-None-Match: * (matches any current representation of the resource) and the weak/strong ETag comparison rules.

check_preconditions

check_preconditions(request: Any) -> Response

Enforce the write-side If-Match precondition (RFC 9110 Sec. 13.1.1).

Raises PreconditionFailed (412) when the request carries an If-Match header that the response's current ETag does not satisfy under the strong comparison (Sec. 8.8.3.1) - the lost-update guard. If-Match: * is satisfied whenever a current representation exists, approximated here by the presence of an ETag header. With no If-Match header the response is returned unchanged. Returns self so it can be chained: return resp.check_preconditions(request).

Invoke this inside a handler (where HTTPException is converted to a response); it raises rather than mutating the status.

set_content_disposition

set_content_disposition(disposition: str = HEADER_VALUE_ATTACHMENT, filename: str | None = None) -> str

Write a Content-Disposition header - RFC 6266.

disposition is "attachment" (force download) or "inline" (render in-browser). When filename is given, an ASCII quotable name uses filename="..." (spaces and punctuation preserved, only \ and " escaped); a non-ASCII or non-quotable name uses only the RFC 5987 filename*=UTF-8''... form, with no lossy legacy slot. Returns the header value written.

delete_cookie(key: str, path: str = '/', domain: str | None = None, secure: bool = False, httponly: bool = False, samesite: str | None = None, partitioned: bool = False, prefix: Literal['host', 'secure'] | None = None) -> None

Delete a cookie by overwriting it with an empty value + Max-Age=0.

The browser only treats the new cookie as a replacement for the existing one if Path, Domain, and the Secure / SameSite / Partitioned attributes match - otherwise it stores both. So a session cookie originally set with Secure; SameSite=None (or with Partitioned) will not be deleted by a plain delete_cookie(key) call. Pass the same flags here. prefix deletes the cookie under its true __Host-/__Secure- wire name and enforces the same invariants on the deletion's attributes.

stream_to async

stream_to(transport: Any, drain: Callable[[], Awaitable[None]] | None = None) -> None

Stream SSE events to transport.

When drain is supplied (the raw serving protocol passes its write-side flow-control awaitable) it is awaited after each event, so a fast event producer feeding a slow client is throttled at the transport buffer instead of growing it without bound. It is a no-op until the buffer crosses the high-water mark.

ServerSentEvent

A single SSE event.

json classmethod

json(payload: Any, *, event: str | None = None, id: str | None = None, retry: int | None = None) -> ServerSentEvent

Build an event whose data field is payload serialized to JSON.

Serialization runs once here, off the per-event stream loop, and the result is stored in the plain data field - so encode() stays the same branch-free path it is for a raw data= string. Use the regular constructor when the payload is already a formatted string.

encode

encode() -> bytes

Encode the event as an SSE-formatted byte string.