Skip to main content

Crate rich_web

Crate rich_web 

Source
Expand description

Serve intuiTUIve apps, and any terminal program, to a web browser.

serve listens on an address and runs one App per browser tab, each in a process of its own (see Isolation). The page is xterm.js, vendored in this crate and served by it; the app’s frames go to it as terminal output over a WebSocket, and its keys, mouse and resizes come back as the same Events a terminal gives. No async runtime, and nothing fetched from a CDN.

The framework is re-exported as rich_web::intuituive, so an app served this way needs no other dependency.

use rich_web::intuituive::prelude::*;

fn counter() -> App {
    App::new(|| {
        let count = signal(0);
        text!("[b]Count:[/] {count} (+ adds one)")
            .on_key("+", move |_| count.update(|c| *c += 1))
    })
}

fn main() -> std::io::Result<()> {
    // Prints http://127.0.0.1:8080/?token=… to open in a browser.
    rich_web::serve("127.0.0.1:8080", counter)
}

Server is the same with options: a session cap, a fixed token, the page’s title, origins a proxy serves the page from, and spawn to run in the background.

§Isolation

Each app session runs in a process of its own (Isolation::Processes, the default): the server starts this program again with SESSION_ENV set, and when that process reaches the same serve or Server::bind call, it runs the tab’s app there, connected back to the server over loopback, and exits. A crash, a hang or a runaway allocation then ends one tab; the server kills a session’s process with what it started (on Unix its process group, on Windows a job object) when the tab closes, the server stops or the server dies, and on Unix can cap its memory and CPU time (Server::session_memory, Server::session_cpu). Server::session_command starts it some other way (an interpreter and its script), and Isolation::Threads runs every session on a thread of the server instead, as 0.0.18 did: for tests, and programs that cannot start themselves again.

§Any terminal program

serve_command runs a program on a pseudo-terminal per tab instead (a PTY on Unix, ConPTY on Windows, through rs-rich-embed’s LocalPty) and streams it to the same page: keys, pastes and resizes go to the program, its output to the page, and its exit ends the session with its status shown. Server::bind_host takes any other PtyHost (an SSH session, a container, a test host).

fn main() -> std::io::Result<()> {
    rich_web::serve_command("127.0.0.1:8080", ["htop"])
}

A page that falls behind holds the program back rather than the server buffering without end: at most Server::max_buffered bytes of output wait for the page, and past that the program’s writes wait, as they do on a slow terminal. Input is bounded too: with 1 MiB that the program has not read held for it, the server stops reading the page until it reads some, so a flood of pastes waits in the network.

§A DOM renderer

Renderer::Dom (or &renderer=dom in the page’s address) draws an app as a grid of styled spans instead of xterm.js, and carries its accessibility tree as ARIA: roles, names and states, the focused node focused in the page, and announcements in a live region, so a browser’s screen reader reads the app. Its messages are described in dom.

§Security

Anyone who can open a session can do whatever the app (or the program) lets them, so the server is closed by default:

  • It listens where it is told. Give it a loopback address (127.0.0.1, [::1], localhost) to keep it on this computer, as the examples do. Any other address prints a warning.
  • A random token (128 bits from the operating system) is part of the URL printed at start; the page and the WebSocket both refuse a request without it.
  • The Origin header is checked: browsers let any web page open a WebSocket to a localhost port, so the upgrade must come from this server’s own page (its address, which must also be the Host asked for, against DNS rebinding) or from an origin allowed with Server::allow_origin. The Host must be one the server answers to even when it is bound to every interface: the loopback addresses, localhost, this machine’s name, the address the connection arrived on, and any Server::allow_host names.
  • Sessions are capped (DEFAULT_MAX_SESSIONS unless set with Server::max_sessions); one more is refused with 503.
  • Requests are timed: a connection has 10 seconds to send its whole request, however slowly it trickles in, and with 64 still sending, one more is answered 503.

There is no other authentication and no TLS. To reach the server from another machine, put it behind a reverse proxy that has both, and allow the proxy’s origin.

§Features

  • server (default): everything above. Without it the crate is input (the page’s key and mouse decoder) and dom (the DOM renderer’s messages) only, with no networking or PTYs, so it builds for wasm32-unknown-unknown: the docs playground decodes xterm.js’s input with it, in the browser.
  • syntax (default): intuiTUIve’s syntax highlighting (syntect).

Re-exports§

pub extern crate rich_intuituive as intuituive;

Modules§

dom
The DOM renderer’s side of the wire: what the server sends a page that draws an app itself (Renderer::Dom), as a grid of styled spans with the app’s accessibility tree laid over it as ARIA.
input
The browser’s input, decoded into rs-rich-interact Events.

Structs§

Command
What a program runs on: the PTY host trait and the local implementation, from rs-rich-embed. A program and its arguments, environment and directory, for LocalPty. A string is a program name; an array or a vector is the program and its arguments.
ExitStatus
What a program runs on: the PTY host trait and the local implementation, from rs-rich-embed. How a program ended.
Handle
A server running in the background, from Server::spawn. Stopping it (or dropping it) stops accepting connections and ends every session.
LocalPty
What a program runs on: the PTY host trait and the local implementation, from rs-rich-embed. A program on this machine, on a PTY (ConPTY on Windows), through portable-pty as rs-rich-record’s tapes run theirs.
Server
A server that is bound and ready to run or spawn, with its options.

Enums§

Isolation
Where each app session runs.
Renderer
How an app’s page draws it.

Constants§

DEFAULT_MAX_BUFFERED
The output a program’s session holds for its page unless told otherwise (Server::max_buffered): 1 MiB.
DEFAULT_MAX_SESSIONS
The sessions a server runs at once unless told otherwise.
SESSION_ENV
The variable a session’s process is started with: what it needs to connect back and run its session. Not for setting by hand.
XTERM_VERSION
The version of xterm.js the page uses, vendored in this crate.

Traits§

PtyHost
What a program runs on: the PTY host trait and the local implementation, from rs-rich-embed. Starts a program and carries its bytes and its size: what a terminal pane talks to. Every method is called on the app’s thread.

Functions§

serve
Serve app at addr: print the URL to open (with its token), then run one app, made by app, per browser tab that opens it, until the process ends. Server::bind and Server::run with the defaults.
serve_command
Serve command at addr: print the URL to open (with its token), then run the program on a pseudo-terminal of its own for each browser tab that opens it, until the process ends. Server::bind_command and Server::run with the defaults.