Compare commits

..

14 Commits

17 changed files with 494 additions and 218 deletions

View File

@ -36,7 +36,7 @@ jobs:
tag: ${{ github.ref }}
- if: matrix.os == 'linux' && matrix.arch == 'amd64'
run: mkdir ./out && ./${{ steps.values.outputs.binary-name }} build --index --target-dir ./out
run: mkdir ./out && ./${{ steps.values.outputs.binary-name }} build --index --disable-minification --target-dir ./out
- if: matrix.os == 'linux' && matrix.arch == 'amd64'
uses: actions/upload-artifact@v4
with:
@ -93,13 +93,12 @@ jobs:
push: true
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8
build-args: "APP_VERSION=${{ steps.slug.outputs.version }}"
tags: ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version }}
# tags: | # TODO: uncomment after the stable release
# tarampampam/error-pages:latest
# tarampampam/error-pages:${{ steps.slug.outputs.version }}
# tarampampam/error-pages:${{ steps.slug.outputs.version-major }}.${{ steps.slug.outputs.version-minor }}
# tarampampam/error-pages:${{ steps.slug.outputs.version-major }}
# ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:latest
# ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version }}
# ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version-major }}.${{ steps.slug.outputs.version-minor }}
# ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version-major }}
tags: |
tarampampam/error-pages:latest
tarampampam/error-pages:${{ steps.slug.outputs.version }}
tarampampam/error-pages:${{ steps.slug.outputs.version-major }}.${{ steps.slug.outputs.version-minor }}
tarampampam/error-pages:${{ steps.slug.outputs.version-major }}
ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:latest
ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version }}
ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version-major }}.${{ steps.slug.outputs.version-minor }}
ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version-major }}

View File

@ -3,7 +3,7 @@
# -✂- this stage is used to develop and build the application locally -------------------------------------------------
FROM docker.io/library/golang:1.22-bookworm AS develop
# use the /var/tmp as the GOPATH to reuse the modules cache
# use the /var/tmp/go as the GOPATH to reuse the modules cache
ENV GOPATH="/var/tmp/go"
RUN set -x \
@ -32,7 +32,10 @@ FROM develop AS compile
# can be passed with any prefix (like `v1.2.3@GITHASH`), e.g.: `docker build --build-arg "APP_VERSION=v1.2.3" .`
ARG APP_VERSION="undefined@docker"
RUN --mount=type=bind,source=.,target=/src set -x \
# copy the source code
COPY . /src
RUN set -x \
&& go generate ./... \
&& CGO_ENABLED=0 LDFLAGS="-s -w -X gh.tarampamp.am/error-pages/internal/appmeta.version=${APP_VERSION}" \
go build -trimpath -ldflags "${LDFLAGS}" -o /tmp/error-pages ./cmd/error-pages/ \
@ -45,10 +48,11 @@ FROM docker.io/library/alpine:3.20 AS rootfs
WORKDIR /tmp/rootfs
# prepare rootfs for runtime
RUN --mount=type=bind,source=.,target=/src set -x \
&& mkdir -p ./etc ./bin \
RUN set -x \
&& mkdir -p ./etc/ssl/certs ./bin \
&& echo 'appuser:x:10001:10001::/nonexistent:/sbin/nologin' > ./etc/passwd \
&& echo 'appuser:x:10001:' > ./etc/group
&& echo 'appuser:x:10001:' > ./etc/group \
&& cp /etc/ssl/certs/ca-certificates.crt ./etc/ssl/certs/
# take the binary from the compile stage
COPY --from=compile /tmp/error-pages ./bin/error-pages
@ -69,7 +73,7 @@ ARG APP_VERSION="undefined@docker"
LABEL \
# docs: https://github.com/opencontainers/image-spec/blob/master/annotations.md
org.opencontainers.image.title="error-pages" \
org.opencontainers.image.description="Static server error pages in the docker image" \
org.opencontainers.image.description="Pretty server's error pages" \
org.opencontainers.image.url="https://github.com/tarampampam/error-pages" \
org.opencontainers.image.source="https://github.com/tarampampam/error-pages" \
org.opencontainers.image.vendor="tarampampam" \
@ -87,13 +91,12 @@ WORKDIR /opt
# to find out which environment variables and CLI arguments are supported by the application, run the app
# with the `--help` flag or refer to the documentation at https://github.com/tarampampam/error-pages#readme
ENV LOG_LEVEL="warn"
ENV LOG_LEVEL="warn" \
LOG_FORMAT="json"
# docs: https://docs.docker.com/reference/dockerfile/#healthcheck
HEALTHCHECK --interval=10s --start-interval=1s --start-period=5s --timeout=2s CMD [\
"/bin/error-pages", "--log-format", "json", "healthcheck" \
]
HEALTHCHECK --interval=10s --start-interval=1s --start-period=5s --timeout=2s CMD ["/bin/error-pages", "healthcheck"]
ENTRYPOINT ["/bin/error-pages"]
CMD ["--log-format", "json", "serve"]
CMD ["serve"]

455
README.md
View File

