# Local server

_reading the wiki on this machine_

**Intent.** The local server exists so that following a citation lands on the code it names, readable in the browser, rather than on a download or a dead link. What a person sees should always be what was just built.

`wiki serve` builds the site, serves it at 127.0.0.1 on port 8787, and opens a browser at it.[^serve] It is
also what `wiki` does when it is given no command at all.[^default] How the site itself is made is
described on [Site](../site/index.md).

## Root

**The server shows the whole project, not only the site.**[^root] A page can link to any file in the
project, and a browser cannot follow a link above the folder it is served from.[^paths]

That means anything that can reach the server can read every file in the project.[^root] For that reason, it
answers only on this machine.[^loopback]

## Sources

Code, settings and markdown files open **as text in the browser** instead of downloading, across 38 common
file types.[^text] Anything else, such as the site's own pages and pictures, is served as it normally
would be.[^text] A file named only `.env` has no suffix, so it is sent as a download.[^text]

## Freshness

The server never rebuilds the site, so a page edited while it runs appears after `wiki build` and a
reload.[^rebuild] **Nothing the server sends may be cached**, so a rebuilt page appears on the next
reload.[^cache] The
addresses of the stylesheet and the script also change whenever their contents do, so even a tab holding
an old copy picks up the new one.[^stamp]

## Errors

A request answered with a success status is not logged; redirects and failures are.[^log] If the port is already in use, the server says
so, names `--port` as the way to pick another, and exits with 2.[^port]

[^serve]: `src/builder/serve.py` — `serve()` binds `127.0.0.1` and opens the site's address with
    `webbrowser.open()`; `src/builder/cli.py` — `run()` builds first, and `main()` defaults the port to
    `PORT`, 8787.
[^default]: `src/builder/cli.py` — `run()` treats no command as `"serve"`.
[^root]: `src/builder/serve.py` — `serve()` roots the handler at the project and points the browser at
    the site beneath it.
[^paths]: `src/builder/build.py` — `rewrite_references()` turns a link to a file outside the site into a
    path from the page to that file.
[^loopback]: `src/builder/serve.py` — `serve()` listens on `127.0.0.1` only.
[^text]: `src/builder/serve.py` — `shown_as_text()` answers every suffix in `AS_TEXT` as `text/plain`, and
    leaves every other type to the stock handler, which sends a name with no suffix as
    `application/octet-stream`.
[^rebuild]: `src/builder/cli.py` — `run()` builds once before it calls `serve()`; `src/builder/serve.py` —
    `serve()` only serves.
[^cache]: `src/builder/serve.py` — `Handler.end_headers()` sends `Cache-Control: no-store, must-revalidate`,
    `Pragma: no-cache` and `Expires: 0`.
[^stamp]: `src/builder/build.py` — `write_site()` adds a digest of each asset to its address.
[^log]: `src/builder/serve.py` — `Handler.log_message()` drops any status starting with 2, and logs every
    other.
[^port]: `src/builder/serve.py` — `serve()` catches the `OSError` a taken port raises and returns 2;
    `src/builder/cli.py` — `main()` defines `--port`.
