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
2a1fa5c108
commit
015e686635
@ -39,8 +39,9 @@ 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.AddTemplateFlag
|
addTplFlag = shared.AddTemplatesFlag
|
||||||
addCodeFlag = shared.AddHTTPCodeFlag
|
disableTplFlag = shared.DisableTemplateNamesFlag
|
||||||
|
addCodeFlag = shared.AddHTTPCodesFlag
|
||||||
jsonFormatFlag = cli.StringFlag{
|
jsonFormatFlag = cli.StringFlag{
|
||||||
Name: "json-format",
|
Name: "json-format",
|
||||||
Usage: "override the default error page response in JSON format (Go templates are supported)",
|
Usage: "override the default error page response in JSON format (Go templates are supported)",
|
||||||
@ -140,16 +141,6 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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{
|
cmd.c = &cli.Command{
|
||||||
@ -160,16 +151,28 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy
|
|||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
cmd.opt.http.addr = c.String(addrFlag.Name)
|
cmd.opt.http.addr = c.String(addrFlag.Name)
|
||||||
cmd.opt.http.port = uint16(c.Uint(portFlag.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.L10n.Disable = c.Bool(disableL10nFlag.Name)
|
||||||
cfg.DefaultCodeToRender = uint16(c.Uint(defaultCodeToRenderFlag.Name))
|
cfg.DefaultCodeToRender = uint16(c.Uint(defaultCodeToRenderFlag.Name))
|
||||||
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)
|
||||||
|
|
||||||
if add := c.StringSlice(addTplFlag.Name); len(add) > 0 { // add templates from files to the config
|
{ // override default JSON, XML, and PlainText formats
|
||||||
|
if c.IsSet(jsonFormatFlag.Name) {
|
||||||
|
cfg.Formats.JSON = strings.TrimSpace(c.String(jsonFormatFlag.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.IsSet(xmlFormatFlag.Name) {
|
||||||
|
cfg.Formats.XML = strings.TrimSpace(c.String(xmlFormatFlag.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.IsSet(plainTextFormatFlag.Name) {
|
||||||
|
cfg.Formats.PlainText = strings.TrimSpace(c.String(plainTextFormatFlag.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add templates from files to the configuration
|
||||||
|
if add := c.StringSlice(addTplFlag.Name); len(add) > 0 {
|
||||||
for _, templatePath := range add {
|
for _, templatePath := range add {
|
||||||
if addedName, err := cfg.Templates.AddFromFile(templatePath); err != nil {
|
if addedName, err := cfg.Templates.AddFromFile(templatePath); err != nil {
|
||||||
return fmt.Errorf("cannot add template from file %s: %w", templatePath, err)
|
return fmt.Errorf("cannot add template from file %s: %w", templatePath, err)
|
||||||
@ -182,10 +185,7 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cfg.Templates.Has(cfg.TemplateName) {
|
// set the list of HTTP headers we need to proxy from the incoming request to the error page response
|
||||||
return fmt.Errorf("template %s not found and cannot be used", cfg.TemplateName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.IsSet(proxyHeadersListFlag.Name) {
|
if c.IsSet(proxyHeadersListFlag.Name) {
|
||||||
var m = make(map[string]struct{}) // map is used to avoid duplicates
|
var m = make(map[string]struct{}) // map is used to avoid duplicates
|
||||||
|
|
||||||
@ -200,7 +200,8 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if add := c.StringMap(addCodeFlag.Name); len(add) > 0 { // add custom HTTP codes
|
// add custom HTTP codes to the configuration
|
||||||
|
if add := c.StringMap(addCodeFlag.Name); len(add) > 0 {
|
||||||
for code, msgAndDesc := range add {
|
for code, msgAndDesc := range add {
|
||||||
var (
|
var (
|
||||||
parts = strings.SplitN(msgAndDesc, "/", 2) //nolint:mnd
|
parts = strings.SplitN(msgAndDesc, "/", 2) //nolint:mnd
|
||||||
@ -225,17 +226,24 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // override default JSON and XML formats
|
// disable templates specified by the user
|
||||||
if c.IsSet(jsonFormatFlag.Name) {
|
if disable := c.StringSlice(disableTplFlag.Name); len(disable) > 0 {
|
||||||
cfg.Formats.JSON = strings.TrimSpace(c.String(jsonFormatFlag.Name))
|
for _, templateName := range disable {
|
||||||
|
if ok := cfg.Templates.Remove(templateName); ok {
|
||||||
|
log.Info("Template disabled", logger.String("name", templateName))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if c.IsSet(xmlFormatFlag.Name) {
|
// if the rotation mode is set to random-on-startup, pick a random template (ignore the user-provided
|
||||||
cfg.Formats.XML = strings.TrimSpace(c.String(xmlFormatFlag.Name))
|
// template name)
|
||||||
}
|
if cfg.RotationMode == config.RotationModeRandomOnStartup {
|
||||||
|
cfg.TemplateName = cfg.Templates.RandomName()
|
||||||
|
} else { // otherwise, use the user-provided template name
|
||||||
|
cfg.TemplateName = c.String(templateNameFlag.Name)
|
||||||
|
|
||||||
if c.IsSet(plainTextFormatFlag.Name) {
|
if !cfg.Templates.Has(cfg.TemplateName) {
|
||||||
cfg.Formats.PlainText = strings.TrimSpace(c.String(plainTextFormatFlag.Name))
|
return fmt.Errorf("template %s not found and cannot be used", cfg.TemplateName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,10 +252,12 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy
|
|||||||
logger.Strings("described HTTP codes", cfg.Codes.Codes()...),
|
logger.Strings("described HTTP codes", cfg.Codes.Codes()...),
|
||||||
logger.String("JSON format", cfg.Formats.JSON),
|
logger.String("JSON format", cfg.Formats.JSON),
|
||||||
logger.String("XML format", cfg.Formats.XML),
|
logger.String("XML format", cfg.Formats.XML),
|
||||||
|
logger.String("plain text format", cfg.Formats.PlainText),
|
||||||
logger.String("template name", cfg.TemplateName),
|
logger.String("template name", cfg.TemplateName),
|
||||||
logger.Bool("disable localization", cfg.L10n.Disable),
|
logger.Bool("disable localization", cfg.L10n.Disable),
|
||||||
logger.Uint16("default code to render", cfg.DefaultCodeToRender),
|
logger.Uint16("default code to render", cfg.DefaultCodeToRender),
|
||||||
logger.Bool("respond with the same HTTP code", cfg.RespondWithSameHTTPCode),
|
logger.Bool("respond with the same HTTP code", cfg.RespondWithSameHTTPCode),
|
||||||
|
logger.String("rotation mode", cfg.RotationMode.String()),
|
||||||
logger.Bool("show details", cfg.ShowDetails),
|
logger.Bool("show details", cfg.ShowDetails),
|
||||||
logger.Strings("proxy HTTP headers", cfg.ProxyHeaders...),
|
logger.Strings("proxy HTTP headers", cfg.ProxyHeaders...),
|
||||||
)
|
)
|
||||||
@ -258,6 +268,7 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy
|
|||||||
&addrFlag,
|
&addrFlag,
|
||||||
&portFlag,
|
&portFlag,
|
||||||
&addTplFlag,
|
&addTplFlag,
|
||||||
|
&disableTplFlag,
|
||||||
&addCodeFlag,
|
&addCodeFlag,
|
||||||
&jsonFormatFlag,
|
&jsonFormatFlag,
|
||||||
&xmlFormatFlag,
|
&xmlFormatFlag,
|
||||||
@ -269,7 +280,6 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy
|
|||||||
&showDetailsFlag,
|
&showDetailsFlag,
|
||||||
&proxyHeadersListFlag,
|
&proxyHeadersListFlag,
|
||||||
&rotationModeFlag,
|
&rotationModeFlag,
|
||||||
// &readBufferSizeFlag,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ var ListenPortFlag = cli.UintFlag{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var AddTemplateFlag = cli.StringSliceFlag{
|
var AddTemplatesFlag = cli.StringSliceFlag{
|
||||||
Name: "add-template",
|
Name: "add-template",
|
||||||
Usage: "to add a new template, provide the path to the file using this flag (the filename without the extension " +
|
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)",
|
"will be used as the template name)",
|
||||||
@ -69,7 +69,13 @@ var AddTemplateFlag = cli.StringSliceFlag{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var AddHTTPCodeFlag = cli.StringMapFlag{
|
var DisableTemplateNamesFlag = cli.StringSliceFlag{
|
||||||
|
Name: "disable-template",
|
||||||
|
Usage: "disable the specified template by its name",
|
||||||
|
Config: cli.StringConfig{TrimSpace: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
var AddHTTPCodesFlag = cli.StringMapFlag{
|
||||||
Name: "add-http-code",
|
Name: "add-http-code",
|
||||||
Usage: "to add a new HTTP status code, provide the code and its message/description using this flag (the format " +
|
Usage: "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, " +
|
"should be '%code%=%message%/%description%'; the code may contain a wildcard '*' to cover multiple codes at once, " +
|
||||||
|
@ -84,10 +84,10 @@ func TestListenPortFlag(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddTemplateFlag(t *testing.T) {
|
func TestAddTemplatesFlag(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var flag = shared.AddTemplateFlag
|
var flag = shared.AddTemplatesFlag
|
||||||
|
|
||||||
assert.Equal(t, "add-template", flag.Name)
|
assert.Equal(t, "add-template", flag.Name)
|
||||||
|
|
||||||
@ -108,10 +108,18 @@ func TestAddTemplateFlag(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddHTTPCodeFlag(t *testing.T) {
|
func TestDisableTemplateNamesFlag(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var flag = shared.AddHTTPCodeFlag
|
var flag = shared.DisableTemplateNamesFlag
|
||||||
|
|
||||||
|
assert.Equal(t, "disable-template", flag.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddHTTPCodesFlag(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var flag = shared.AddHTTPCodesFlag
|
||||||
|
|
||||||
assert.Equal(t, "add-http-code", flag.Name)
|
assert.Equal(t, "add-http-code", flag.Name)
|
||||||
|
|
||||||
|
@ -74,7 +74,8 @@ const defaultJSONFormat string = `{
|
|||||||
"request_id": {{ request_id | json }},
|
"request_id": {{ request_id | json }},
|
||||||
"timestamp": {{ now.Unix }}
|
"timestamp": {{ now.Unix }}
|
||||||
}{{ end }}
|
}{{ end }}
|
||||||
}`
|
}
|
||||||
|
` // an empty line at the end is important for better UX
|
||||||
|
|
||||||
const defaultXMLFormat string = `<?xml version="1.0" encoding="utf-8"?>
|
const defaultXMLFormat string = `<?xml version="1.0" encoding="utf-8"?>
|
||||||
<error>
|
<error>
|
||||||
@ -92,7 +93,8 @@ const defaultXMLFormat string = `<?xml version="1.0" encoding="utf-8"?>
|
|||||||
<requestID>{{ request_id }}</requestID>
|
<requestID>{{ request_id }}</requestID>
|
||||||
<timestamp>{{ now.Unix }}</timestamp>
|
<timestamp>{{ now.Unix }}</timestamp>
|
||||||
</details>{{ end }}
|
</details>{{ end }}
|
||||||
</error>`
|
</error>
|
||||||
|
` // an empty line at the end is important for better UX
|
||||||
|
|
||||||
const defaultPlainTextFormat string = `Error {{ code }}: {{ message }}{{ if description }}
|
const defaultPlainTextFormat string = `Error {{ code }}: {{ message }}{{ if description }}
|
||||||
{{ description }}{{ end }}{{ if show_details }}
|
{{ description }}{{ end }}{{ if show_details }}
|
||||||
@ -105,7 +107,8 @@ Ingress Name: {{ ingress_name }}
|
|||||||
Service Name: {{ service_name }}
|
Service Name: {{ service_name }}
|
||||||
Service Port: {{ service_port }}
|
Service Port: {{ service_port }}
|
||||||
Request ID: {{ request_id }}
|
Request ID: {{ request_id }}
|
||||||
Timestamp: {{ now.Unix }}{{ end }}`
|
Timestamp: {{ now.Unix }}{{ end }}
|
||||||
|
` // an empty line at the end is important for better UX
|
||||||
|
|
||||||
//nolint:lll
|
//nolint:lll
|
||||||
var defaultCodes = Codes{ //nolint:gochecknoglobals
|
var defaultCodes = Codes{ //nolint:gochecknoglobals
|
||||||
|
@ -12,8 +12,8 @@ const (
|
|||||||
RotationModeDisabled RotationMode = iota // do not rotate templates, default
|
RotationModeDisabled RotationMode = iota // do not rotate templates, default
|
||||||
RotationModeRandomOnStartup // pick a random template on startup
|
RotationModeRandomOnStartup // pick a random template on startup
|
||||||
RotationModeRandomOnEachRequest // pick a random template on each request
|
RotationModeRandomOnEachRequest // pick a random template on each request
|
||||||
RotationModeRandomDaily // once a day switch to a random template
|
|
||||||
RotationModeRandomHourly // once an hour switch to a random template
|
RotationModeRandomHourly // once an hour switch to a random template
|
||||||
|
RotationModeRandomDaily // once a day switch to a random template
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a human-readable representation of the rotation mode.
|
// String returns a human-readable representation of the rotation mode.
|
||||||
@ -25,10 +25,10 @@ func (rm RotationMode) String() string {
|
|||||||
return "random-on-startup"
|
return "random-on-startup"
|
||||||
case RotationModeRandomOnEachRequest:
|
case RotationModeRandomOnEachRequest:
|
||||||
return "random-on-each-request"
|
return "random-on-each-request"
|
||||||
case RotationModeRandomDaily:
|
|
||||||
return "random-daily"
|
|
||||||
case RotationModeRandomHourly:
|
case RotationModeRandomHourly:
|
||||||
return "random-hourly"
|
return "random-hourly"
|
||||||
|
case RotationModeRandomDaily:
|
||||||
|
return "random-daily"
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("RotationMode(%d)", rm)
|
return fmt.Sprintf("RotationMode(%d)", rm)
|
||||||
@ -40,8 +40,8 @@ func RotationModes() []RotationMode {
|
|||||||
RotationModeDisabled,
|
RotationModeDisabled,
|
||||||
RotationModeRandomOnStartup,
|
RotationModeRandomOnStartup,
|
||||||
RotationModeRandomOnEachRequest,
|
RotationModeRandomOnEachRequest,
|
||||||
RotationModeRandomDaily,
|
|
||||||
RotationModeRandomHourly,
|
RotationModeRandomHourly,
|
||||||
|
RotationModeRandomDaily,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,11 +77,11 @@ func ParseRotationMode[T string | []byte](text T) (RotationMode, error) {
|
|||||||
return RotationModeRandomOnStartup, nil
|
return RotationModeRandomOnStartup, nil
|
||||||
case RotationModeRandomOnEachRequest.String():
|
case RotationModeRandomOnEachRequest.String():
|
||||||
return RotationModeRandomOnEachRequest, nil
|
return RotationModeRandomOnEachRequest, nil
|
||||||
case RotationModeRandomDaily.String():
|
|
||||||
return RotationModeRandomDaily, nil
|
|
||||||
case RotationModeRandomHourly.String():
|
case RotationModeRandomHourly.String():
|
||||||
return RotationModeRandomHourly, nil
|
return RotationModeRandomHourly, nil
|
||||||
|
case RotationModeRandomDaily.String():
|
||||||
|
return RotationModeRandomDaily, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return RotationMode(0), fmt.Errorf("unrecognized rotation mode: %q", mode)
|
return RotationModeDisabled, fmt.Errorf("unrecognized rotation mode: %q", mode)
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,8 @@ func TestRotationModes(t *testing.T) {
|
|||||||
config.RotationModeDisabled,
|
config.RotationModeDisabled,
|
||||||
config.RotationModeRandomOnStartup,
|
config.RotationModeRandomOnStartup,
|
||||||
config.RotationModeRandomOnEachRequest,
|
config.RotationModeRandomOnEachRequest,
|
||||||
config.RotationModeRandomDaily,
|
|
||||||
config.RotationModeRandomHourly,
|
config.RotationModeRandomHourly,
|
||||||
|
config.RotationModeRandomDaily,
|
||||||
}, config.RotationModes())
|
}, config.RotationModes())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,8 +39,8 @@ func TestRotationModeStrings(t *testing.T) {
|
|||||||
"disabled",
|
"disabled",
|
||||||
"random-on-startup",
|
"random-on-startup",
|
||||||
"random-on-each-request",
|
"random-on-each-request",
|
||||||
"random-daily",
|
|
||||||
"random-hourly",
|
"random-hourly",
|
||||||
|
"random-daily",
|
||||||
}, config.RotationModeStrings())
|
}, config.RotationModeStrings())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,3 +81,25 @@ func (tpl templates) Has(name string) (found bool) { _, found = tpl[name]; retur
|
|||||||
|
|
||||||
// Get returns the template content by the specified name, if it exists.
|
// Get returns the template content by the specified name, if it exists.
|
||||||
func (tpl templates) Get(name string) (data string, ok bool) { data, ok = tpl[name]; return } //nolint:nlreturn
|
func (tpl templates) Get(name string) (data string, ok bool) { data, ok = tpl[name]; return } //nolint:nlreturn
|
||||||
|
|
||||||
|
// Remove deletes the template by the specified name.
|
||||||
|
func (tpl templates) Remove(name string) (ok bool) {
|
||||||
|
if _, ok = tpl[name]; ok {
|
||||||
|
delete(tpl, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandomName picks a random template name. It returns an empty string if there are no templates.
|
||||||
|
func (tpl templates) RandomName() string {
|
||||||
|
if len(tpl) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
for name := range tpl { // map iteration order is unpredictable (random) by design
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
@ -39,6 +39,10 @@ func TestTemplates_Common(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, []string{"_test11", "_test99", "test"}, tpl.Names()) // sorted
|
assert.Equal(t, []string{"_test11", "_test99", "test"}, tpl.Names()) // sorted
|
||||||
assert.True(t, tpl.Has("_test99"))
|
assert.True(t, tpl.Has("_test99"))
|
||||||
|
|
||||||
|
assert.True(t, tpl.Remove("_test99"))
|
||||||
|
assert.False(t, tpl.Has("_test99"))
|
||||||
|
assert.False(t, tpl.Remove("_test99"))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("adding template without a name should fail", func(t *testing.T) {
|
t.Run("adding template without a name should fail", func(t *testing.T) {
|
||||||
@ -134,3 +138,27 @@ func TestTemplates_AddFromFile(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTemplates_RandomName(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
tpl = templates{"test": "content", "test2": "content", "test3": "content"}
|
||||||
|
|
||||||
|
lastName = tpl.RandomName()
|
||||||
|
changedCount int
|
||||||
|
)
|
||||||
|
|
||||||
|
for range 1_000 {
|
||||||
|
var name = tpl.RandomName()
|
||||||
|
|
||||||
|
if name != lastName {
|
||||||
|
changedCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
lastName = name
|
||||||
|
}
|
||||||
|
|
||||||
|
// I expect at least 100 different names in 1000 iterations
|
||||||
|
assert.True(t, changedCount > 200)
|
||||||
|
}
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gh.tarampamp.am/error-pages/internal/config"
|
"gh.tarampamp.am/error-pages/internal/config"
|
||||||
"gh.tarampamp.am/error-pages/internal/logger"
|
"gh.tarampamp.am/error-pages/internal/logger"
|
||||||
@ -86,7 +88,6 @@ func New(cfg *config.Config, log *logger.Logger) http.Handler { //nolint:funlen,
|
|||||||
tplProps.Host = r.Header.Get("Host") // the value of the `Host` header
|
tplProps.Host = r.Header.Get("Host") // the value of the `Host` header
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: ADD SUPPORT FOR THE RANDOM TEMPLATE AND SO ON
|
|
||||||
// try to find the code message and description in the config and if not - use the standard status text or fallback
|
// try to find the code message and description in the config and if not - use the standard status text or fallback
|
||||||
if desc, found := cfg.Codes.Find(code); found {
|
if desc, found := cfg.Codes.Find(code); found {
|
||||||
tplProps.Message = desc.Message
|
tplProps.Message = desc.Message
|
||||||
@ -116,11 +117,13 @@ func New(cfg *config.Config, log *logger.Logger) http.Handler { //nolint:funlen,
|
|||||||
}
|
}
|
||||||
|
|
||||||
case format == htmlFormat:
|
case format == htmlFormat:
|
||||||
if tpl, found := cfg.Templates.Get(cfg.TemplateName); found {
|
var templateName = templateToUse(cfg)
|
||||||
|
|
||||||
|
if tpl, found := cfg.Templates.Get(templateName); found {
|
||||||
if content, err := template.Render(tpl, tplProps); err != nil {
|
if content, err := template.Render(tpl, tplProps); err != nil {
|
||||||
write(w, log, fmt.Sprintf(
|
write(w, log, fmt.Sprintf(
|
||||||
"<!DOCTYPE html>\n<html><body>Failed to render the HTML template %s: %s</body></html>",
|
"<!DOCTYPE html>\n<html><body>Failed to render the HTML template %s: %s</body></html>",
|
||||||
cfg.TemplateName,
|
templateName,
|
||||||
err.Error(),
|
err.Error(),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
@ -128,7 +131,7 @@ func New(cfg *config.Config, log *logger.Logger) http.Handler { //nolint:funlen,
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
write(w, log, fmt.Sprintf(
|
write(w, log, fmt.Sprintf(
|
||||||
"<!DOCTYPE html>\n<html><body>Template %s not found and cannot be used</body></html>", cfg.TemplateName,
|
"<!DOCTYPE html>\n<html><body>Template %s not found and cannot be used</body></html>", templateName,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,6 +152,54 @@ Supported formats: JSON, XML, HTML, Plain Text`)
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
templateChangedAt atomic.Pointer[time.Time] //nolint:gochecknoglobals // the time when the theme was changed last time
|
||||||
|
pickedTemplate atomic.Pointer[string] //nolint:gochecknoglobals // the name of the randomly picked template
|
||||||
|
)
|
||||||
|
|
||||||
|
// templateToUse decides which template to use based on the rotation mode and the last time the template was changed.
|
||||||
|
func templateToUse(cfg *config.Config) string {
|
||||||
|
switch rotationMode := cfg.RotationMode; rotationMode {
|
||||||
|
case config.RotationModeDisabled:
|
||||||
|
return cfg.TemplateName // not needed to do anything
|
||||||
|
case config.RotationModeRandomOnStartup:
|
||||||
|
return cfg.TemplateName // do nothing, the scope of this rotation mode is not here
|
||||||
|
case config.RotationModeRandomOnEachRequest:
|
||||||
|
return cfg.Templates.RandomName() // pick a random template on each request
|
||||||
|
case config.RotationModeRandomHourly, config.RotationModeRandomDaily:
|
||||||
|
var now, rndTemplate = time.Now(), cfg.Templates.RandomName()
|
||||||
|
|
||||||
|
if changedAt := templateChangedAt.Load(); changedAt == nil {
|
||||||
|
// the template was not changed yet (first request)
|
||||||
|
templateChangedAt.Store(&now)
|
||||||
|
pickedTemplate.Store(&rndTemplate)
|
||||||
|
|
||||||
|
return rndTemplate
|
||||||
|
} else {
|
||||||
|
// is it time to change the template?
|
||||||
|
if (rotationMode == config.RotationModeRandomHourly && changedAt.Hour() != now.Hour()) ||
|
||||||
|
(rotationMode == config.RotationModeRandomDaily && changedAt.Day() != now.Day()) {
|
||||||
|
templateChangedAt.Store(&now)
|
||||||
|
pickedTemplate.Store(&rndTemplate)
|
||||||
|
|
||||||
|
return rndTemplate
|
||||||
|
} else if lastUsed := pickedTemplate.Load(); lastUsed != nil {
|
||||||
|
// time to change the template has not come yet, so use the last picked template
|
||||||
|
return *lastUsed
|
||||||
|
} else {
|
||||||
|
// in case if the last picked template is not set, pick a random one and store it
|
||||||
|
templateChangedAt.Store(&now)
|
||||||
|
pickedTemplate.Store(&rndTemplate)
|
||||||
|
|
||||||
|
return rndTemplate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg.TemplateName // the fallback of the fallback :D
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the content to the response writer and log the error if any.
|
||||||
func write[T string | []byte](w http.ResponseWriter, log *logger.Logger, content T) {
|
func write[T string | []byte](w http.ResponseWriter, log *logger.Logger, content T) {
|
||||||
var data []byte
|
var data []byte
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user