Opinionated HTTP server for hosting static files of Single Page Apps from memory with blazing fast speeds.
Goals:
- serve static files
- simple templating to allow injecting ENV-values-based configurations
- easy docker image composition
- sane configuration for select HTTP header values, like CSP and etc
Non-goals:
- SSR
- SPA code builds (you can still do this via your own docker stages)
- API proxying (already handled by the reverse proxy / edge)
- HTTPS (already handled by the reverse proxy / edge)
Using the onbuild
image:
FROM ghcr.io/mozgiii/sap:latest-onbuild
or using the regular image and copying the files manually:
FROM ghcr.io/mozgiii/sap:latest
COPY . /app
ENV ROOT_DIR=/app
All the configuration parameters are set via env vars.
See crates/sap/src/main.rs
to learn about the available settings.
There are two (opinionated) ways for configuring Single Page Applications
with sap
:
- substituting values from the env variables for the corresponding
JSON object keys in the HTML
script
tag with thetype
attribute set toapplication/spa-cfg
on the/
route - this is called HTML templating, and - substituting values from the env variables for the corresponding
JSON object keys in the JSON file on the
/config.json
route - this is called JSON templating.
The in both cases we take the JSON object. We iterate over the keys of
the object, we take each key, turn it into CONSTANT_CASE
add prefix and
check if a env var with this name exists. If it doesn't - the value of the key
is left as it is in the original JSON, but if the env var is set - then
we replace the value in JSON with the value of the env var.
Warning
We require that JSON object used for configuration has only string
values.
In TypeScript that requirement would look like this:
type Config = { [key: string]: string };
Here is a simplified example of index.html
file (used as the root (/
) route):
<html>
<body>
<script type="application/spa-cfg">{"myKey": "default value"}</script>
</body>
</html>
With sap
using default SPA configs prefix APP_
and with
the APP_MY_KEY
env var set to override value
, this is what the returned
page body will look like:
<html>
<body>
<script type="application/spa-cfg">{"myKey": "override value"}</script>
</body>
</html>
If we assume that you have a Dockerfile
like this:
$ cat Dockerfile
FROM ghcr.io/mozgiii/sap:latest
ENV ROOT_DIR /app
COPY build /app
and the index.html
in the build
directory like this:
$ cat build/index.html
<html>
<body>
<script type="application/spa-cfg">{"myKey": "default value"}</script>
</body>
</html>
we can build the test image like this:
$ docker build . -t sap-test
...
At this point we have our app dockerized and ready to run.
$ docker run --rm -it -p 8080:8080 sap-test
...
2024-01-01T12:34:56.789123Z Successfully applied HTML templating route=/ dir_entry_path="/app/index.html"
...
2024-01-01T12:34:56.789123Z INFO sap: About to start the server addr=0.0.0.0:8080
...
In another terminal we can run the following command to try it:
$ curl http://localhost:8080
<html>
<body>
<script type="application/spa-cfg">{"myKey": "default value"}</script>
</body>
</html>
The neat thing is that we can change the settings as we please, without rebuilding the app from source or recreating docker image - just like it was intended with containers!
Continue with stopping the sap-test
container via Ctrl+C
...
C^
... (stopping) ...
$
... and running with the env var to change the SPA configuration:
$ docker run --rm -it -p 8080:8080 -e APP_MY_KEY="override value" sap-test
...
2024-01-01T12:34:56.789123Z Successfully applied HTML templating route=/ dir_entry_path="/app/index.html"
...
2024-01-01T12:34:56.789123Z INFO sap: About to start the server addr=0.0.0.0:8080
...
Then in the other terminal:
$ curl http://localhost:8080
<html>
<body>
<script type="application/spa-cfg">{"myKey": "override value"}</script>
</body>
</html>
Note how the configuration value myKey
has changed.
This can be utilized from the SPA with the code like this:
function readConfig() {
const el = document.querySelector("script[type=\"application/spa-cfg\"]");
const configText = el.innerText;
const config = JSON.parse(configText);
return config;
}
This allows you to get the dynamic configuration without rebuilds. This way also
has a benefit of not involving any async
or Promise
s.
Important
JSON templating in an opt-in, and requires CONFIG_JSON_TEMPLATING=true
env
var to be set to work.
An example of config.json
file (with the /config.json
route):
{"myKey":"default value"}
With sap
with CONFIG_JSON_TEMPLATING=true
, using default SPA configs prefix
APP_
and with the APP_MY_KEY
env var set to override value
, this is what
the returned /config.json
response body will look like:
{"myKey":"override value"}
If we assume that you have a Dockerfile
like this:
$ cat Dockerfile
FROM ghcr.io/mozgiii/sap:latest
ENV ROOT_DIR /app
COPY build /app
and the config.json
in the build
directory like this:
$ cat build/config.json
{"myKey":"default value"}
we can build the test image like this:
$ docker build . -t sap-test
...
At this point we have our app dockerized and ready to run.
Well, technically it is just the config, but you can add the rest of the files of your app there as well.
$ docker run --rm -it -p 8080:8080 sap-test
...
2024-01-01T12:34:56.789123Z Successfully applied JSON templating route=/config.json dir_entry_path="/app/config.json"
...
2024-01-01T12:34:56.789123Z INFO sap: About to start the server addr=0.0.0.0:8080
...
In another terminal we can run the following command to try it:
$ curl http://localhost:8080/config.json
{"myKey":"default value"}
The neat thing, just like with HTML templating, is that we can change the settings as we please, without rebuilding the app from source or recreating docker image - just like it was intended with containers!
Continue with stopping the sap-test
container via Ctrl+C
...
C^
... (stopping) ...
$
... and running with the env var to change the SPA configuration:
$ docker run --rm -it -p 8080:8080 -e APP_MY_KEY="override value" sap-test
...
2024-01-01T12:34:56.789123Z Successfully applied JSON templating route=/config.json dir_entry_path="/app/config.json"
...
2024-01-01T12:34:56.789123Z INFO sap: About to start the server addr=0.0.0.0:8080
...
Then in the other terminal:
$ curl http://localhost:8080/config.json
{"myKey":"override value"}
Again, just like with HTML templating, configuration value of the key myKey
has changed.
This can be utilized from the SPA with the code like this:
const readConfig = async () => fetch("/config.json").then(res => res.json());