topcoat_runtime/layer.rs
1mod rerun;
2#[cfg(not(target_family = "wasm"))]
3mod socket;
4
5pub use rerun::*;
6use topcoat_core::context::Cx;
7use topcoat_router::{Body, Layer, LayerFuture, Next, Path};
8
9/// The WebSocket subprotocol for runtime connections.
10///
11/// Request this subprotocol at a page's URL to open a connection through
12/// [`RuntimeLayer`]. The browser can then request renders and receive the
13/// content as frames. WebSocket connections require a native server.
14pub const RUNTIME_PROTOCOL: &str = "topcoat-runtime";
15
16/// A [`Layer`] that handles runtime requests at each page's URL.
17///
18/// The browser can request a render in two ways:
19///
20/// - Send a `POST` with `X-Topcoat-Runtime: true` and the document's signal values as JSON. The
21/// layer rewrites this to a `GET` at the same path and query. The page, layouts, and guards run
22/// with the supplied signal values. The rewritten request has an empty body and no
23/// [`RUNTIME_HEADER`], `Content-Type`, or `Content-Length` headers. To read the original method,
24/// use [`original_method`](topcoat_router::request::original_method).
25/// - Open a WebSocket with the [`RUNTIME_PROTOCOL`] subprotocol. Each render request on this
26/// connection describes an HTTP request: a method, a path, a body, and a few runtime headers,
27/// such as [`RUNTIME_HEADER`] for a page rerun. The layer dispatches it with the handshake's
28/// other headers as a connected render. Renders run side by side, and each message sent back
29/// names the render it belongs to. A connection may have at most
30/// [`max_runs_per_connection`](crate::RouterBuilderRuntimeExt::max_runs_per_connection) renders
31/// at once.
32///
33/// WebSocket connections are supported on native servers. HTTP page reruns
34/// are also available on WebAssembly.
35///
36/// Other requests pass through unchanged, including form submissions and
37/// WebSocket requests that do not use the runtime subprotocol.
38///
39/// Register this layer with
40/// [`RouterBuilderRuntimeExt::runtime`](crate::RouterBuilderRuntimeExt::runtime).
41/// Call it after adding your application's pathless layers so they receive
42/// the rewritten `GET`.
43#[derive(Debug, Clone, Copy, Default)]
44pub struct RuntimeLayer;
45
46impl Layer for RuntimeLayer {
47 fn path(&self) -> Option<&Path> {
48 None
49 }
50
51 fn handle<'a>(&'a self, cx: &'a Cx, body: Body, next: Next<'a>) -> LayerFuture<'a> {
52 #[cfg(not(target_family = "wasm"))]
53 if socket::requested(cx) {
54 return Box::pin(socket::accept(cx, body));
55 }
56 if rerun::requested(cx) {
57 return Box::pin(rerun::dispatch(cx, body));
58 }
59 next.run(cx, body)
60 }
61}
62
63/// The limit set with
64/// [`RouterBuilderRuntimeExt::max_runs_per_connection`](crate::RouterBuilderRuntimeExt::max_runs_per_connection).
65#[derive(Debug, Clone, Copy)]
66// Read only by the WebSocket handler, which native servers compile.
67#[cfg_attr(target_family = "wasm", allow(dead_code))]
68pub(crate) struct MaxRunsPerConnection(pub(crate) usize);