Skip to content

Use Scope

Overview

use_scope is a ReactPy hook that returns the ASGI connection scope behind the current websocket.

scope = use_scope()

Use it for what the server knows about the connection: who the peer is, which headers arrived, which server accepted the request. For the page the visitor is looking at, use use_location instead.


Reference

use_scope()

Call it at the top level of a component. It takes no arguments.

from reactpy import use_scope

Returns

The dict at use_connection().scope — the raw ASGI scope, with the same shape as any other ASGI app's scope. It is a live dictionary: nothing copies it for you.

Read ASGI: Websockets for the specification; the keys below are what uvicorn's websocket implementation actually delivered, captured from a live Chrome session against ReactPy:

Key What it holds
"type" "websocket" for these connections.
"path" The ReactPy websocket endpoint, e.g. "/reactpy/". Not the page.
"headers" A list of (bytes, bytes) pairs, lower-cased by the server.
"client" (ip, port) of the peer, or None if unknown.
"server" (host, port) that accepted the connection.
"scheme" "ws" or "wss".
"query_string" The websocket URL's query string, as bytes — this carries ReactPy's own path/qs parameters.
"raw_path" The request path, as bytes, un-decoded.
"root_path" The ASGI root path, normally "".
"http_version" e.g. "1.1".
"asgi" {"version": ..., "spec_version": ...}.
"subprotocols" Requested websocket subprotocols, usually [].
"state" A dict shared across the connection's tasks; empty unless something put values in it.
"extensions" Server-specific extras, e.g. {"websocket.http.response": {}} under uvicorn.

Which extras appear depends on the server: "state" and that "extensions" entry are uvicorn/websockets additions. Read them defensively, with scope.get(...).

Caveats

  • "path" is not the page. For every component in the app it is the websocket endpoint — "/reactpy/" under the default path_prefix. Verified: on the page /some/deep/page?tab=2&user=ada, the scope still says "/reactpy/". Use use_location().path for the page.
  • Headers are bytes. Both halves of each pair are bytes and lower-cased, so compare against b"user-agent" and .decode() the value before using it. There is no dict of headers; see the example.
  • "query_string" is the websocket's, not the page's. It contains ReactPy's own path= and qs= parameters. Parse the page's query with use_location().
  • None is possible. "client" can be absent or None behind a proxy or in-process transport. scope["client"][0] will raise on None; check first.
  • It needs a live connection. In a plain Layout render it raises RuntimeError: No backend established a connection.
  • It does not exist under client-side rendering. With ReactPyCsr there is no connection object, so the hook raises the same RuntimeError.
  • Like every hook, call it only at the top level.

Usage

Reading the peer and a header

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def header(scope, name):
    """Read one request header out of an ASGI scope.

    Headers arrive as a list of ``(bytes, bytes)`` pairs, lower-cased by the
    server, so both halves need decoding before you can compare them.
    """
    wanted = name.lower().encode()
    for key, value in scope.get("headers", []):
        if key == wanted:
            return value.decode()
    return None


@component
def visitor():
    scope = use_scope()
    # Note this is the *websocket* scope. Its "path" is the ReactPy websocket
    # path, not the page the visitor is on -- use use_location() for that.
    # `client` is legitimately absent or None behind some proxies, so read it
    # through a local and check it rather than subscripting the scope directly.
    client = scope.get("client")
    peer = client[0] if client else "unknown"
    return html.div(
        html.h1(f"Hello, {peer}"),
        html.p(f"browser: {header(scope, 'user-agent') or 'unknown'}"),
        html.p(f"connection type: {scope['type']}"),
    )
1
# TODO

header() exists because the ASGI scope hands you raw bytes: it lower-cases the name, matches on bytes, and decodes the value. The component reads scope["client"] defensively because a proxy can leave it unset.

What belongs here and what does not

You want Use
The visitor's IP address use_scope()["client"]
A request header use_scope()["headers"]
The page the visitor is on use_location
The query string use_location
The object mediating the connection use_connection

Troubleshooting

My header lookups always return None

You compared against a str. The keys are bytes and lower-cased: (b"user-agent", b"Mozilla/5.0 ...").

It raises in my tests

A bare Layout render establishes no connection. Wrap the call in try/except RuntimeError, or inject a connection — see use_connection.

Every page shows /reactpy/

That is the scope's "path": the websocket endpoint, identical for all components. Use use_location().path.

I need the request path before the websocket opens

use_scope only exists inside a component, which means inside an established websocket. For pre-connection decisions, handle it in ASGI middleware in front of the ReactPy app — see ReactPyMiddleware.

Authors: Archmonger