@ -1,5 +1,10 @@
<p align="center">
<a href="https://github.com/tarampampam/error-pages#readme"><img src="https://socialify.git.ci/tarampampam/error-pages/image?description=1&font=Raleway&forks=1&issues=1&logo=https%3A%2F%2Fhsto.org%2Fwebt%2Frm%2F9y%2Fww%2Frm9ywwx3gjv9agwkcmllhsuyo7k.png&owner=1&pulls=1&pattern=Solid&stargazers=1&theme=Dark" alt="banner" width="100%" /></a>
<a href="https://github.com/tarampampam/error-pages#readme">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://socialify.git.ci/tarampampam/error-pages/image?description=1&font=Raleway&forks=1&issues=1&logo=https%3A%2F%2Fhsto.org%2Fwebt%2Frm%2F9y%2Fww%2Frm9ywwx3gjv9agwkcmllhsuyo7k.png&owner=1&pulls=1&pattern=Solid&stargazers=1&theme=Dark">
<img align="center" src="https://socialify.git.ci/tarampampam/error-pages/image?description=1&font=Raleway&forks=1&issues=1&logo=https%3A%2F%2Fhsto.org%2Fwebt%2Frm%2F9y%2Fww%2Frm9ywwx3gjv9agwkcmllhsuyo7k.png&owner=1&pulls=1&pattern=Solid&stargazers=1&theme=Light">
</picture>
</a>
</p>
<p align="center">
@ -15,21 +20,25 @@ One day, you might want to replace the standard error pages of your HTTP server
original and attractive. That's why this repository was created :) It contains:
- A simple error page generator written in Go
- Single-page error templates (themes) with various designs (located in the [templates](templates) directory) that
- Single-page error templates (themes) with various designs (located in the [templates][templates-dir] directory) that
you can customize as you wish
- A fast and lightweight HTTP server is available as a single binary file and Docker image. It includes built-in error
page templates from this repository. You don't need anything except the compiled binary file or Docker image
- Pre-generated error pages (sources can be [found here][preview-sources], and the **demo** is always
accessible [here][preview-demo])
- Pre-generated error pages (sources can be [found here][preview-sources], and the [**demo** is always
accessible here][preview-demo])
[preview-sources]:https://github.com/tarampampam/error-pages/tree/gh-pages
[preview-demo]:https://tarampampam.github.io/error-pages/
[templates-dir]:https://github.com/tarampampam/error-pages/tree/master/templates
## 🔥 Features List
- HTTP server written in Go, utilizing the extremely fast [FastHTTP][fasthttp] and in-memory caching
- Respects the `Content-Type` HTTP header (and `X-Format`) value, responding with the corresponding format
(supported formats: `json`, `xml`, and `plaintext`)
- Error pages are configured to be excluded from search engine indexing (using meta tags and HTTP headers) to
prevent SEO issues on your website
- HTML content (including CSS, SVG, and JS) is minified on the fly
- Logs written in `json` format
- Contains a health check endpoint (`/healthz`)
- Consumes very few resources and is suitable for use in resource-constrained environments
@ -40,10 +49,11 @@ original and attractive. That's why this repository was created :) It contains:
- Fully configurable
- Distributed as a Docker image and compiled binary files
- Localized HTML error pages (🇺🇸, 🇫🇷, 🇺🇦, 🇷🇺, 🇵🇹, 🇳🇱, 🇩🇪, 🇪🇸, 🇨🇳, 🇮🇩, 🇵🇱) - translation process
[described here](l10n) - other translations are welcome!
[described here][l10n-dir] - other translations are welcome!
[fasthttp]:https://github.com/valyala/fasthttp
[traefik]:https://github.com/traefik/traefik
[l10n-dir]:https://github.com/tarampampam/error-pages/tree/master/l10n
## 🧩 Install
@ -67,6 +77,189 @@ Download the latest binary file for your OS/architecture from the [releases page
[pages-pack-zip]:https://github.com/tarampampam/error-pages/zipball/gh-pages/
[pages-pack-tar-gz]:https://github.com/tarampampam/error-pages/tarball/gh-pages/
## 🪂 Templates (themes)
The following templates are built-in and available for use without any additional setup:
> [!NOTE]
> The `cats` template is the only one of those that fetches resources (the actual cat pictures) from external
> servers - all other templates are self-contained.
<table>
<thead>
<tr>
<th>Template</th>
<th>Preview</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">
<code>app-down</code><br/><br/>
<picture>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Fapp-down.json&query=%24.count&label=used%20times" alt="used times">
</picture>
</td>
<td>
<a href="https://tarampampam.github.io/error-pages/app-down/404.html">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/tarampampam/error-pages/assets/7326800/4e668a56-a4c4-47cd-ac4d-b6b45db54ab8">
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/ad4b4fd7-7c7b-4bdc-a6b6-44f9ba7f77ca">
</picture>
</a>
</td>
</tr>
<tr>
<td align="center">
<code>cats</code><br/><br/>
<picture>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Fcats.json&query=%24.count&label=used%20times" alt="used times">
</picture>
</td>
<td>
<a href="https://tarampampam.github.io/error-pages/cats/404.html">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/tarampampam/error-pages/assets/7326800/5689880b-f770-406c-81dd-2d28629e6f2e">
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/056cd00e-bc9a-4120-8325-310d7b0ebd1b">
</picture>
</a>
</td>
</tr>
<tr>
<td align="center">
<code>connection</code><br/><br/>
<picture>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Fconnection.json&query=%24.count&label=used%20times" alt="used times">
</picture>
</td>
<td>
<a href="https://tarampampam.github.io/error-pages/connection/404.html">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/tarampampam/error-pages/assets/7326800/3f03dc1b-c1ee-4a91-b3d7-e3b93c79020e">
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/099ecc2d-e724-4d9c-b5ed-66ddabd71139">
</picture>
</a>
</td>
</tr>
<tr>
<td align="center">
<code>ghost</code><br/><br/>
<picture>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Fghost.json&query=%24.count&label=used%20times" alt="used times">
</picture>
</td>
<td>
<a href="https://tarampampam.github.io/error-pages/ghost/404.html">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/tarampampam/error-pages/assets/7326800/714482ab-f8c1-4455-8ae8-b2ae78f7a2c6">
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/f253dfe7-96a0-4e96-915b-d4c544d4a237">
</picture>
</a>
</td>
</tr>
<tr>
<td align="center">
<code>hacker-terminal</code><br/><br/>
<picture>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Fhacker-terminal.json&query=%24.count&label=used%20times" alt="used times">
</picture>
</td>
<td>
<a href="https://tarampampam.github.io/error-pages/hacker-terminal/404.html">
<picture>
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/c197fc35-0844-43d0-9830-82440cee4559">
</picture>
</a>
</td>
</tr>
<tr>
<td align="center">
<code>l7</code><br/><br/>
<picture>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Fl7.json&query=%24.count&label=used%20times" alt="used times">
</picture>
</td>
<td>
<a href="https://tarampampam.github.io/error-pages/l7/404.html">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/tarampampam/error-pages/assets/7326800/18e43ea3-6389-4459-be41-0fc6566a073f">
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/05f26669-94ec-40ce-8d67-a199cde54202">
</picture>
</a>
</td>
</tr>
<tr>
<td align="center">
<code>lost-in-space</code><br/><br/>
<picture>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Flost-in-space.json&query=%24.count&label=used%20times" alt="used times">
</picture>
</td>
<td>
<a href="https://tarampampam.github.io/error-pages/lost-in-space/404.html">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/tarampampam/error-pages/assets/7326800/debf87c0-6f27-41a8-b141-ee3464cbd6cc">
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/c347e63d-13a7-46d4-81b9-b25266819a1d">
</picture>
</a>
</td>
</tr>
<tr>
<td align="center">
<code>noise</code><br/><br/>
<picture>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Fnoise.json&query=%24.count&label=used%20times" alt="used times">
</picture>
</td>
<td>
<a href="https://tarampampam.github.io/error-pages/noise/404.html">
<picture>
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/4cc5c3bd-6ebb-4e96-bee8-02d4ad4e7266">
</picture>
</a>
</td>
</tr>
<tr>
<td align="center">
<code>orient</code><br/><br/>
<picture>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Forient.json&query=%24.count&label=used%20times" alt="used times">
</picture>
</td>
<td>
<a href="https://tarampampam.github.io/error-pages/orient/404.html">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/tarampampam/error-pages/assets/7326800/bc2b0dad-c32c-4628-98f6-e3eab61dd1f2">
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/8fc0a7ea-694d-49ce-bb50-3ea032d52d1e">
</picture>
</a>
</td>
</tr>
<tr>
<td align="center">
<code>shuffle</code><br/><br/>
<picture>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Fshuffle.json&query=%24.count&label=used%20times" alt="used times">
</picture>
</td>
<td>
<a href="https://tarampampam.github.io/error-pages/shuffle/404.html">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/tarampampam/error-pages/assets/7326800/7504b7c3-b0cb-4991-9ac2-759cd6c50fc0">
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/d2a73fc8-cf5f-4f42-bff8-cce33d8ae47e">
</picture>
</a>
</td>
</tr>
</tbody>
</table>
> [!NOTE]
> The "used times" counter increments when someone start the server with the specified template. Stats service does
> not collect any information about location, IP addresses, and so on. Moreover, the stats are open and available for
> everyone at [error-pages.goatcounter.com](https://error-pages.goatcounter.com/). This is simply a counter to display
> how often a particular template is used, nothing more.
## 🛠 Usage scenarios
### HTTP server starting, utilizing either a binary file or Docker image
@ -75,9 +268,9 @@ First, ensure you have a precompiled binary file on your machine or have Docker/
server with the following command:
```bash
./error-pages serve
# or
docker run --rm -p '8080:8080/tcp' tarampampam/error-pages serve
$ ./error-pages serve
# --- or ---
$ docker run --rm -p '8080:8080/tcp' tarampampam/error-pages serve
```
That's it! The server will begin running and listen on address `0.0.0.0` and port `8080`. Access error pages using
@ -86,7 +279,7 @@ URLs like `http://127.0.0.1:8080/{page_code}.html`.
To retrieve different error page codes using a static URL, use the `X-Code` HTTP header:
```bash
curl -H 'X-Code: 500' http://127.0.0.1:8080/
$ curl -H 'X-Code: 500' http://127.0.0.1:8080/
```
The server respects the `Content-Type` HTTP header (and `X-Format`), delivering responses in requested formats
@ -108,6 +301,55 @@ are detailed in the readme file below.
To proxy HTTP headers from requests to responses, utilize the `--proxy-headers` flag or environment variable
(comma-separated list of headers).
<details>
<summary><strong>🚀 Start the HTTP server with my custom template (theme)</strong></summary>
First, create your own template file, for example `my-super-theme.html`:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{ code }}</title>
</head>
<body>
<h1>YEAH! {{ message }}: {{ description }}</h1>
</body>
</html>
```
And simply start the server with the following command:
```bash
$ docker run --rm \
-v "$(pwd)/my-super-theme.html:/opt/my-template.html:ro" \
-p '8080:8080/tcp' ghcr.io/tarampampam/error-pages:3 serve \
--add-template /opt/my-template.html \
--template-name my-template
# --- or ---
$ ./error-pages serve \
--add-template /opt/my-template.html \
--template-name my-template
```
And test it:
```bash
$ curl -H "Accept: text/html" http://127.0.0.1:8080/503
<!DOCTYPE html>
<html lang="en">
<head>
<title>503</title>
</head>
<body>
<h1>YEAH! Service Unavailable: The server is temporarily overloading or down</h1>
</body>
</html>
```
</details>
<details>
<summary><strong>🚀 Generate a set of error pages using built-in or my own template</strong></summary>
@ -129,8 +371,8 @@ Create a file like this:
Save it as `my-template.html` and use it as your custom template. Then, generate your error pages using the command:
```bash
mkdir -p /path/to/output
./error-pages build --add-template /path/to/your/my-template.html --target-dir /path/to/output
$ mkdir -p /path/to/output
$ ./error-pages build --add-template /path/to/your/my-template.html --target-dir /path/to/output
```
This will create error pages based on your template in the specified output directory:
@ -228,14 +470,15 @@ COPY --chown=nginx \
Now, we can build the image:
```bash
docker build --tag your-nginx:local -f ./Dockerfile .
$ docker build --tag your-nginx:local -f ./Dockerfile .
```
And voilà! Let's start the image and test if everything is working as expected:
```bash
docker run --rm -p '8081:80/tcp' your-nginx:local
curl http://127.0.0.1:8081/foobar | head -n 15 # in another terminal
$ docker run --rm -p '8081:80/tcp' your-nginx:local
$ curl http://127.0.0.1:8081/foobar | head -n 15 # in another terminal
```
</details>
@ -325,6 +568,7 @@ controller:
config:
custom-http-errors: >-
401,403,404,500,501,502,503
defaultBackend:
enabled: true
image:
@ -367,11 +611,11 @@ Test completed successfully. Here is the output:
Running 15s test @ http://127.0.0.1:8080/
12 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 3.54ms 4.90ms 74.57ms 86.55%
Req/Sec 16.47k 2.89k 38.11k 69.46%
2967567 requests in 15.09s, 44.70GB read
Requests/sec: 196596.49
Transfer/sec: 2.96GB
Latency 4.52ms 6.43ms 94.34ms 85.44%
Req/Sec 15.76k 2.83k 29.64k 69.20%
2839632 requests in 15.09s, 32.90GB read
Requests/sec: 188185.61
Transfer/sec: 2.18GB
Starting the test to bomb DIFFERENT PAGES (codes). Please, be patient...
Test completed successfully. Here is the output:
@ -379,11 +623,11 @@ Test completed successfully. Here is the output:
Running 15s test @ http://127.0.0.1:8080/
12 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 4.25ms 6.03ms 74.23ms 86.97%
Req/Sec 14.29k 2.75k 32.16k 69.63%
2563245 requests in 15.07s, 38.47GB read
Requests/sec: 170062.69
Transfer/sec: 2.55GB
Latency 6.75ms 13.71ms 252.66ms 91.94%
Req/Sec 14.06k 3.25k 26.39k 71.98%
2534473 requests in 15.10s, 29.22GB read
Requests/sec: 167899.78
Transfer/sec: 1.94GB
```
</details>
@ -421,7 +665,7 @@ The following flags are supported:
|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------:|:---------------------------:|
| `--listen="…"` (`-l`) | The HTTP server will listen on this IP (v4 or v6) address (set 127.0.0.1/::1 for localhost, 0.0.0.0 to listen on all interfaces, or specify a custom IP) | `0.0.0.0` | `LISTEN_ADDR` |
| `--port="…"` (`-p`) | The TCP port number for the HTTP server to listen on (0-65535) | `8080` | `LISTEN_PORT` |
| `--add-template="…"` | To add a new template, provide the path to the file using this flag (the filename without the extension will be used as the template name) | `[]` | *none* |
| `--add-template="…"` | To add a new template, provide the path to the file using this flag (the filename without the extension will be used as the template name) | `[]` | `ADD_TEMPLATE` |
| `--disable-template="…"` | Disable the specified template by its name (useful to disable the built-in templates and use only custom ones) | `[]` | *none* |
| `--add-code="…"` | To add a new HTTP status code, provide the code and its message/description using this flag (the format should be '%code%=%message%/%description%'; the code may contain a wildcard '*' to cover multiple codes at once, for example, '4**' will cover all 4xx codes unless a more specific code is described previously) | `map[]` | *none* |
| `--json-format="…"` | Override the default error page response in JSON format (Go templates are supported; the error page will use this template if the client requests JSON content type) | | `RESPONSE_JSON_FORMAT` |
@ -435,6 +679,7 @@ The following flags are supported:
| `--proxy-headers="…"` | HTTP headers listed here will be proxied from the original request to the error page response (comma-separated list) | `X-Request-Id,X-Trace-Id,X-Amzn-Trace-Id` | `PROXY_HTTP_HEADERS` |
| `--rotation-mode="…"` | Templates automatic rotation mode (disabled/random-on-startup/random-on-each-request/random-hourly/random-daily) | `disabled` | `TEMPLATES_ROTATION_MODE` |
| `--read-buffer-size="…"` | Per-connection buffer size in bytes for reading requests, this also limits the maximum header size (increase this buffer if your clients send multi-KB Request URIs and/or multi-KB headers (e.g., large cookies), note that increasing this value will increase memory consumption) | `5120` | `READ_BUFFER_SIZE` |
| `--disable-minification` | Disable the minification of HTML pages, including CSS, SVG, and JS (may be useful for debugging) | `false` | `DISABLE_MINIFICATION` |
### `build` command (aliases: `b`)
@ -448,14 +693,15 @@ $ error-pages [GLOBAL FLAGS] build [COMMAND FLAGS] [ARGUMENTS...]
The following flags are supported:
| Name | Description | Default value | Environment variables |
|---------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------:|:---------------------:|
| `--add-template="…"` | To add a new template, provide the path to the file using this flag (the filename without the extension will be used as the template name) | `[]` | *none* |
| `--disable-template="…"` | Disable the specified template by its name (useful to disable the built-in templates and use only custom ones) | `[]` | *none* |
| `--add-code="…"` | To add a new HTTP status code, provide the code and its message/description using this flag (the format should be '%code%=%message%/%description%'; the code may contain a wildcard '*' to cover multiple codes at once, for example, '4**' will cover all 4xx codes unless a more specific code is described previously) | `map[]` | *none* |
| `--disable-l10n` | Disable localization of error pages (if the template supports localization) | `false` | `DISABLE_L10N` |
| `--index` (`-i`) | Generate index.html file with links to all error pages | `false` | *none* |
| `--target-dir="…"` (`--out`, `--dir`, `-o`) | Directory to put the built error pages into | `.` | *none* |
| Name | Description | Default value | Environment variables |
|---------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------:|:----------------------:|
| `--add-template="…"` | To add a new template, provide the path to the file using this flag (the filename without the extension will be used as the template name) | `[]` | `ADD_TEMPLATE` |
| `--disable-template="…"` | Disable the specified template by its name (useful to disable the built-in templates and use only custom ones) | `[]` | *none* |
| `--add-code="…"` | To add a new HTTP status code, provide the code and its message/description using this flag (the format should be '%code%=%message%/%description%'; the code may contain a wildcard '*' to cover multiple codes at once, for example, '4**' will cover all 4xx codes unless a more specific code is described previously) | `map[]` | *none* |
| `--disable-l10n` | Disable localization of error pages (if the template supports localization) | `false` | `DISABLE_L10N` |
| `--index` (`-i`) | Generate index.html file with links to all error pages | `false` | *none* |
| `--target-dir="…"` (`--out`, `--dir`, `-o`) | Directory to put the built error pages into | `.` | *none* |
| `--disable-minification` | Disable the minification of HTML pages, including CSS, SVG, and JS (may be useful for debugging) | `false` | `DISABLE_MINIFICATION` |
### `healthcheck` command (aliases: `chk`, `health`, `check`)
@ -475,149 +721,6 @@ The following flags are supported:
<!--/GENERATED:CLI_DOCS-->
## 🪂 Templates (themes)
The following templates are built-in and available for use without any additional setup:
> [!NOTE]
> The `cats` template is the only one of those that fetches resources (the actual cat pictures) from external
> servers - all other templates are self-contained.
<table>
<thead>
<tr>
<th>Template</th>
<th>Preview</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">
<code>app-down</code><br/><br/>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Fapp-down.json&query=%24.count&label=used%20times" alt="used times">
</td>
<td>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/tarampampam/error-pages/assets/7326800/4e668a56-a4c4-47cd-ac4d-b6b45db54ab8">
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/ad4b4fd7-7c7b-4bdc-a6b6-44f9ba7f77ca">
</picture>
</td>
</tr>
<tr>
<td align="center">
<code>cats</code><br/><br/>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Fcats.json&query=%24.count&label=used%20times" alt="used times">
</td>
<td>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/tarampampam/error-pages/assets/7326800/5689880b-f770-406c-81dd-2d28629e6f2e">
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/056cd00e-bc9a-4120-8325-310d7b0ebd1b">
</picture>
</td>
</tr>
<tr>
<td align="center">
<code>connection</code><br/><br/>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Fconnection.json&query=%24.count&label=used%20times" alt="used times">
</td>
<td>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/tarampampam/error-pages/assets/7326800/3f03dc1b-c1ee-4a91-b3d7-e3b93c79020e">
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/099ecc2d-e724-4d9c-b5ed-66ddabd71139">
</picture>
</td>
</tr>
<tr>
<td align="center">
<code>ghost</code><br/><br/>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Fghost.json&query=%24.count&label=used%20times" alt="used times">
</td>
<td>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/tarampampam/error-pages/assets/7326800/714482ab-f8c1-4455-8ae8-b2ae78f7a2c6">
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/f253dfe7-96a0-4e96-915b-d4c544d4a237">
</picture>
</td>
</tr>
<tr>
<td align="center">
<code>hacker-terminal</code><br/><br/>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Fhacker-terminal.json&query=%24.count&label=used%20times" alt="used times">
</td>
<td>
<picture>
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/c197fc35-0844-43d0-9830-82440cee4559">
</picture>
</td>
</tr>
<tr>
<td align="center">
<code>l7</code><br/><br/>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Fl7.json&query=%24.count&label=used%20times" alt="used times">
</td>
<td>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/tarampampam/error-pages/assets/7326800/18e43ea3-6389-4459-be41-0fc6566a073f">
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/05f26669-94ec-40ce-8d67-a199cde54202">
</picture>
</td>
</tr>
<tr>
<td align="center">
<code>lost-in-space</code><br/><br/>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Flost-in-space.json&query=%24.count&label=used%20times" alt="used times">
</td>
<td>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/tarampampam/error-pages/assets/7326800/debf87c0-6f27-41a8-b141-ee3464cbd6cc">
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/c347e63d-13a7-46d4-81b9-b25266819a1d">
</picture>
</td>
</tr>
<tr>
<td align="center">
<code>noise</code><br/><br/>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Fnoise.json&query=%24.count&label=used%20times" alt="used times">
</td>
<td>
<picture>
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/4cc5c3bd-6ebb-4e96-bee8-02d4ad4e7266">
</picture>
</td>
</tr>
<tr>
<td align="center">
<code>orient</code><br/><br/>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Forient.json&query=%24.count&label=used%20times" alt="used times">
</td>
<td>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/tarampampam/error-pages/assets/7326800/bc2b0dad-c32c-4628-98f6-e3eab61dd1f2">
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/8fc0a7ea-694d-49ce-bb50-3ea032d52d1e">
</picture>
</td>
</tr>
<tr>
<td align="center">
<code>shuffle</code><br/><br/>
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Fshuffle.json&query=%24.count&label=used%20times" alt="used times">
</td>
<td>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/tarampampam/error-pages/assets/7326800/7504b7c3-b0cb-4991-9ac2-759cd6c50fc0">
<img align="center" src="https://github.com/tarampampam/error-pages/assets/7326800/d2a73fc8-cf5f-4f42-bff8-cce33d8ae47e">
</picture>
</td>
</tr>
</tbody>
</table>
> [!NOTE]
> The "used times" counter increments when someone start the server with the specified template. Stats service does
> not collect any information about location, IP addresses, and so on. Moreover, the stats are open and available for
> everyone at [error-pages.goatcounter.com](https://error-pages.goatcounter.com/). This is simply a counter to display
> how often a particular template is used, nothing more.
## 🦾 Contributors
I want to say a big thank you to everyone who contributed to this project:

2
go.mod
View File

@ -4,6 +4,7 @@ go 1.22
require (
github.com/stretchr/testify v1.9.0
github.com/tdewolff/minify/v2 v2.20.35
github.com/urfave/cli-docs/v3 v3.0.0-alpha5
github.com/urfave/cli/v3 v3.0.0-alpha9
github.com/valyala/fasthttp v1.55.0
@ -19,6 +20,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/tdewolff/parse/v2 v2.7.15 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect

7
go.sum
View File

@ -26,6 +26,13 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tdewolff/minify/v2 v2.20.35 h1:/Vq/oivpkFyi2PViD25XHZZbJz+eO4OmPSgePex1kBU=
github.com/tdewolff/minify/v2 v2.20.35/go.mod h1:L1VYef/jwKw6Wwyk5A+T0mBjjn3mMPgmjjA688RNsxU=
github.com/tdewolff/parse/v2 v2.7.15 h1:hysDXtdGZIRF5UZXwpfn3ZWRbm+ru4l53/ajBRGpCTw=
github.com/tdewolff/parse/v2 v2.7.15/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/urfave/cli-docs/v3 v3.0.0-alpha5 h1:H1oWnR2/GN0dNm2PVylws+GxSOD6YOwW/jI5l78YfPk=
github.com/urfave/cli-docs/v3 v3.0.0-alpha5/go.mod h1:AIqom6Q60U4tiqHp41i7+/AB2XHgi1WvQ7jOFlccmZ4=
github.com/urfave/cli/v3 v3.0.0-alpha9 h1:P0RMy5fQm1AslQS+XCmy9UknDXctOmG/q/FZkUFnJSo=

View File

@ -6,7 +6,6 @@ import (
"runtime"
"strings"
_ "github.com/urfave/cli-docs/v3" // required for `go generate` to work
"github.com/urfave/cli/v3"
"gh.tarampamp.am/error-pages/internal/appmeta"
@ -17,7 +16,7 @@ import (
"gh.tarampamp.am/error-pages/internal/logger"
)
//go:generate go run update_readme.go
//go:generate go run app_generate.go
// NewApp creates a new console application.
func NewApp(appName string) *cli.Command { //nolint:funlen

View File

@ -1,5 +1,4 @@
//go:build ignore
// +build ignore
//go:build generate
package main
@ -17,8 +16,10 @@ func main() {
if stat, err := os.Stat(readmePath); err == nil && stat.Mode().IsRegular() {
if err = cliDocs.ToTabularToFileBetweenTags(cli.NewApp(""), "error-pages", readmePath); err != nil {
panic(err)
} else {
println("✔ cli docs updated successfully")
}
} else if err != nil {
println("readme file not found, cli docs not updated:", err.Error())
println("readme file not found, cli docs not updated:", err.Error())
}
}

View File

@ -39,11 +39,12 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit
cmd command
cfg = config.New()
addTplFlag = shared.AddTemplatesFlag
disableTplFlag = shared.DisableTemplateNamesFlag
addCodeFlag = shared.AddHTTPCodesFlag
disableL10nFlag = shared.DisableL10nFlag
createIndexFlag = cli.BoolFlag{
addTplFlag = shared.AddTemplatesFlag
disableTplFlag = shared.DisableTemplateNamesFlag
addCodeFlag = shared.AddHTTPCodesFlag
disableL10nFlag = shared.DisableL10nFlag
disableMinificationFlag = shared.DisableMinificationFlag
createIndexFlag = cli.BoolFlag{
Name: "index",
Aliases: []string{"i"},
Usage: "Generate index.html file with links to all error pages",
@ -81,6 +82,7 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit
Usage: "Build the static error pages and put them into a specified directory",
Action: func(ctx context.Context, c *cli.Command) error {
cfg.L10n.Disable = c.Bool(disableL10nFlag.Name)
cfg.DisableMinification = c.Bool(disableMinificationFlag.Name)
cmd.opt.createIndex = c.Bool(createIndexFlag.Name)
cmd.opt.targetDirAbsPath, _ = filepath.Abs(c.String(targetDirFlag.Name)) // an error checked by [os.Stat] validator
@ -140,13 +142,14 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit
&disableL10nFlag,
&createIndexFlag,
&targetDirFlag,
&disableMinificationFlag,
},
}
return cmd.c
}
func (cmd *command) Run( //nolint:funlen
func (cmd *command) Run( //nolint:funlen,gocognit
ctx context.Context,
log *logger.Logger,
cfg *config.Config,
@ -172,13 +175,21 @@ func (cmd *command) Run( //nolint:funlen
var outFilePath = path.Join(cmd.opt.targetDirAbsPath, templateName, code+".html")
if content, renderErr := appTemplate.Render(templateContent, appTemplate.Props{
if content, renderErr := appTemplate.Render(templateContent, appTemplate.Props{ //nolint:nestif
Code: uint16(codeAsUint),
Message: codeDescription.Message,
Description: codeDescription.Description,
L10nDisabled: cfg.L10n.Disable,
ShowRequestDetails: false,
}); renderErr == nil {
if !cfg.DisableMinification {
if mini, minErr := appTemplate.MiniHTML(content); minErr != nil {
log.Warn("Cannot minify the content", logger.Error(minErr))
} else {
content = mini
}
}
if err := os.WriteFile(outFilePath, []byte(content), os.FileMode(0664)); err != nil { //nolint:mnd
return err
}

View File

@ -38,13 +38,14 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy
)
var (
addrFlag = shared.ListenAddrFlag
portFlag = shared.ListenPortFlag
addTplFlag = shared.AddTemplatesFlag
disableTplFlag = shared.DisableTemplateNamesFlag
addCodeFlag = shared.AddHTTPCodesFlag
disableL10nFlag = shared.DisableL10nFlag
jsonFormatFlag = cli.StringFlag{
addrFlag = shared.ListenAddrFlag
portFlag = shared.ListenPortFlag
addTplFlag = shared.AddTemplatesFlag
disableTplFlag = shared.DisableTemplateNamesFlag
addCodeFlag = shared.AddHTTPCodesFlag
disableL10nFlag = shared.DisableL10nFlag
disableMinificationFlag = shared.DisableMinificationFlag
jsonFormatFlag = cli.StringFlag{
Name: "json-format",
Usage: "Override the default error page response in JSON format (Go templates are supported; the error " +
"page will use this template if the client requests JSON content type)",
@ -182,6 +183,7 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy
cfg.RespondWithSameHTTPCode = c.Bool(sendSameHTTPCodeFlag.Name)
cfg.RotationMode, _ = config.ParseRotationMode(c.String(rotationModeFlag.Name))
cfg.ShowDetails = c.Bool(showDetailsFlag.Name)
cfg.DisableMinification = c.Bool(disableMinificationFlag.Name)
{ // override default JSON, XML, and PlainText formats
if c.IsSet(jsonFormatFlag.Name) {
@ -303,6 +305,7 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy
&proxyHeadersListFlag,
&rotationModeFlag,
&readBufferSizeFlag,
&disableMinificationFlag,
},
}

View File

@ -67,6 +67,7 @@ var AddTemplatesFlag = cli.StringSliceFlag{
Usage: "To add a new template, provide the path to the file using this flag (the filename without the extension " +
"will be used as the template name)",
Config: cli.StringConfig{TrimSpace: true},
Sources: cli.EnvVars("ADD_TEMPLATE"),
Category: CategoryTemplates,
Validator: func(paths []string) error {
for _, path := range paths {
@ -146,3 +147,11 @@ var DisableL10nFlag = cli.BoolFlag{
Category: CategoryOther,
OnlyOnce: true,
}
var DisableMinificationFlag = cli.BoolFlag{
Name: "disable-minification",
Usage: "Disable the minification of HTML pages, including CSS, SVG, and JS (may be useful for debugging)",
Sources: cli.EnvVars("DISABLE_MINIFICATION"),
Category: CategoryOther,
OnlyOnce: true,
}

View File

@ -91,6 +91,7 @@ func TestAddTemplatesFlag(t *testing.T) {
var flag = shared.AddTemplatesFlag
assert.Equal(t, "add-template", flag.Name)
assert.Contains(t, flag.Sources.String(), "ADD_TEMPLATE")
for wantErrMsg, giveValue := range map[string][]string{
"missing template path": {""},
@ -216,3 +217,12 @@ func TestDisableL10nFlag(t *testing.T) {
assert.Equal(t, "disable-l10n", flag.Name)
assert.Contains(t, flag.Sources.String(), "DISABLE_L10N")
}
func TestDisableMinificationFlag(t *testing.T) {
t.Parallel()
var flag = shared.DisableMinificationFlag
assert.Equal(t, "disable-minification", flag.Name)
assert.Contains(t, flag.Sources.String(), "DISABLE_MINIFICATION")
}

View File

@ -56,6 +56,9 @@ type Config struct {
// ShowDetails determines whether to show additional details in the error response, extracted from the
// incoming request (if supported by the template).
ShowDetails bool
// DisableMinification determines whether to disable minification of the rendered content (e.g., HTML, CSS) or not.
DisableMinification bool
}
const defaultJSONFormat string = `{

View File

@ -24,6 +24,7 @@ func TestNew(t *testing.T) {
assert.NotEmpty(t, cfg.TemplateName)
assert.True(t, cfg.Templates.Has(cfg.TemplateName))
assert.Equal(t, uint16(http.StatusNotFound), cfg.DefaultCodeToRender)
assert.False(t, cfg.DisableMinification)
})
t.Run("changing cfg1 should not affect cfg2", func(t *testing.T) {

View File

@ -175,6 +175,14 @@ func New(cfg *config.Config, log *logger.Logger) (_ fasthttp.RequestHandler, clo
err.Error(),
))
} else {
if !cfg.DisableMinification {
if mini, minErr := template.MiniHTML(content); minErr != nil {
log.Warn("HTML minification failed", logger.Error(minErr))
} else {
content = mini
}
}
cache.Put(tpl, tplProps, []byte(content))
write(ctx, log, content)

View File

@ -48,7 +48,7 @@ func TestHandler(t *testing.T) {
wantStatusCode: http.StatusOK,
wantHeaders: map[string]string{"Content-Type": "text/html; charset=utf-8"},
wantBodyIncludes: []string{
"<!DOCTYPE html>",
"<!doctype html>",
"<title>407: Proxy Authentication Required",
"Proxy Authentication Required",
},

View File

@ -0,0 +1,23 @@
package template
import (
"github.com/tdewolff/minify/v2"
"github.com/tdewolff/minify/v2/css"
"github.com/tdewolff/minify/v2/html"
"github.com/tdewolff/minify/v2/js"
"github.com/tdewolff/minify/v2/svg"
)
var htmlMinify = func() *minify.M { //nolint:gochecknoglobals
var m = minify.New()
m.AddFunc("text/css", css.Minify)
m.Add("text/html", &html.Minifier{KeepDocumentTags: true, KeepEndTags: true, KeepQuotes: true})
m.AddFunc("image/svg+xml", svg.Minify)
m.AddFunc("application/javascript", js.Minify)
return m
}()
// MiniHTML minifies HTML data, including inline CSS, SVG and JS.
func MiniHTML(data string) (string, error) { return htmlMinify.String("text/html", data) }

View File

@ -0,0 +1,94 @@
package template_test
import (
"sync"
"testing"
"github.com/stretchr/testify/assert"
"gh.tarampamp.am/error-pages/internal/template"
)
func TestMiniHTML(t *testing.T) {
t.Parallel()
var wg sync.WaitGroup
for range 100 { // race condition provocation
wg.Add(1)
go func() {
defer wg.Done()
for give, want := range map[string]string{
"": "",
`<!-- Simple HTML page -->
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
<h1 align="center">Test</h1>
</body>
</html>`: `<!doctype html><html><head><title>Test</title></head><body><h1 align="center">Test</h1></body></html>`,
`<!-- css styles -->
<html>
<head>
<style>
.foo:hover {
color: #f0a; /* comment */
}
</style>
</head>
<body>
<p style="color: red" class="bar">Text</p>
</body>
</html>`: `<html><head><style>.foo:hover{color:#f0a}</style></head><body><p style="color:red" class="bar">Text</p></body></html>`,
`<!-- svg -->
<svg xmlns="http://www.w3.org/2000/svg">
<g>
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</g>
</svg>`: `<svg><g><circle cx="50" cy="50" r="40" stroke="#000" stroke-width="3" fill="red"/></g></svg>`,
`<!-- js -->
<html>
<body>
<script>
// comment
console.log('Hello, World!');
let foo = 1;
foo++;
</script>
</body>
</html>`: `<html><body><script>console.log("Hello, World!");let foo=1;foo++</script></body></html>`,
`<!-- js module not changed -->
<html>
<body>
<script type="module">
// comment
console.log('Hello, World!');
let foo = 1;
foo++;
</script>
</body>
</html>`: `<html><body><script type="module">
// comment
console.log('Hello, World!');
let foo = 1;
foo++;
</script></body></html>`,
} {
var got, err = template.MiniHTML(give)
assert.NoError(t, err)
assert.Equal(t, want, got)
}
}()
}
wg.Wait()
}