ros_sugar.ui_node.api_utils#
API helpers built on Starlette, which serves the API and the browser front end
Module Contents#
Classes#
Middleware refusing commands another site’s page sends through a browser. |
|
Middleware forbidding other sites to frame the pages (clickjacking). |
|
Middleware requiring an API key on the API, or the browser front end’s cookie. |
Functions#
Field schema for a topic’s ROS message type (empty dict on failure). |
|
JSON-serializable representation of a ROS message (e.g. a service response). |
|
JSON-serialize cached output content. |
|
Turn an overlay/path payload into a map op. |
|
Interface name from the URL path, normalized like |
|
Parse a request’s JSON body. |
|
Refuse a WebSocket for a name the recipe did not declare. |
|
Accept a websocket and push sampled JSON at the client’s requested rate. |
|
Accept a websocket and push JSON the moment new data arrives (no sampling) |
|
The uvicorn configuration serving |
|
Whether a request is a command. Any request but GET, HEAD and OPTIONS,
and any WebSocket not in |
API#
- ros_sugar.ui_node.api_utils.topic_schema(topic) Dict[str, Any]#
Field schema for a topic’s ROS message type (empty dict on failure).
- ros_sugar.ui_node.api_utils.msg_to_jsonable(msg: Any) Any#
JSON-serializable representation of a ROS message (e.g. a service response).
- ros_sugar.ui_node.api_utils.content_to_jsonable(content: Any) Any#
JSON-serialize cached output content.
Specialized callbacks already return JSON-native content; types without a specialized
_get_ui_contentyield a raw ROS message, which is converted here.
- ros_sugar.ui_node.api_utils.marker_from_content(topic_name: str, type_name: str, content: Any) Optional[Dict]#
Turn an overlay/path payload into a map op.
Point/Pose/Odometry become
{op:"overlay", x, y[, theta]}markers; Path becomes{op:"path", points}. ReturnsNoneif there is nothing to render.
- ros_sugar.ui_node.api_utils.name_param(conn) str#
Interface name from the URL path, normalized like
Topicnames. Routes use{name:path}so namespaced names (ns/topic) stay addressable.
- async ros_sugar.ui_node.api_utils.json_body(request) Any#
Parse a request’s JSON body.
An empty body is an empty request,
{}, so body-less calls (e.g. a Trigger service) still work. A body that is not valid JSON isNone, which callers reject: treating it as{}would publish a default message.
- async ros_sugar.ui_node.api_utils.reject_websocket(websocket, reason: str) None#
Refuse a WebSocket for a name the recipe did not declare.
Accepted first and then closed with 1008 and a reason. Closing before accepting makes the server answer the handshake with a bare HTTP 403.
- async ros_sugar.ui_node.api_utils.stream_at_rate(websocket, default_rate, max_rate, sample) None#
Accept a websocket and push sampled JSON at the client’s requested rate.
Reads an optional
?rate=<hz>query param (clamped tomax_rate).sample()returns(payload, done):payloadis sent as JSON when notNone; the loop stops whendoneis True or the client disconnects. The caller must validate the resource before calling this (it accepts the connection).
- async ros_sugar.ui_node.api_utils.stream_pushed(websocket, subscribe, unsubscribe, sample) None#
Accept a websocket and push JSON the moment new data arrives (no sampling)
- ros_sugar.ui_node.api_utils.server_config(app: Any, port: int, certificate: Optional[ros_sugar.ui_node.security.Certificate])#
The uvicorn configuration serving
app, over TLS when a certificate is given.
- ros_sugar.ui_node.api_utils.is_command(scope, read_streams: Sequence[str]) bool#
Whether a request is a command. Any request but GET, HEAD and OPTIONS, and any WebSocket not in
read_streams.
- class ros_sugar.ui_node.api_utils.SameOriginGuard(app, open_streams: Sequence[str] = ())#
Middleware refusing commands another site’s page sends through a browser.
A command is as
is_commanddecides, withopen_streamsas the streams that only send data out. One whoseOriginhost is not the request’sHostorX-Forwarded-Hostis refused with 403, or a WebSocket close 1008. Clients that are not browsers send noOriginand pass.- Parameters:
app – The ASGI app to guard
open_streams – fnmatch patterns of WebSocket paths any site may open, streams that only send data out
- class ros_sugar.ui_node.api_utils.NoFraming(app)#
Middleware forbidding other sites to frame the pages (clickjacking).
- class ros_sugar.ui_node.api_utils.ApiKeyGuard(app, keys: ros_sugar.ui_node.security.ApiKeys, session_key: Optional[str] = None, prefix: str = '/api/', open_paths: Sequence[str] = (), read_streams: Sequence[str] = (), logger: Any = None)#
Middleware requiring an API key on the API, or the browser front end’s cookie.
A request under
prefix, other thanopen_paths, needsAuthorization: Bearer <key>with thecommandscope for a command (asis_commanddecides) and thereadscope otherwise. It is refused with 401 without a valid key and 403 without the scope, or a WebSocket close 1008.With a
session_key, any other response sets a cookie granting the API to the browser that loaded the page. It is HttpOnly, Secure and SameSite=Strict, so other sites’ pages cannot use it.- Parameters:
app – The ASGI app to guard
keys – The API keys
session_key – Secret the cookie is derived from. None when no browser front end is served, so no cookie is set
prefix – Path prefix of the API
open_paths – Paths under the prefix open to anyone
read_streams – fnmatch patterns of WebSocket paths that only send data out
logger – Logs a key’s first use from each address, and the first refusal for each address