mirror of
https://github.com/tarampampam/error-pages.git
synced 2024-08-30 18:22:40 +00:00
wip: 🔕 temporary commit
This commit is contained in:
parent
71f8cfc162
commit
b71475fcf7
20
README.md
20
README.md
@ -27,11 +27,21 @@ $ error-pages [GLOBAL FLAGS] serve [COMMAND FLAGS] [ARGUMENTS...]
|
||||
|
||||
The following flags are supported:
|
||||
|
||||
| Name | Description | Default value | Environment variables |
|
||||
|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------:|:---------------------:|
|
||||
| `--port="…"` (`-p`) | TCP port number | `8080` | `LISTEN_PORT` |
|
||||
| `--listen="…"` (`-l`) | IP (v4 or v6) address to listen on | `0.0.0.0` | `LISTEN_ADDR` |
|
||||
| `--add-template="…"` | to add a new template, provide the path to the file here (may be specified multiple times; the filename without the extension will be used as the template name) | `[]` | *none* |
|
||||
| Name | Description | Default value | Environment variables |
|
||||
|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------:|:----------------------:|
|
||||
| `--listen="…"` (`-l`) | IP (v4 or v6) address to listen on | `0.0.0.0` | `LISTEN_ADDR` |
|
||||
| `--port="…"` (`-p`) | TCP port number | `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-http-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 was described previously) | `map[]` | *none* |
|
||||
| `--json-format="…"` | override the default error page response in JSON format (Go templates are supported) | | `RESPONSE_JSON_FORMAT` |
|
||||
| `--xml-format="…"` | override the default error page response in XML format (Go templates are supported) | | `RESPONSE_XML_FORMAT` |
|
||||
| `--template-name="…"` (`-t`) | name of the template to use for rendering error pages | `template-1` | `TEMPLATE_NAME` |
|
||||
| `--disable-l10n` | disable localization of error pages (if the template supports localization) | `false` | `DISABLE_L10N` |
|
||||
| `--default-error-page="…"` | the code of the default (index page, when a code is not specified) error page to render | `404` | `DEFAULT_ERROR_PAGE` |
|
||||
| `--default-http-code="…"` | the default (index page, when a code is not specified) HTTP response code | `404` | `DEFAULT_HTTP_CODE` |
|
||||
| `--show-details` | show request details in the error page response (if supported by the template) | `false` | `SHOW_DETAILS` |
|
||||
| `--proxy-headers="…"` | listed here HTTP headers 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` |
|
||||
| `--read-buffer-size="…"` | customize the HTTP read buffer size (set per connection for reading requests, also limits the maximum header size; consider increasing it if your clients send multi-KB request URIs or multi-KB headers, such as large cookies) | `0` | `READ_BUFFER_SIZE` |
|
||||
|
||||
### `healthcheck` command (aliases: `chk`, `health`, `check`)
|
||||
|
||||
|
@ -3,6 +3,7 @@ package serve
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
@ -15,19 +16,27 @@ import (
|
||||
type command struct {
|
||||
c *cli.Command
|
||||
|
||||
opt struct{}
|
||||
opt struct {
|
||||
http struct { // our HTTP server
|
||||
addr string
|
||||
port uint16
|
||||
readBufferSize uint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewCommand creates `serve` command.
|
||||
func NewCommand(log *zap.Logger) *cli.Command { //nolint:funlen
|
||||
var cmd command
|
||||
func NewCommand(log *zap.Logger) *cli.Command { //nolint:funlen,gocognit,gocyclo
|
||||
var (
|
||||
cmd command
|
||||
cfg = config.New()
|
||||
)
|
||||
|
||||
var (
|
||||
portFlag = shared.ListenPortFlag
|
||||
addrFlag = shared.ListenAddrFlag
|
||||
addTplFlag = shared.AddTemplateFlag
|
||||
addCodeFlag = shared.AddHTTPCodeFlag
|
||||
|
||||
addrFlag = shared.ListenAddrFlag
|
||||
portFlag = shared.ListenPortFlag
|
||||
addTplFlag = shared.AddTemplateFlag
|
||||
addCodeFlag = shared.AddHTTPCodeFlag
|
||||
jsonFormatFlag = cli.StringFlag{
|
||||
Name: "json-format",
|
||||
Usage: "override the default error page response in JSON format (Go templates are supported)",
|
||||
@ -35,7 +44,6 @@ func NewCommand(log *zap.Logger) *cli.Command { //nolint:funlen
|
||||
OnlyOnce: true,
|
||||
Config: cli.StringConfig{TrimSpace: true},
|
||||
}
|
||||
|
||||
xmlFormatFlag = cli.StringFlag{
|
||||
Name: "xml-format",
|
||||
Usage: "override the default error page response in XML format (Go templates are supported)",
|
||||
@ -43,6 +51,78 @@ func NewCommand(log *zap.Logger) *cli.Command { //nolint:funlen
|
||||
OnlyOnce: true,
|
||||
Config: cli.StringConfig{TrimSpace: true},
|
||||
}
|
||||
templateNameFlag = cli.StringFlag{
|
||||
Name: "template-name",
|
||||
Aliases: []string{"t"},
|
||||
Value: cfg.TemplateName,
|
||||
Usage: "name of the template to use for rendering error pages",
|
||||
Sources: cli.EnvVars("TEMPLATE_NAME"),
|
||||
OnlyOnce: true,
|
||||
Config: cli.StringConfig{TrimSpace: true},
|
||||
}
|
||||
disableL10nFlag = cli.BoolFlag{
|
||||
Name: "disable-l10n",
|
||||
Usage: "disable localization of error pages (if the template supports localization)",
|
||||
Value: cfg.L10n.Disable,
|
||||
Sources: cli.EnvVars("DISABLE_L10N"),
|
||||
OnlyOnce: true,
|
||||
}
|
||||
defaultCodeToRenderFlag = cli.UintFlag{
|
||||
Name: "default-error-page",
|
||||
Usage: "the code of the default (index page, when a code is not specified) error page to render",
|
||||
Value: uint64(cfg.Default.CodeToRender),
|
||||
Sources: cli.EnvVars("DEFAULT_ERROR_PAGE"),
|
||||
Validator: func(code uint64) error {
|
||||
if code > 999 { //nolint:mnd
|
||||
return fmt.Errorf("wrong HTTP code [%d] for the default error page", code)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
OnlyOnce: true,
|
||||
}
|
||||
defaultHTTPCodeFlag = cli.UintFlag{
|
||||
Name: "default-http-code",
|
||||
Usage: "the default (index page, when a code is not specified) HTTP response code",
|
||||
Value: uint64(cfg.Default.HttpCode),
|
||||
Sources: cli.EnvVars("DEFAULT_HTTP_CODE"),
|
||||
Validator: defaultCodeToRenderFlag.Validator,
|
||||
OnlyOnce: true,
|
||||
}
|
||||
showDetailsFlag = cli.BoolFlag{
|
||||
Name: "show-details",
|
||||
Usage: "show request details in the error page response (if supported by the template)",
|
||||
Value: cfg.ShowDetails,
|
||||
Sources: cli.EnvVars("SHOW_DETAILS"),
|
||||
OnlyOnce: true,
|
||||
}
|
||||
proxyHeadersListFlag = cli.StringFlag{
|
||||
Name: "proxy-headers",
|
||||
Usage: "listed here HTTP headers will be proxied from the original request to the error page response " +
|
||||
"(comma-separated list)",
|
||||
Value: strings.Join(cfg.ProxyHeaders, ","),
|
||||
Sources: cli.EnvVars("PROXY_HTTP_HEADERS"),
|
||||
Validator: func(s string) error {
|
||||
for _, raw := range strings.Split(s, ",") {
|
||||
if clean := strings.TrimSpace(raw); strings.ContainsRune(clean, ' ') {
|
||||
return fmt.Errorf("whitespaces in the HTTP headers are not allowed: %s", clean)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
OnlyOnce: true,
|
||||
Config: cli.StringConfig{TrimSpace: true},
|
||||
}
|
||||
readBufferSizeFlag = cli.UintFlag{
|
||||
Name: "read-buffer-size",
|
||||
Usage: "customize the HTTP read buffer size (set per connection for reading requests, also limits the " +
|
||||
"maximum header size; consider increasing it if your clients send multi-KB request URIs or multi-KB " +
|
||||
"headers, such as large cookies)",
|
||||
DefaultText: "not set",
|
||||
Sources: cli.EnvVars("READ_BUFFER_SIZE"),
|
||||
OnlyOnce: true,
|
||||
}
|
||||
)
|
||||
|
||||
cmd.c = &cli.Command{
|
||||
@ -51,7 +131,29 @@ func NewCommand(log *zap.Logger) *cli.Command { //nolint:funlen
|
||||
Usage: "Start HTTP server",
|
||||
Suggest: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
var cfg = config.New()
|
||||
cmd.opt.http.addr = c.String(addrFlag.Name)
|
||||
cmd.opt.http.port = uint16(c.Uint(portFlag.Name))
|
||||
cmd.opt.http.readBufferSize = uint(c.Uint(readBufferSizeFlag.Name))
|
||||
|
||||
cfg.TemplateName = c.String(templateNameFlag.Name)
|
||||
cfg.L10n.Disable = c.Bool(disableL10nFlag.Name)
|
||||
cfg.Default.CodeToRender = uint16(c.Uint(defaultCodeToRenderFlag.Name))
|
||||
cfg.Default.HttpCode = uint16(c.Uint(defaultHTTPCodeFlag.Name))
|
||||
cfg.ShowDetails = c.Bool(showDetailsFlag.Name)
|
||||
|
||||
if c.IsSet(proxyHeadersListFlag.Name) {
|
||||
var m = make(map[string]struct{}) // map is used to avoid duplicates
|
||||
|
||||
for _, header := range strings.Split(c.String(proxyHeadersListFlag.Name), ",") {
|
||||
m[http.CanonicalHeaderKey(strings.TrimSpace(header))] = struct{}{}
|
||||
}
|
||||
|
||||
clear(cfg.ProxyHeaders) // clear the list before adding new headers
|
||||
|
||||
for header := range m {
|
||||
cfg.ProxyHeaders = append(cfg.ProxyHeaders, header)
|
||||
}
|
||||
}
|
||||
|
||||
if add := c.StringSlice(addTplFlag.Name); len(add) > 0 { // add templates from files to the config
|
||||
for _, templatePath := range add {
|
||||
@ -106,17 +208,30 @@ func NewCommand(log *zap.Logger) *cli.Command { //nolint:funlen
|
||||
zap.Strings("described HTTP codes", cfg.Codes.Codes()),
|
||||
zap.String("JSON format", cfg.Formats.JSON),
|
||||
zap.String("XML format", cfg.Formats.XML),
|
||||
zap.String("template name", cfg.TemplateName),
|
||||
zap.Bool("disable localization", cfg.L10n.Disable),
|
||||
zap.Uint16("default code to render", cfg.Default.CodeToRender),
|
||||
zap.Uint16("default HTTP code", cfg.Default.HttpCode),
|
||||
zap.Bool("show details", cfg.ShowDetails),
|
||||
zap.Strings("proxy HTTP headers", cfg.ProxyHeaders),
|
||||
)
|
||||
|
||||
return cmd.Run(ctx, log, &cfg)
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
&portFlag,
|
||||
&addrFlag,
|
||||
&portFlag,
|
||||
&addTplFlag,
|
||||
&addCodeFlag,
|
||||
&jsonFormatFlag,
|
||||
&xmlFormatFlag,
|
||||
&templateNameFlag,
|
||||
&disableL10nFlag,
|
||||
&defaultCodeToRenderFlag,
|
||||
&defaultHTTPCodeFlag,
|
||||
&showDetailsFlag,
|
||||
&proxyHeadersListFlag,
|
||||
&readBufferSizeFlag,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,8 @@ package config
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
builtinTemplates "gh.tarampamp.am/error-pages/templates"
|
||||
)
|
||||
@ -20,6 +22,34 @@ type Config struct {
|
||||
|
||||
// Codes hold descriptions for HTTP codes (e.g., 404: "Not Found / The server can not find the requested page").
|
||||
Codes Codes
|
||||
|
||||
// TemplateName is the name of the template to use for rendering error pages. The template must be present in the
|
||||
// Templates map.
|
||||
TemplateName string
|
||||
|
||||
// ProxyHeaders contains a list of HTTP headers that will be proxied from the incoming request to the
|
||||
// error page response.
|
||||
ProxyHeaders []string
|
||||
|
||||
// L10n contains localization settings.
|
||||
L10n struct {
|
||||
// Disable the localization of error pages.
|
||||
Disable bool
|
||||
}
|
||||
|
||||
// Default contains default settings.
|
||||
Default struct {
|
||||
// CodeToRender is the code for the default error page to be displayed. It is used when the requested
|
||||
// code is not defined in the incoming request (i.e., the code to render as the index page).
|
||||
CodeToRender uint16
|
||||
|
||||
// HTTPCode is the HTTP code to return when the requested code is not defined in the incoming request.
|
||||
HttpCode uint16
|
||||
}
|
||||
|
||||
// ShowDetails determines whether to show additional details in the error response, extracted from the
|
||||
// incoming request (if supported by the template).
|
||||
ShowDetails bool
|
||||
}
|
||||
|
||||
const defaultJSONFormat string = `{
|
||||
@ -82,6 +112,14 @@ var defaultCodes = Codes{ //nolint:gochecknoglobals
|
||||
"505": {"HTTP Version Not Supported", "The server does not support the \"http protocol\" version"},
|
||||
}
|
||||
|
||||
var defaultProxyHeaders = []string{ //nolint:gochecknoglobals
|
||||
// "Traceparent", // W3C Trace Context
|
||||
// "Tracestate", // W3C Trace Context
|
||||
"X-Request-Id", // unofficial HTTP header, used to trace individual HTTP requests
|
||||
"X-Trace-Id", // same as above
|
||||
"X-Amzn-Trace-Id", // to track HTTP requests from clients to targets or other AWS services
|
||||
}
|
||||
|
||||
// New creates a new configuration with default values.
|
||||
func New() Config {
|
||||
var cfg = Config{
|
||||
@ -97,5 +135,19 @@ func New() Config {
|
||||
cfg.Templates[name] = content
|
||||
}
|
||||
|
||||
// set first template as default
|
||||
for _, name := range cfg.Templates.Names() {
|
||||
cfg.TemplateName = name
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
// set default HTTP headers to proxy
|
||||
cfg.ProxyHeaders = slices.Clone(defaultProxyHeaders)
|
||||
|
||||
// set defaults
|
||||
cfg.Default.CodeToRender = 404
|
||||
cfg.Default.HttpCode = http.StatusNotFound
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
@ -18,6 +18,10 @@ func TestNew(t *testing.T) {
|
||||
assert.NotEmpty(t, cfg.Formats.JSON)
|
||||
assert.True(t, len(cfg.Codes) >= 19)
|
||||
assert.True(t, len(cfg.Templates) >= 2)
|
||||
assert.NotEmpty(t, cfg.TemplateName)
|
||||
assert.True(t, cfg.Templates.Has(cfg.TemplateName))
|
||||
assert.Equal(t, uint16(404), cfg.Default.CodeToRender)
|
||||
assert.Equal(t, uint16(404), cfg.Default.HttpCode)
|
||||
})
|
||||
|
||||
t.Run("changing cfg1 should not affect cfg2", func(t *testing.T) {
|
||||
@ -26,5 +30,9 @@ func TestNew(t *testing.T) {
|
||||
cfg1.Codes["400"] = config.CodeDescription{Message: "foo", Description: "bar"}
|
||||
|
||||
assert.NotEqual(t, cfg1.Codes["400"], cfg2.Codes["400"])
|
||||
|
||||
cfg1.ProxyHeaders = append(cfg1.ProxyHeaders, "foo")
|
||||
|
||||
assert.NotEqual(t, cfg1.ProxyHeaders, cfg2.ProxyHeaders)
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user