mirror of
https://github.com/tarampampam/error-pages.git
synced 2024-08-30 18:22:40 +00:00
feat: Add HTML/CSS/JS minification on the fly (#293)
This commit is contained in:
parent
b677064733
commit
141c18cf29
@ -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" .`
|
# 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"
|
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 ./... \
|
&& go generate ./... \
|
||||||
&& CGO_ENABLED=0 LDFLAGS="-s -w -X gh.tarampamp.am/error-pages/internal/appmeta.version=${APP_VERSION}" \
|
&& 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/ \
|
go build -trimpath -ldflags "${LDFLAGS}" -o /tmp/error-pages ./cmd/error-pages/ \
|
||||||
|
38
README.md
38
README.md
@ -610,11 +610,11 @@ Test completed successfully. Here is the output:
|
|||||||
Running 15s test @ http://127.0.0.1:8080/
|
Running 15s test @ http://127.0.0.1:8080/
|
||||||
12 threads and 400 connections
|
12 threads and 400 connections
|
||||||
Thread Stats Avg Stdev Max +/- Stdev
|
Thread Stats Avg Stdev Max +/- Stdev
|
||||||
Latency 3.54ms 4.90ms 74.57ms 86.55%
|
Latency 4.52ms 6.43ms 94.34ms 85.44%
|
||||||
Req/Sec 16.47k 2.89k 38.11k 69.46%
|
Req/Sec 15.76k 2.83k 29.64k 69.20%
|
||||||
2967567 requests in 15.09s, 44.70GB read
|
2839632 requests in 15.09s, 32.90GB read
|
||||||
Requests/sec: 196596.49
|
Requests/sec: 188185.61
|
||||||
Transfer/sec: 2.96GB
|
Transfer/sec: 2.18GB
|
||||||
|
|
||||||
Starting the test to bomb DIFFERENT PAGES (codes). Please, be patient...
|
Starting the test to bomb DIFFERENT PAGES (codes). Please, be patient...
|
||||||
Test completed successfully. Here is the output:
|
Test completed successfully. Here is the output:
|
||||||
@ -622,11 +622,11 @@ Test completed successfully. Here is the output:
|
|||||||
Running 15s test @ http://127.0.0.1:8080/
|
Running 15s test @ http://127.0.0.1:8080/
|
||||||
12 threads and 400 connections
|
12 threads and 400 connections
|
||||||
Thread Stats Avg Stdev Max +/- Stdev
|
Thread Stats Avg Stdev Max +/- Stdev
|
||||||
Latency 4.25ms 6.03ms 74.23ms 86.97%
|
Latency 6.75ms 13.71ms 252.66ms 91.94%
|
||||||
Req/Sec 14.29k 2.75k 32.16k 69.63%
|
Req/Sec 14.06k 3.25k 26.39k 71.98%
|
||||||
2563245 requests in 15.07s, 38.47GB read
|
2534473 requests in 15.10s, 29.22GB read
|
||||||
Requests/sec: 170062.69
|
Requests/sec: 167899.78
|
||||||
Transfer/sec: 2.55GB
|
Transfer/sec: 1.94GB
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
@ -678,6 +678,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` |
|
| `--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` |
|
| `--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` |
|
| `--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`)
|
### `build` command (aliases: `b`)
|
||||||
|
|
||||||
@ -691,14 +692,15 @@ $ error-pages [GLOBAL FLAGS] build [COMMAND FLAGS] [ARGUMENTS...]
|
|||||||
|
|
||||||
The following flags are supported:
|
The following flags are supported:
|
||||||
|
|
||||||
| Name | Description | Default value | Environment variables |
|
| 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* |
|
| `--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* |
|
| `--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* |
|
| `--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` |
|
| `--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* |
|
| `--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* |
|
| `--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`)
|
### `healthcheck` command (aliases: `chk`, `health`, `check`)
|
||||||
|
|
||||||
|
2
go.mod
2
go.mod
@ -4,6 +4,7 @@ go 1.22
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/stretchr/testify v1.9.0
|
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-docs/v3 v3.0.0-alpha5
|
||||||
github.com/urfave/cli/v3 v3.0.0-alpha9
|
github.com/urfave/cli/v3 v3.0.0-alpha9
|
||||||
github.com/valyala/fasthttp v1.55.0
|
github.com/valyala/fasthttp v1.55.0
|
||||||
@ -19,6 +20,7 @@ require (
|
|||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.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/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
|
7
go.sum
7
go.sum
@ -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/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 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
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 h1:H1oWnR2/GN0dNm2PVylws+GxSOD6YOwW/jI5l78YfPk=
|
||||||
github.com/urfave/cli-docs/v3 v3.0.0-alpha5/go.mod h1:AIqom6Q60U4tiqHp41i7+/AB2XHgi1WvQ7jOFlccmZ4=
|
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=
|
github.com/urfave/cli/v3 v3.0.0-alpha9 h1:P0RMy5fQm1AslQS+XCmy9UknDXctOmG/q/FZkUFnJSo=
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
_ "github.com/urfave/cli-docs/v3" // required for `go generate` to work
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
|
|
||||||
"gh.tarampamp.am/error-pages/internal/appmeta"
|
"gh.tarampamp.am/error-pages/internal/appmeta"
|
||||||
@ -17,7 +16,7 @@ import (
|
|||||||
"gh.tarampamp.am/error-pages/internal/logger"
|
"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.
|
// NewApp creates a new console application.
|
||||||
func NewApp(appName string) *cli.Command { //nolint:funlen
|
func NewApp(appName string) *cli.Command { //nolint:funlen
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
//go:build ignore
|
//go:build generate
|
||||||
// +build ignore
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
@ -17,8 +16,10 @@ func main() {
|
|||||||
if stat, err := os.Stat(readmePath); err == nil && stat.Mode().IsRegular() {
|
if stat, err := os.Stat(readmePath); err == nil && stat.Mode().IsRegular() {
|
||||||
if err = cliDocs.ToTabularToFileBetweenTags(cli.NewApp(""), "error-pages", readmePath); err != nil {
|
if err = cliDocs.ToTabularToFileBetweenTags(cli.NewApp(""), "error-pages", readmePath); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
} else {
|
||||||
|
println("✔ cli docs updated successfully")
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} 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())
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -39,11 +39,12 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit
|
|||||||
cmd command
|
cmd command
|
||||||
cfg = config.New()
|
cfg = config.New()
|
||||||
|
|
||||||
addTplFlag = shared.AddTemplatesFlag
|
addTplFlag = shared.AddTemplatesFlag
|
||||||
disableTplFlag = shared.DisableTemplateNamesFlag
|
disableTplFlag = shared.DisableTemplateNamesFlag
|
||||||
addCodeFlag = shared.AddHTTPCodesFlag
|
addCodeFlag = shared.AddHTTPCodesFlag
|
||||||
disableL10nFlag = shared.DisableL10nFlag
|
disableL10nFlag = shared.DisableL10nFlag
|
||||||
createIndexFlag = cli.BoolFlag{
|
disableMinificationFlag = shared.DisableMinificationFlag
|
||||||
|
createIndexFlag = cli.BoolFlag{
|
||||||
Name: "index",
|
Name: "index",
|
||||||
Aliases: []string{"i"},
|
Aliases: []string{"i"},
|
||||||
Usage: "Generate index.html file with links to all error pages",
|
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",
|
Usage: "Build the static error pages and put them into a specified directory",
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
cfg.L10n.Disable = c.Bool(disableL10nFlag.Name)
|
cfg.L10n.Disable = c.Bool(disableL10nFlag.Name)
|
||||||
|
cfg.DisableMinification = c.Bool(disableMinificationFlag.Name)
|
||||||
cmd.opt.createIndex = c.Bool(createIndexFlag.Name)
|
cmd.opt.createIndex = c.Bool(createIndexFlag.Name)
|
||||||
cmd.opt.targetDirAbsPath, _ = filepath.Abs(c.String(targetDirFlag.Name)) // an error checked by [os.Stat] validator
|
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,
|
&disableL10nFlag,
|
||||||
&createIndexFlag,
|
&createIndexFlag,
|
||||||
&targetDirFlag,
|
&targetDirFlag,
|
||||||
|
&disableMinificationFlag,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd.c
|
return cmd.c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cmd *command) Run( //nolint:funlen
|
func (cmd *command) Run( //nolint:funlen,gocognit
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
log *logger.Logger,
|
log *logger.Logger,
|
||||||
cfg *config.Config,
|
cfg *config.Config,
|
||||||
@ -172,13 +175,21 @@ func (cmd *command) Run( //nolint:funlen
|
|||||||
|
|
||||||
var outFilePath = path.Join(cmd.opt.targetDirAbsPath, templateName, code+".html")
|
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),
|
Code: uint16(codeAsUint),
|
||||||
Message: codeDescription.Message,
|
Message: codeDescription.Message,
|
||||||
Description: codeDescription.Description,
|
Description: codeDescription.Description,
|
||||||
L10nDisabled: cfg.L10n.Disable,
|
L10nDisabled: cfg.L10n.Disable,
|
||||||
ShowRequestDetails: false,
|
ShowRequestDetails: false,
|
||||||
}); renderErr == nil {
|
}); 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
|
if err := os.WriteFile(outFilePath, []byte(content), os.FileMode(0664)); err != nil { //nolint:mnd
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -38,13 +38,14 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
addrFlag = shared.ListenAddrFlag
|
addrFlag = shared.ListenAddrFlag
|
||||||
portFlag = shared.ListenPortFlag
|
portFlag = shared.ListenPortFlag
|
||||||
addTplFlag = shared.AddTemplatesFlag
|
addTplFlag = shared.AddTemplatesFlag
|
||||||
disableTplFlag = shared.DisableTemplateNamesFlag
|
disableTplFlag = shared.DisableTemplateNamesFlag
|
||||||
addCodeFlag = shared.AddHTTPCodesFlag
|
addCodeFlag = shared.AddHTTPCodesFlag
|
||||||
disableL10nFlag = shared.DisableL10nFlag
|
disableL10nFlag = shared.DisableL10nFlag
|
||||||
jsonFormatFlag = cli.StringFlag{
|
disableMinificationFlag = shared.DisableMinificationFlag
|
||||||
|
jsonFormatFlag = cli.StringFlag{
|
||||||
Name: "json-format",
|
Name: "json-format",
|
||||||
Usage: "Override the default error page response in JSON format (Go templates are supported; the error " +
|
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)",
|
"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.RespondWithSameHTTPCode = c.Bool(sendSameHTTPCodeFlag.Name)
|
||||||
cfg.RotationMode, _ = config.ParseRotationMode(c.String(rotationModeFlag.Name))
|
cfg.RotationMode, _ = config.ParseRotationMode(c.String(rotationModeFlag.Name))
|
||||||
cfg.ShowDetails = c.Bool(showDetailsFlag.Name)
|
cfg.ShowDetails = c.Bool(showDetailsFlag.Name)
|
||||||
|
cfg.DisableMinification = c.Bool(disableMinificationFlag.Name)
|
||||||
|
|
||||||
{ // override default JSON, XML, and PlainText formats
|
{ // override default JSON, XML, and PlainText formats
|
||||||
if c.IsSet(jsonFormatFlag.Name) {
|
if c.IsSet(jsonFormatFlag.Name) {
|
||||||
@ -303,6 +305,7 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy
|
|||||||
&proxyHeadersListFlag,
|
&proxyHeadersListFlag,
|
||||||
&rotationModeFlag,
|
&rotationModeFlag,
|
||||||
&readBufferSizeFlag,
|
&readBufferSizeFlag,
|
||||||
|
&disableMinificationFlag,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,3 +146,11 @@ var DisableL10nFlag = cli.BoolFlag{
|
|||||||
Category: CategoryOther,
|
Category: CategoryOther,
|
||||||
OnlyOnce: true,
|
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,
|
||||||
|
}
|
||||||
|
@ -216,3 +216,12 @@ func TestDisableL10nFlag(t *testing.T) {
|
|||||||
assert.Equal(t, "disable-l10n", flag.Name)
|
assert.Equal(t, "disable-l10n", flag.Name)
|
||||||
assert.Contains(t, flag.Sources.String(), "DISABLE_L10N")
|
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")
|
||||||
|
}
|
||||||
|
@ -56,6 +56,9 @@ type Config struct {
|
|||||||
// ShowDetails determines whether to show additional details in the error response, extracted from the
|
// ShowDetails determines whether to show additional details in the error response, extracted from the
|
||||||
// incoming request (if supported by the template).
|
// incoming request (if supported by the template).
|
||||||
ShowDetails bool
|
ShowDetails bool
|
||||||
|
|
||||||
|
// DisableMinification determines whether to disable minification of the rendered content (e.g., HTML, CSS) or not.
|
||||||
|
DisableMinification bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultJSONFormat string = `{
|
const defaultJSONFormat string = `{
|
||||||
|
@ -24,6 +24,7 @@ func TestNew(t *testing.T) {
|
|||||||
assert.NotEmpty(t, cfg.TemplateName)
|
assert.NotEmpty(t, cfg.TemplateName)
|
||||||
assert.True(t, cfg.Templates.Has(cfg.TemplateName))
|
assert.True(t, cfg.Templates.Has(cfg.TemplateName))
|
||||||
assert.Equal(t, uint16(http.StatusNotFound), cfg.DefaultCodeToRender)
|
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) {
|
t.Run("changing cfg1 should not affect cfg2", func(t *testing.T) {
|
||||||
|
@ -175,6 +175,14 @@ func New(cfg *config.Config, log *logger.Logger) (_ fasthttp.RequestHandler, clo
|
|||||||
err.Error(),
|
err.Error(),
|
||||||
))
|
))
|
||||||
} else {
|
} 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))
|
cache.Put(tpl, tplProps, []byte(content))
|
||||||
|
|
||||||
write(ctx, log, content)
|
write(ctx, log, content)
|
||||||
|
@ -48,7 +48,7 @@ func TestHandler(t *testing.T) {
|
|||||||
wantStatusCode: http.StatusOK,
|
wantStatusCode: http.StatusOK,
|
||||||
wantHeaders: map[string]string{"Content-Type": "text/html; charset=utf-8"},
|
wantHeaders: map[string]string{"Content-Type": "text/html; charset=utf-8"},
|
||||||
wantBodyIncludes: []string{
|
wantBodyIncludes: []string{
|
||||||
"<!DOCTYPE html>",
|
"<!doctype html>",
|
||||||
"<title>407: Proxy Authentication Required",
|
"<title>407: Proxy Authentication Required",
|
||||||
"Proxy Authentication Required",
|
"Proxy Authentication Required",
|
||||||
},
|
},
|
||||||
|
23
internal/template/minify.go
Normal file
23
internal/template/minify.go
Normal 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) }
|
94
internal/template/minify_test.go
Normal file
94
internal/template/minify_test.go
Normal 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()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user