musli-web
This crate provides a set of utilities for working with various web-based APIs and Müsli.
It includes support for:
axumJsonintegration, allowing you to use Müsli for serialization and deserialization in your Axum applications.axumws::Serverintegration, allowing you to build the server side of the websocket protocol this crate implements.yewintegration, allowing you to use Müsli for communicating with websocket clients using a well-defined API.tokio-tungsteniteintegration through [tungstenite029] and [tungstenite030], allowing non-browser clients to talk to the same websocket API.
Note that the organization of the modules include the version of the corresponding
crate. Unstable versions are prefixed with 0, such as [yew023].
See the following modules for how to use:
- [
axum08] foraxum0.8.xintegration. - [
tungstenite029] fortokio-tungstenite0.29.xintegration. - [
tungstenite030] fortokio-tungstenite0.30.xintegration. - [
yew022] foryew0.22.xintegration. - [
yew023] foryew0.23.xintegration. - [
web03] forweb-sys0.3.xintegration.
Features
- A wasm-compatible and convenient websocket protocol with support for request-reply and broadcasts.
- Channel support allowing the server to identify the source of a message and clients to correlate messages.
- Typed request errors. A handler can fail a request with an error the client decodes back into its type, rather than a message it has to parse.
- A negotiable body format, so a client can pick how much schema evolution it needs and the server adapts to it. Message envelopes never depend on the outcome, so negotiation itself is always readable.
- A selectable socket mode, so a connection can be switched from binary
frames to text ones whose envelope is a block of
<key>: <value>lines. Together with [Format::Json] this makes the whole conversation readable in a proxy or a browser inspector, which is what it is for. - An explicitly versioned protocol. Every message states the [
VERSION] it was written against and a header which is not recognized is refused rather than skipped, so peers which do not speak the same protocol part ways before either has acted on what the other said. - An optional application version in the handshake, so a page which was loaded before the server was upgraded learns that it has to reload instead of talking to an API which has moved on.
The available formats, from most compact to most capable, are
[Format::Packed], [Format::Storage], [Format::Wire] (the default, and
the least capable one which is fully upgrade safe), [Format::Descriptive]
and [Format::Json]. Each is gated behind a format-* feature.
Choosing a format
The body of every message is encoded with one format per connection. The envelope around it never depends on the choice, so the two sides can always talk long enough to settle it.
- The client asks. Call
formaton the service builder (ServiceBuilderin the browser andclient::ServiceBuilderfor native clients) with aFormat. A client which says nothing speaksFormat::Wire, which tolerates fields being added, removed and unknown, so client and server can be deployed separately.FormatimplementsFromStrandDisplay, so a choice can come from a configuration value or a query string, as in"json".parse::<Format>(). - The server accepts. By default it accepts every format it was built with.
Call
with_formatson the server to narrow that to a list, such as.with_formats(&[Format::Wire, Format::Json]). This can never add a format which was not built in. - A request for a format the server does not accept is reported to the client
through its
on_errorcallback and the connection continues withFormat::Wire. The format in use can be read back withformat()on the client's handle. Typed request errors are encoded in the connection's format too. - A client that requests
Format::Jsoncan also ask for text frames withmode(Mode::Text), which makes the whole conversation readable in a browser inspector. Text frames require a human readable format. - The application version of the handshake is independent of the format. It is what tells an old page that the server has moved on.
A format is only available if its format-* feature is enabled, in the
crate that performs the encoding. format-wire is on wherever api is, so
Format::Wire is always available.
| Role | Features to enable | Notes |
|---|---|---|
| Shared api crate | api |
Enables format-wire. Nothing else is needed to define requests and broadcasts. |
| Server | axum08 |
Enables format-storage and format-wire. Add format-descriptive, format-json or format-packed for each further format it should accept. |
| Browser client | yew022, yew023 or web03 |
Enables format-storage and format-wire. Add the format-* feature of any other format it selects. |
| Native client | tungstenite029 or tungstenite030 |
Enables format-storage and format-wire. Add the format-* feature of any other format it selects. |
Only enable what is used. A browser client which never calls format speaks
Format::Wire and needs no format-* feature of its own, and a server which
only serves such clients does not need format-descriptive or format-json.
Cargo unifies features across a build, so in a workspace a format-* feature
enabled by one crate is on for every crate that uses musli-web. Check what
each crate really selects.
The json feature is unrelated: it provides the Json extractor for axum
and does not enable format-json. The formats feature enables every
format-* feature at once.
Examples
apiis the example crate which defines API types shared between server and client.serveris the axum-based server implementation.clientis the yew client communicating with the server.
You can run the client like this:
&&
You can run the server like this:
&&