Skip to main content

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);