apsis-book/README.md

233 lines
9.9 KiB
Markdown

# apsis-web
[简体中文](./README.zh.md) | English
The web service layer of ApsisRS. It presents the ApsisRS computational
kernels for commercial spaceflight and satellite internet systems in a
browser: the concept documentation, a knowledge graph, the interface
documentation extracted from the library source, and an online tool set that
runs the kernels on parameters the user supplies.
The service builds to a single binary that embeds every asset it serves. It
does not read a content directory or a frontend build at run time, and it
does not need either to be present next to the binary.
## What it serves
| Section | Content | Route prefix |
|---------|---------|--------------|
| Concepts | Documents on the time scales, the reference frames, the element formats, and the propagation models, with the units and the applicable range of each | `/api/docs` |
| Knowledge graph | The concepts and their relationships, plus the crate structure of the library and the algorithm flow graphs | `/api/graph` |
| Interface | The public interface of the ApsisRS layers the service builds against, extracted from the source doc comments | `/api/api` |
| Online tools | TLE parsing, SGP4 propagation, track sampling, time scale conversion, coordinate conversion, frame rotation, Keplerian conversion, and Hohmann transfer planning | `/api/tools` |
## Design constraints
The service follows the constraints of the workspace it belongs to.
- **One runtime dependency.** The only entry in `[dependencies]` is
`apsis-rs`, through a path dependency. The HTTP server, the JSON codec,
the router, the Markdown renderer, and the graph model are all written
against the standard library.
- **No `unsafe`.** The crate does not contain an unsafe block.
- **No panic in a request path.** A malformed request, a bad parameter, or
a failing computation produces a response, not a panic. The worker that
serves a request catches a panic in the handler as a final guard and
answers with a server error.
- **Self-contained comments.** Every comment explains the code, the
algorithm, the interface, or the project. No comment refers to a private
document, an instruction, or a plan.
## Building
The frontend must be built before the release binary so that its output can
be embedded. A release build does this automatically.
Every built asset carries a content hash in its name, so replacing the binary
replaces those names. A browser therefore cannot be holding a stale bundle:
the new bundle is requested under a URL it has never seen. The entry point
is served with `no-cache, must-revalidate`, and hashed assets with
`public, max-age=31536000, immutable`. The service registers no service
worker.
```sh
# Development: a debug build embeds whatever is in frontend/dist and does
# not run the frontend build.
cargo build
# Release: build.rs builds the frontend and embeds the result.
cargo build --release
```
The binary is at `target/release/apsis-web`.
Environment variables:
| Variable | Effect |
|----------|--------|
| `APSIS_WEB_HOST` | Interface to bind, default `127.0.0.1` |
| `APSIS_WEB_PORT` | Port to bind, default `8080` |
| `APSIS_WEB_ADDRESS` | Bind address as `HOST:PORT`, overriding the two above |
| `APSIS_WEB_WORKERS` | Worker threads, default 4 |
| `APSIS_WEB_SKIP_FRONTEND` | Set to `1` to skip the frontend build in a release build |
| `APSIS_WEB_FRONTEND_DIR` | Frontend directory, default `frontend` |
Command line options override the environment:
```sh
# Bind every interface on port 8080.
apsis-web --host 0.0.0.0 --port 8080
# The combined form is equivalent.
apsis-web --address 0.0.0.0:8080
```
`--host` and `--port` may be given together. `--address` cannot be combined
with either of them, because the order in which they would apply is not
defined. A host may be a literal address, an IPv6 literal in brackets, or a
name the operating system resolves.
### Language
The interface is bilingual and opens in Chinese. The reader switches to
English with the control in the top bar; the choice is stored in the browser
and applies to every page.
The backend carries every user facing string in both languages, so the switch
takes effect without a request. A concept document is written once in English
as `content/concepts/<id>.md` and translated as
`content/concepts/<id>.zh.md`. A document without a translation is shown in
English to every reader, with a note saying so.
## Running
```sh
./target/release/apsis-web --address 127.0.0.1:8080
```
Open `http://127.0.0.1:8080`. The JSON interfaces are available without the
frontend:
- `GET /health`
- `GET /api/docs`, `GET /api/docs/<id>`, `GET /api/docs/search?q=`
- `GET /api/graph`, `GET /api/graph/crates`, `GET /api/graph/flows`, `GET /api/graph/<flow>`
- `GET /api/api`, `GET /api/api/crates/<name>`, `GET /api/api/items/<name>`
- `GET /api/tools`, `GET /api/tools/<id>`, `POST /api/tools/<id>/run`
A tool call takes a JSON object of parameters and returns a shaped result:
```sh
curl -s http://127.0.0.1:8080/api/tools/hohmann-transfer/run \
-H 'content-type: application/json' \
-d '{"from_radius": 6778, "to_radius": 42164}'
```
## Content layout
The content is a superset of what any one library implements. It has two
groups.
**概念 / Concepts** describes the field. The layers follow the ones the
library is built in, because that is the order a reader meets the subject:
| Layer | Subject |
|-------|---------|
| 导论 Introduction | Scope, organisation, and how to read a page |
| 基础层 Foundations | Units, dimensions, numerical method, error |
| 物理层 Physics | Time scales and reference frames, which are two unrelated subjects and are both held here |
| 轨道层 Orbit | Elements, element formats, perturbations, propagators, manoeuvres |
| 射频与传播 Radio and propagation | Doppler, link budgets, the medium, interference |
| 星座与网络 Constellation and network | Constellations, access, topology, handover, routing |
| 运行层 Operations | Telemetry, tracking, command, planning |
| 仿真层 Simulation | Scenarios, events, and what a result means |
**实现 / Implementation** describes what one particular library implements
of the above, including how it is organised and where its algorithms come
from. Keeping it separate is what makes the concept set independent: a page
about link budgets does not change when a library changes.
A document belongs to the layer named by the directory it is in, so adding a
document means creating it under its layer and listing it in
`chapters.json`.
## Regenerating the interface catalog
The interface documentation is generated from the ApsisRS sources rather than
written by hand. Run the extractor from this directory when the sources
change, then rebuild:
```sh
scripts/extract_api.sh
cargo build --release
```
The script reads the workspace at `../apsis-rs` by default; set
`APSIS_RS_WORKSPACE` to point at a checkout elsewhere. It requires Python 3.8
or later. The Rust build does not run it and does not depend on Python.
## Editing the content
| Path | Contents |
|------|----------|
| `content/concepts/chapters.json` | The groups, their layers, and the reading order, with bilingual titles |
| `content/concepts/<layer>/*.md` | Documents, one directory per layer; the directory gives the layer, the file name gives the identifier |
| `content/concepts/<layer>/*.zh.md` | Chinese translations, named for the file they translate |
| `content/graph/concepts.json` | The concept graph |
| `content/flows/*.json` | The algorithm flow graphs |
| `content/api.json` | The generated interface catalog; do not edit by hand |
| `content/api.zh.json` | Chinese translations of the crate summaries and item doc comments |
A document header declares `title`, `summary`, `tags`, and `prerequisites`.
The identifier and the layer are not declared: the identifier is the file
name and the layer is the directory, so the two cannot disagree with the
tree. A cross reference is written `[[id]]` or `[[id|label]]` and renders as
a link that the frontend routes without a page load.
## Layout
```text
apsis-web/
├── build.rs embed the frontend build and the text content
├── Cargo.toml one runtime dependency: apsis-rs
├── content/ authored documents, graphs, and generated catalog
│ └── concepts/<layer>/ documents, one directory per layer
├── frontend/ Svelte and TypeScript, built by Vite
│ ├── src/lib/ API client, router, components, formatting
│ ├── src/pages/ one component per route
│ └── dist/ build output, embedded by build.rs
├── scripts/extract_api.sh interface catalog generator
├── src/
│ ├── api/ the router and the JSON payloads
│ ├── docs/ content, Markdown, documents, interface catalog
│ ├── embedded/ access to the embedded frontend assets
│ ├── graph/ the knowledge graph and the flow graphs
│ ├── http/ request, response, and server
│ ├── tools/ the tool registry, the handlers, the result shape
│ ├── json.rs the JSON codec
│ ├── main.rs the process entry point
│ └── state.rs the parsed content, shared by every worker
└── tests/service.rs end to end tests against the built binary
```
## Testing
```sh
cargo test # unit tests and end to end tests
cargo clippy --all-targets
cargo fmt --check
cd frontend && npm run check
```
The end to end tests start the built binary on an ephemeral port and make
plain TCP requests against it.
## License
GNU General Public License, version 3 or later, and that alone. See
`LICENSE`.
The service depends on `apsis-rs`, which is offered under MIT or
Apache-2.0. Both of those permit redistribution inside a GPL work, so the
two are distributed together under these terms. No other licence is
offered for this service.