error-pages/internal/config/config.go

179 lines
7.0 KiB
Go

package config
import (
"maps"
"net/http"
"slices"
builtinTemplates "gh.tarampamp.am/error-pages/templates"
)
type Config struct {
// Templates hold all templates, with the key being the template name and the value being the template content
// in HTML format (Go templates are supported here).
Templates templates
// Formats contain alternative response formats (e.g., if a client requests a response in one of these formats,
// we will render the response using the specified format instead of HTML; Go templates are supported).
Formats struct {
JSON string
XML string
PlainText string
}
// 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
}
// DefaultCodeToRender 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).
DefaultCodeToRender uint16
// RespondWithSameHTTPCode determines whether the response should have the same HTTP status code as the requested
// error page.
// In other words, if set to true and the requested error page has a code of 404, the HTTP response will also have
// a status code of 404. If set to false, the HTTP response will have a status code of 200 regardless of the
// requested error page's status code.
RespondWithSameHTTPCode bool
// RotationMode allows to set the rotation mode for templates to switch between them automatically on startup,
// on each request, daily, hourly and so on.
RotationMode RotationMode
// 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 = `{
"error": true,
"code": {{ code | json }},
"message": {{ message | json }},
"description": {{ description | json }}{{ if show_details }},
"details": {
"host": {{ host | json }},
"original_uri": {{ original_uri | json }},
"forwarded_for": {{ forwarded_for | json }},
"namespace": {{ namespace | json }},
"ingress_name": {{ ingress_name | json }},
"service_name": {{ service_name | json }},
"service_port": {{ service_port | json }},
"request_id": {{ request_id | json }},
"timestamp": {{ nowUnix }}
}{{ end }}
}
` // an empty line at the end is important for better UX
const defaultXMLFormat string = `<?xml version="1.0" encoding="utf-8"?>
<error>
<code>{{ code }}</code>
<message>{{ message }}</message>
<description>{{ description }}</description>{{ if show_details }}
<details>
<host>{{ host }}</host>
<originalURI>{{ original_uri }}</originalURI>
<forwardedFor>{{ forwarded_for }}</forwardedFor>
<namespace>{{ namespace }}</namespace>
<ingressName>{{ ingress_name }}</ingressName>
<serviceName>{{ service_name }}</serviceName>
<servicePort>{{ service_port }}</servicePort>
<requestID>{{ request_id }}</requestID>
<timestamp>{{ nowUnix }}</timestamp>
</details>{{ end }}
</error>
` // an empty line at the end is important for better UX
const defaultPlainTextFormat string = `Error {{ code }}: {{ message }}{{ if description }}
{{ description }}{{ end }}{{ if show_details }}
Host: {{ host }}
Original URI: {{ original_uri }}
Forwarded For: {{ forwarded_for }}
Namespace: {{ namespace }}
Ingress Name: {{ ingress_name }}
Service Name: {{ service_name }}
Service Port: {{ service_port }}
Request ID: {{ request_id }}
Timestamp: {{ nowUnix }}{{ end }}
` // an empty line at the end is important for better UX
//nolint:lll
var defaultCodes = Codes{ //nolint:gochecknoglobals
"400": {"Bad Request", "The server did not understand the request"},
"401": {"Unauthorized", "The requested page needs a username and a password"},
"403": {"Forbidden", "Access is forbidden to the requested page"},
"404": {"Not Found", "The server can not find the requested page"},
"405": {"Method Not Allowed", "The method specified in the request is not allowed"},
"407": {"Proxy Authentication Required", "You must authenticate with a proxy server before this request can be served"},
"408": {"Request Timeout", "The request took longer than the server was prepared to wait"},
"409": {"Conflict", "The request could not be completed because of a conflict"},
"410": {"Gone", "The requested page is no longer available"},
"411": {"Length Required", "The \"Content-Length\" is not defined. The server will not accept the request without it"},
"412": {"Precondition Failed", "The pre condition given in the request evaluated to false by the server"},
"413": {"Payload Too Large", "The server will not accept the request, because the request entity is too large"},
"416": {"Requested Range Not Satisfiable", "The requested byte range is not available and is out of bounds"},
"418": {"I'm a teapot", "Attempt to brew coffee with a teapot is not supported"},
"429": {"Too Many Requests", "Too many requests in a given amount of time"},
"500": {"Internal Server Error", "The server met an unexpected condition"},
"502": {"Bad Gateway", "The server received an invalid response from the upstream server"},
"503": {"Service Unavailable", "The server is temporarily overloading or down"},
"504": {"Gateway Timeout", "The gateway has timed out"},
"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{
Templates: make(templates), // allocate memory for templates
Codes: maps.Clone(defaultCodes), // copy default codes
}
cfg.Formats.JSON = defaultJSONFormat
cfg.Formats.XML = defaultXMLFormat
cfg.Formats.PlainText = defaultPlainTextFormat
// add built-in templates
for name, content := range builtinTemplates.BuiltIn() {
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.DefaultCodeToRender = http.StatusNotFound
return cfg
}