Added possibility to disable error pages auto-localization (#94)

This commit is contained in:
Paramtamtam 2022-04-12 14:34:35 +04:00 committed by GitHub
parent a3389aaafa
commit d21a6f2797
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 216 additions and 134 deletions

View File

@ -6,11 +6,16 @@ The format is based on [Keep a Changelog][keepachangelog] and this project adher
## UNRELEASED ## UNRELEASED
### Added
- Possibility to disable error pages auto-localization (using `--disable-l10n` flag for the `serve` & `build` commands or environment variable `DISABLE_L10N`) [#91]
### Fixed ### Fixed
- User UID/GID changed to the numeric values in the dockerfile [#92] - User UID/GID changed to the numeric values in the dockerfile [#92]
[#92]:https://github.com/tarampampam/error-pages/issues/92 [#92]:https://github.com/tarampampam/error-pages/issues/92
[#91]:https://github.com/tarampampam/error-pages/issues/91
## v2.12.1 ## v2.12.1

View File

@ -68,7 +68,8 @@ ENV LISTEN_PORT="8080" \
TEMPLATE_NAME="ghost" \ TEMPLATE_NAME="ghost" \
DEFAULT_ERROR_PAGE="404" \ DEFAULT_ERROR_PAGE="404" \
DEFAULT_HTTP_CODE="404" \ DEFAULT_HTTP_CODE="404" \
SHOW_DETAILS="false" SHOW_DETAILS="false" \
DISABLE_L10N="false"
# Docs: <https://docs.docker.com/engine/reference/builder/#healthcheck> # Docs: <https://docs.docker.com/engine/reference/builder/#healthcheck>
HEALTHCHECK --interval=7s --timeout=2s CMD ["/bin/error-pages", "healthcheck", "--log-json"] HEALTHCHECK --interval=7s --timeout=2s CMD ["/bin/error-pages", "healthcheck", "--log-json"]

View File

@ -15,6 +15,7 @@ import (
func NewCommand(log *zap.Logger, configFile *string) *cobra.Command { func NewCommand(log *zap.Logger, configFile *string) *cobra.Command {
var ( var (
generateIndex bool generateIndex bool
disableL10n bool
cfg *config.Config cfg *config.Config
) )
@ -39,7 +40,7 @@ func NewCommand(log *zap.Logger, configFile *string) *cobra.Command {
return errors.New("wrong arguments count") return errors.New("wrong arguments count")
} }
return run(log, cfg, args[0], generateIndex) return run(log, cfg, args[0], generateIndex, disableL10n)
}, },
} }
@ -50,6 +51,13 @@ func NewCommand(log *zap.Logger, configFile *string) *cobra.Command {
"generate index page", "generate index page",
) )
cmd.Flags().BoolVarP(
&disableL10n,
"disable-l10n", "",
false,
"disable error pages localization",
)
return cmd return cmd
} }
@ -60,7 +68,7 @@ const (
outDirPerm = os.FileMode(0775) outDirPerm = os.FileMode(0775)
) )
func run(log *zap.Logger, cfg *config.Config, outDirectoryPath string, generateIndex bool) error { //nolint:funlen func run(log *zap.Logger, cfg *config.Config, outDirectoryPath string, generateIndex, disableL10n bool) error { //nolint:funlen,lll
if len(cfg.Templates) == 0 { if len(cfg.Templates) == 0 {
return errors.New("no loaded templates") return errors.New("no loaded templates")
} }
@ -92,6 +100,7 @@ func run(log *zap.Logger, cfg *config.Config, outDirectoryPath string, generateI
Message: page.Message(), Message: page.Message(),
Description: page.Description(), Description: page.Description(),
ShowRequestDetails: false, ShowRequestDetails: false,
L10nDisabled: disableL10n,
}) })
if renderingErr != nil { if renderingErr != nil {
return renderingErr return renderingErr

View File

@ -30,7 +30,7 @@ func NewCommand(ctx context.Context, log *zap.Logger, configFile *string) *cobra
return errors.New("path to the config file is required for this command") return errors.New("path to the config file is required for this command")
} }
if err = f.overrideUsingEnv(cmd.Flags()); err != nil { if err = f.OverrideUsingEnv(cmd.Flags()); err != nil {
return err return err
} }
@ -38,18 +38,18 @@ func NewCommand(ctx context.Context, log *zap.Logger, configFile *string) *cobra
return err return err
} }
return f.validate() return f.Validate()
}, },
RunE: func(*cobra.Command, []string) error { return run(ctx, log, f, cfg) }, RunE: func(*cobra.Command, []string) error { return run(ctx, log, cfg, f) },
} }
f.init(cmd.Flags()) f.Init(cmd.Flags())
return cmd return cmd
} }
// run current command. // run current command.
func run(parentCtx context.Context, log *zap.Logger, f flags, cfg *config.Config) error { //nolint:funlen func run(parentCtx context.Context, log *zap.Logger, cfg *config.Config, f flags) error { //nolint:funlen
var ( var (
ctx, cancel = context.WithCancel(parentCtx) // serve context creation ctx, cancel = context.WithCancel(parentCtx) // serve context creation
oss = breaker.NewOSSignals(ctx) // OS signals listener oss = breaker.NewOSSignals(ctx) // OS signals listener
@ -70,9 +70,11 @@ func run(parentCtx context.Context, log *zap.Logger, f flags, cfg *config.Config
var ( var (
templateNames = cfg.TemplateNames() templateNames = cfg.TemplateNames()
picker interface{ Pick() string } picker interface{ Pick() string }
opt = f.ToOptions()
) )
switch f.template.name { switch opt.Template.Name {
case useRandomTemplate: case useRandomTemplate:
log.Info("A random template will be used") log.Info("A random template will be used")
@ -99,28 +101,19 @@ func run(parentCtx context.Context, log *zap.Logger, f flags, cfg *config.Config
picker = pick.NewStringsSlice(templateNames, pick.First) picker = pick.NewStringsSlice(templateNames, pick.First)
default: default:
if t, found := cfg.Template(f.template.name); found { if t, found := cfg.Template(opt.Template.Name); found {
log.Info("We will use the requested template", zap.String("name", t.Name())) log.Info("We will use the requested template", zap.String("name", t.Name()))
picker = pick.NewStringsSlice([]string{t.Name()}, pick.First) picker = pick.NewStringsSlice([]string{t.Name()}, pick.First)
} else { } else {
return errors.New("requested nonexistent template: " + f.template.name) return errors.New("requested nonexistent template: " + opt.Template.Name)
} }
} }
var proxyHTTPHeaders = f.HeadersToProxy()
// create HTTP server // create HTTP server
server := appHttp.NewServer(log) server := appHttp.NewServer(log)
// register server routes, middlewares, etc. // register server routes, middlewares, etc.
if err := server.Register( if err := server.Register(cfg, picker, opt); err != nil {
cfg,
picker,
f.defaultErrorPage,
f.defaultHTTPCode,
f.showDetails,
proxyHTTPHeaders,
); err != nil {
return err return err
} }
@ -131,15 +124,16 @@ func run(parentCtx context.Context, log *zap.Logger, f flags, cfg *config.Config
defer close(errCh) defer close(errCh)
log.Info("Server starting", log.Info("Server starting",
zap.String("addr", f.listen.ip), zap.String("addr", f.Listen.IP),
zap.Uint16("port", f.listen.port), zap.Uint16("port", f.Listen.Port),
zap.String("default error page", f.defaultErrorPage), zap.String("default error page", opt.Default.PageCode),
zap.Uint16("default HTTP response code", f.defaultHTTPCode), zap.Uint16("default HTTP response code", opt.Default.HTTPCode),
zap.Strings("proxy headers", proxyHTTPHeaders), zap.Strings("proxy headers", opt.ProxyHTTPHeaders),
zap.Bool("show request details", f.showDetails), zap.Bool("show request details", opt.ShowDetails),
zap.Bool("localization disabled", opt.L10n.Disabled),
) )
if err := server.Start(f.listen.ip, f.listen.port); err != nil { if err := server.Start(f.Listen.IP, f.Listen.Port); err != nil {
errCh <- err errCh <- err
} }
}(startingErrCh) }(startingErrCh)

View File

@ -9,60 +9,26 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/tarampampam/error-pages/internal/env" "github.com/tarampampam/error-pages/internal/env"
"github.com/tarampampam/error-pages/internal/options"
) )
type flags struct { type flags struct {
listen struct { Listen struct {
ip string IP string
port uint16 Port uint16
} }
template struct { template struct {
name string name string
} }
l10n struct {
disabled bool
}
defaultErrorPage string defaultErrorPage string
defaultHTTPCode uint16 defaultHTTPCode uint16
showDetails bool showDetails bool
proxyHTTPHeaders string // comma-separated proxyHTTPHeaders string // comma-separated
} }
// HeadersToProxy converts a comma-separated string with headers list into strings slice (with a sorting and without
// duplicates).
func (f *flags) HeadersToProxy() []string {
var raw = strings.Split(f.proxyHTTPHeaders, ",")
if len(raw) == 0 {
return []string{}
} else if len(raw) == 1 {
if h := strings.TrimSpace(raw[0]); h != "" {
return []string{h}
} else {
return []string{}
}
}
var m = make(map[string]struct{}, len(raw))
// make unique and ignore empty strings
for _, h := range raw {
if h = strings.TrimSpace(h); h != "" {
if _, ok := m[h]; !ok {
m[h] = struct{}{}
}
}
}
// convert map into slice
var headers = make([]string, 0, len(m))
for h := range m {
headers = append(headers, h)
}
// make sort
sort.Strings(headers)
return headers
}
const ( const (
listenFlagName = "listen" listenFlagName = "listen"
portFlagName = "port" portFlagName = "port"
@ -71,6 +37,7 @@ const (
defaultHTTPCodeFlagName = "default-http-code" defaultHTTPCodeFlagName = "default-http-code"
showDetailsFlagName = "show-details" showDetailsFlagName = "show-details"
proxyHTTPHeadersFlagName = "proxy-headers" proxyHTTPHeadersFlagName = "proxy-headers"
disableL10nFlagName = "disable-l10n"
) )
const ( const (
@ -80,18 +47,18 @@ const (
useRandomTemplateHourly = "random-hourly" useRandomTemplateHourly = "random-hourly"
) )
func (f *flags) init(flagSet *pflag.FlagSet) { func (f *flags) Init(flagSet *pflag.FlagSet) {
flagSet.StringVarP( flagSet.StringVarP(
&f.listen.ip, &f.Listen.IP,
listenFlagName, "l", listenFlagName, "l",
"0.0.0.0", "0.0.0.0",
fmt.Sprintf("IP address to listen on [$%s]", env.ListenAddr), fmt.Sprintf("IP address to Listen on [$%s]", env.ListenAddr),
) )
flagSet.Uint16VarP( flagSet.Uint16VarP(
&f.listen.port, &f.Listen.Port,
portFlagName, "p", portFlagName, "p",
8080, //nolint:gomnd // must be same as default healthcheck `--port` flag value 8080, //nolint:gomnd // must be same as default healthcheck `--port` flag value
fmt.Sprintf("TCP port number [$%s]", env.ListenPort), fmt.Sprintf("TCP prt number [$%s]", env.ListenPort),
) )
flagSet.StringVarP( flagSet.StringVarP(
&f.template.name, &f.template.name,
@ -131,22 +98,28 @@ func (f *flags) init(flagSet *pflag.FlagSet) {
"", "",
fmt.Sprintf("proxy HTTP request headers list (comma-separated) [$%s]", env.ProxyHTTPHeaders), fmt.Sprintf("proxy HTTP request headers list (comma-separated) [$%s]", env.ProxyHTTPHeaders),
) )
flagSet.BoolVarP(
&f.l10n.disabled,
disableL10nFlagName, "",
false,
fmt.Sprintf("disable error pages localization [$%s]", env.DisableL10n),
)
} }
func (f *flags) overrideUsingEnv(flagSet *pflag.FlagSet) (lastErr error) { //nolint:gocognit,gocyclo func (f *flags) OverrideUsingEnv(flagSet *pflag.FlagSet) (lastErr error) { //nolint:gocognit,gocyclo
flagSet.VisitAll(func(flag *pflag.Flag) { flagSet.VisitAll(func(flag *pflag.Flag) {
// flag was NOT defined using CLI (flags should have maximal priority) // flag was NOT defined using CLI (flags should have maximal priority)
if !flag.Changed { //nolint:nestif if !flag.Changed { //nolint:nestif
switch flag.Name { switch flag.Name {
case listenFlagName: case listenFlagName:
if envVar, exists := env.ListenAddr.Lookup(); exists { if envVar, exists := env.ListenAddr.Lookup(); exists {
f.listen.ip = strings.TrimSpace(envVar) f.Listen.IP = strings.TrimSpace(envVar)
} }
case portFlagName: case portFlagName:
if envVar, exists := env.ListenPort.Lookup(); exists { if envVar, exists := env.ListenPort.Lookup(); exists {
if p, err := strconv.ParseUint(envVar, 10, 16); err == nil { //nolint:gomnd if p, err := strconv.ParseUint(envVar, 10, 16); err == nil { //nolint:gomnd
f.listen.port = uint16(p) f.Listen.Port = uint16(p)
} else { } else {
lastErr = fmt.Errorf("wrong TCP port environment variable [%s] value", envVar) lastErr = fmt.Errorf("wrong TCP port environment variable [%s] value", envVar)
} }
@ -182,6 +155,13 @@ func (f *flags) overrideUsingEnv(flagSet *pflag.FlagSet) (lastErr error) { //nol
if envVar, exists := env.ProxyHTTPHeaders.Lookup(); exists { if envVar, exists := env.ProxyHTTPHeaders.Lookup(); exists {
f.proxyHTTPHeaders = strings.TrimSpace(envVar) f.proxyHTTPHeaders = strings.TrimSpace(envVar)
} }
case disableL10nFlagName:
if envVar, exists := env.DisableL10n.Lookup(); exists {
if b, err := strconv.ParseBool(envVar); err == nil {
f.l10n.disabled = b
}
}
} }
} }
}) })
@ -189,9 +169,9 @@ func (f *flags) overrideUsingEnv(flagSet *pflag.FlagSet) (lastErr error) { //nol
return lastErr return lastErr
} }
func (f *flags) validate() error { func (f *flags) Validate() error {
if net.ParseIP(f.listen.ip) == nil { if net.ParseIP(f.Listen.IP) == nil {
return fmt.Errorf("wrong IP address [%s] for listening", f.listen.ip) return fmt.Errorf("wrong IP address [%s] for listening", f.Listen.IP)
} }
if f.defaultHTTPCode > 599 { //nolint:gomnd if f.defaultHTTPCode > 599 { //nolint:gomnd
@ -204,3 +184,52 @@ func (f *flags) validate() error {
return nil return nil
} }
// headersToProxy converts a comma-separated string with headers list into strings slice (with a sorting and without
// duplicates).
func (f *flags) headersToProxy() []string {
var raw = strings.Split(f.proxyHTTPHeaders, ",")
if len(raw) == 0 {
return []string{}
} else if len(raw) == 1 {
if h := strings.TrimSpace(raw[0]); h != "" {
return []string{h}
} else {
return []string{}
}
}
var m = make(map[string]struct{}, len(raw))
// make unique and ignore empty strings
for _, h := range raw {
if h = strings.TrimSpace(h); h != "" {
if _, ok := m[h]; !ok {
m[h] = struct{}{}
}
}
}
// convert map into slice
var headers = make([]string, 0, len(m))
for h := range m {
headers = append(headers, h)
}
// make sort
sort.Strings(headers)
return headers
}
func (f *flags) ToOptions() (o options.ErrorPage) {
o.Default.PageCode = f.defaultErrorPage
o.Default.HTTPCode = f.defaultHTTPCode
o.L10n.Disabled = f.l10n.disabled
o.Template.Name = f.template.name
o.ShowDetails = f.showDetails
o.ProxyHTTPHeaders = f.headersToProxy()
return o
}

1
internal/env/env.go vendored
View File

@ -14,6 +14,7 @@ const (
DefaultHTTPCode envVariable = "DEFAULT_HTTP_CODE" // default HTTP response code DefaultHTTPCode envVariable = "DEFAULT_HTTP_CODE" // default HTTP response code
ShowDetails envVariable = "SHOW_DETAILS" // show request details in response ShowDetails envVariable = "SHOW_DETAILS" // show request details in response
ProxyHTTPHeaders envVariable = "PROXY_HTTP_HEADERS" // proxy HTTP request headers list (request -> response) ProxyHTTPHeaders envVariable = "PROXY_HTTP_HEADERS" // proxy HTTP request headers list (request -> response)
DisableL10n envVariable = "DISABLE_L10N" // disable pages localization
) )
// String returns environment variable name in the string representation. // String returns environment variable name in the string representation.

View File

@ -16,6 +16,7 @@ func TestConstants(t *testing.T) {
assert.Equal(t, "DEFAULT_HTTP_CODE", string(DefaultHTTPCode)) assert.Equal(t, "DEFAULT_HTTP_CODE", string(DefaultHTTPCode))
assert.Equal(t, "SHOW_DETAILS", string(ShowDetails)) assert.Equal(t, "SHOW_DETAILS", string(ShowDetails))
assert.Equal(t, "PROXY_HTTP_HEADERS", string(ProxyHTTPHeaders)) assert.Equal(t, "PROXY_HTTP_HEADERS", string(ProxyHTTPHeaders))
assert.Equal(t, "DISABLE_L10N", string(DisableL10n))
} }
func TestEnvVariable_Lookup(t *testing.T) { func TestEnvVariable_Lookup(t *testing.T) {
@ -30,6 +31,7 @@ func TestEnvVariable_Lookup(t *testing.T) {
{giveEnv: DefaultHTTPCode}, {giveEnv: DefaultHTTPCode},
{giveEnv: ShowDetails}, {giveEnv: ShowDetails},
{giveEnv: ProxyHTTPHeaders}, {giveEnv: ProxyHTTPHeaders},
{giveEnv: DisableL10n},
} }
for _, tt := range cases { for _, tt := range cases {

View File

@ -4,6 +4,7 @@ import (
"strconv" "strconv"
"github.com/tarampampam/error-pages/internal/config" "github.com/tarampampam/error-pages/internal/config"
"github.com/tarampampam/error-pages/internal/options"
"github.com/tarampampam/error-pages/internal/tpl" "github.com/tarampampam/error-pages/internal/tpl"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
) )
@ -24,8 +25,7 @@ func RespondWithErrorPage( //nolint:funlen,gocyclo
rdr renderer, rdr renderer,
pageCode string, pageCode string,
httpCode int, httpCode int,
showRequestDetails bool, opt options.ErrorPage,
proxyHeaders []string,
) { ) {
ctx.Response.Header.Set("X-Robots-Tag", "noindex") // block Search indexing ctx.Response.Header.Set("X-Robots-Tag", "noindex") // block Search indexing
@ -33,10 +33,14 @@ func RespondWithErrorPage( //nolint:funlen,gocyclo
clientWant = ClientWantFormat(ctx) clientWant = ClientWantFormat(ctx)
json, canJSON = cfg.JSONFormat() json, canJSON = cfg.JSONFormat()
xml, canXML = cfg.XMLFormat() xml, canXML = cfg.XMLFormat()
props = tpl.Properties{Code: pageCode, ShowRequestDetails: showRequestDetails} props = tpl.Properties{
Code: pageCode,
ShowRequestDetails: opt.ShowDetails,
L10nDisabled: opt.L10n.Disabled,
}
) )
if showRequestDetails { if opt.ShowDetails {
props.OriginalURI = string(ctx.Request.Header.Peek(OriginalURI)) props.OriginalURI = string(ctx.Request.Header.Peek(OriginalURI))
props.Namespace = string(ctx.Request.Header.Peek(Namespace)) props.Namespace = string(ctx.Request.Header.Peek(Namespace))
props.IngressName = string(ctx.Request.Header.Peek(IngressName)) props.IngressName = string(ctx.Request.Header.Peek(IngressName))
@ -66,7 +70,7 @@ func RespondWithErrorPage( //nolint:funlen,gocyclo
} }
// proxy required HTTP headers from the request to the response // proxy required HTTP headers from the request to the response
for _, headerToProxy := range proxyHeaders { for _, headerToProxy := range opt.ProxyHTTPHeaders {
if reqHeader := ctx.Request.Header.Peek(headerToProxy); len(reqHeader) > 0 { if reqHeader := ctx.Request.Header.Peek(headerToProxy); len(reqHeader) > 0 {
ctx.Response.Header.SetBytesV(headerToProxy, reqHeader) ctx.Response.Header.SetBytesV(headerToProxy, reqHeader)
} }

View File

@ -3,6 +3,7 @@ package errorpage
import ( import (
"github.com/tarampampam/error-pages/internal/config" "github.com/tarampampam/error-pages/internal/config"
"github.com/tarampampam/error-pages/internal/http/core" "github.com/tarampampam/error-pages/internal/http/core"
"github.com/tarampampam/error-pages/internal/options"
"github.com/tarampampam/error-pages/internal/tpl" "github.com/tarampampam/error-pages/internal/tpl"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
) )
@ -19,18 +20,12 @@ type (
) )
// NewHandler creates handler for error pages serving. // NewHandler creates handler for error pages serving.
func NewHandler( func NewHandler(cfg *config.Config, p templatePicker, rdr renderer, opt options.ErrorPage) fasthttp.RequestHandler {
cfg *config.Config,
p templatePicker,
rdr renderer,
showRequestDetails bool,
proxyHTTPHeaders []string,
) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) { return func(ctx *fasthttp.RequestCtx) {
core.SetClientFormat(ctx, core.PlainTextContentType) // default content type core.SetClientFormat(ctx, core.PlainTextContentType) // default content type
if code, ok := ctx.UserValue("code").(string); ok { if code, ok := ctx.UserValue("code").(string); ok {
core.RespondWithErrorPage(ctx, cfg, p, rdr, code, fasthttp.StatusOK, showRequestDetails, proxyHTTPHeaders) core.RespondWithErrorPage(ctx, cfg, p, rdr, code, fasthttp.StatusOK, opt)
} else { // will never occur } else { // will never occur
ctx.SetStatusCode(fasthttp.StatusInternalServerError) ctx.SetStatusCode(fasthttp.StatusInternalServerError)
_, _ = ctx.WriteString("cannot extract requested code from the request") _, _ = ctx.WriteString("cannot extract requested code from the request")

View File

@ -5,6 +5,7 @@ import (
"github.com/tarampampam/error-pages/internal/config" "github.com/tarampampam/error-pages/internal/config"
"github.com/tarampampam/error-pages/internal/http/core" "github.com/tarampampam/error-pages/internal/http/core"
"github.com/tarampampam/error-pages/internal/options"
"github.com/tarampampam/error-pages/internal/tpl" "github.com/tarampampam/error-pages/internal/tpl"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
) )
@ -21,23 +22,15 @@ type (
) )
// NewHandler creates handler for the index page serving. // NewHandler creates handler for the index page serving.
func NewHandler( func NewHandler(cfg *config.Config, p templatePicker, rdr renderer, opt options.ErrorPage) fasthttp.RequestHandler {
cfg *config.Config,
p templatePicker,
rdr renderer,
defaultPageCode string,
defaultHTTPCode uint16,
showRequestDetails bool,
proxyHTTPHeaders []string,
) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) { return func(ctx *fasthttp.RequestCtx) {
pageCode, httpCode := defaultPageCode, int(defaultHTTPCode) pageCode, httpCode := opt.Default.PageCode, int(opt.Default.HTTPCode)
if returnCode, ok := extractCodeToReturn(ctx); ok { if returnCode, ok := extractCodeToReturn(ctx); ok {
pageCode, httpCode = strconv.Itoa(returnCode), returnCode pageCode, httpCode = strconv.Itoa(returnCode), returnCode
} }
core.RespondWithErrorPage(ctx, cfg, p, rdr, pageCode, httpCode, showRequestDetails, proxyHTTPHeaders) core.RespondWithErrorPage(ctx, cfg, p, rdr, pageCode, httpCode, opt)
} }
} }

View File

@ -15,6 +15,7 @@ import (
notfoundHandler "github.com/tarampampam/error-pages/internal/http/handlers/notfound" notfoundHandler "github.com/tarampampam/error-pages/internal/http/handlers/notfound"
versionHandler "github.com/tarampampam/error-pages/internal/http/handlers/version" versionHandler "github.com/tarampampam/error-pages/internal/http/handlers/version"
"github.com/tarampampam/error-pages/internal/metrics" "github.com/tarampampam/error-pages/internal/metrics"
"github.com/tarampampam/error-pages/internal/options"
"github.com/tarampampam/error-pages/internal/tpl" "github.com/tarampampam/error-pages/internal/tpl"
"github.com/tarampampam/error-pages/internal/version" "github.com/tarampampam/error-pages/internal/version"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
@ -66,14 +67,7 @@ type templatePicker interface {
// Register server routes, middlewares, etc. // Register server routes, middlewares, etc.
// Router docs: <https://github.com/fasthttp/router> // Router docs: <https://github.com/fasthttp/router>
func (s *Server) Register( func (s *Server) Register(cfg *config.Config, templatePicker templatePicker, opt options.ErrorPage) error {
cfg *config.Config,
templatePicker templatePicker,
defaultPageCode string,
defaultHTTPCode uint16,
showDetails bool,
proxyHTTPHeaders []string,
) error {
reg, m := metrics.NewRegistry(), metrics.NewMetrics() reg, m := metrics.NewRegistry(), metrics.NewMetrics()
if err := m.Register(reg); err != nil { if err := m.Register(reg); err != nil {
@ -82,8 +76,9 @@ func (s *Server) Register(
s.fast.Handler = common.DurationMetrics(common.LogRequest(s.router.Handler, s.log), &m) s.fast.Handler = common.DurationMetrics(common.LogRequest(s.router.Handler, s.log), &m)
s.router.GET("/", indexHandler.NewHandler(cfg, templatePicker, s.rdr, defaultPageCode, defaultHTTPCode, showDetails, proxyHTTPHeaders)) //nolint:lll s.router.GET("/", indexHandler.NewHandler(cfg, templatePicker, s.rdr, opt))
s.router.GET("/{code}.html", errorpageHandler.NewHandler(cfg, templatePicker, s.rdr, showDetails, proxyHTTPHeaders)) //nolint:lll s.router.GET("/{code}.html", errorpageHandler.NewHandler(cfg, templatePicker, s.rdr, opt))
s.router.GET("/version", versionHandler.NewHandler(version.Version())) s.router.GET("/version", versionHandler.NewHandler(version.Version()))
liveHandler := healthzHandler.NewHandler(checkers.NewLiveChecker()) liveHandler := healthzHandler.NewHandler(checkers.NewLiveChecker())

View File

@ -0,0 +1,16 @@
package options
type ErrorPage struct {
Default struct {
PageCode string // default error page code
HTTPCode uint16 // default HTTP response code
}
L10n struct {
Disabled bool // disable error pages localization
}
Template struct {
Name string // template name
}
ShowDetails bool // show request details in response
ProxyHTTPHeaders []string // proxy HTTP request headers list
}

View File

@ -16,6 +16,7 @@ type Properties struct { // only string properties with a "token" tag, please
RequestID string `token:"request_id"` RequestID string `token:"request_id"`
ForwardedFor string `token:"forwarded_for"` ForwardedFor string `token:"forwarded_for"`
Host string `token:"host"` Host string `token:"host"`
L10nDisabled bool
ShowRequestDetails bool ShowRequestDetails bool
} }

View File

@ -142,6 +142,8 @@ func (tr *TemplateRenderer) Render(content []byte, props Properties) ([]byte, er
var funcMap = template.FuncMap{ var funcMap = template.FuncMap{
"show_details": func() bool { return props.ShowRequestDetails }, "show_details": func() bool { return props.ShowRequestDetails },
"hide_details": func() bool { return !props.ShowRequestDetails }, "hide_details": func() bool { return !props.ShowRequestDetails },
"l10n_disabled": func() bool { return props.L10nDisabled },
"l10n_enabled": func() bool { return !props.L10nDisabled },
} }
// make a copy of template functions map // make a copy of template functions map

View File

@ -56,6 +56,17 @@ func Test_Render(t *testing.T) {
giveProps: tpl.Properties{Code: "201", Message: "lorem ipsum"}, giveProps: tpl.Properties{Code: "201", Message: "lorem ipsum"},
wantContent: `{"code": "201", "message": {"here":[ " Yeah " ]}}`, wantContent: `{"code": "201", "message": {"here":[ " Yeah " ]}}`,
}, },
"fn l10n_enabled": {
giveContent: "{{ if l10n_enabled }}Y{{ else }}N{{ end }}",
giveProps: tpl.Properties{L10nDisabled: true},
wantContent: "N",
},
"fn l10n_disabled": {
giveContent: "{{ if l10n_disabled }}Y{{ else }}N{{ end }}",
giveProps: tpl.Properties{L10nDisabled: true},
wantContent: "Y",
},
} { } {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
content, err := renderer.Render([]byte(tt.giveContent), tt.giveProps) content, err := renderer.Render([]byte(tt.giveContent), tt.giveProps)

View File

@ -225,6 +225,7 @@
} }
}); });
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') { if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n) ((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js'; s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -233,6 +234,7 @@
p.appendChild(s); p.appendChild(s);
})(document.createElement('script'), document.body); })(document.createElement('script'), document.body);
} }
// {{ end }}
</script> </script>
</body> </body>
<!-- <!--

View File

@ -104,6 +104,7 @@
</div> </div>
</div> </div>
<script> <script>
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') { if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n) ((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js'; s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -112,6 +113,7 @@
p.appendChild(s); p.appendChild(s);
})(document.createElement('script'), document.body); })(document.createElement('script'), document.body);
} }
// {{ end }}
</script> </script>
</body> </body>
<!-- <!--

View File

@ -254,6 +254,7 @@
console.warn('Cannot parse the error code:', errorCode); console.warn('Cannot parse the error code:', errorCode);
} }
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') { if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n) ((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js'; s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -262,6 +263,7 @@
p.appendChild(s); p.appendChild(s);
})(document.createElement('script'), document.body); })(document.createElement('script'), document.body);
} }
// {{ end }}
</script> </script>
<!-- <!--
Error {{ code }}: {{ message }} Error {{ code }}: {{ message }}

View File

@ -98,6 +98,7 @@
</div> </div>
</div> </div>
<script> <script>
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') { if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n) ((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js'; s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -106,6 +107,7 @@
p.appendChild(s); p.appendChild(s);
})(document.createElement('script'), document.body); })(document.createElement('script'), document.body);
} }
// {{ end }}
</script> </script>
</body> </body>
<!-- <!--

View File

@ -163,6 +163,7 @@
{{ end }} {{ end }}
</div> </div>
<script> <script>
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') { if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n) ((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js'; s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -171,6 +172,7 @@
p.appendChild(s); p.appendChild(s);
})(document.createElement('script'), document.body); })(document.createElement('script'), document.body);
} }
// {{ end }}
</script> </script>
</body> </body>
<!-- <!--

View File

@ -82,6 +82,7 @@
</div> </div>
</div> </div>
<script> <script>
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') { if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n) ((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js'; s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -90,6 +91,7 @@
p.appendChild(s); p.appendChild(s);
})(document.createElement('script'), document.body); })(document.createElement('script'), document.body);
} }
// {{ end }}
</script> </script>
</body> </body>
<!-- <!--

View File

@ -84,6 +84,7 @@
</div> </div>
</div> </div>
<script> <script>
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') { if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n) ((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js'; s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -92,6 +93,7 @@
p.appendChild(s); p.appendChild(s);
})(document.createElement('script'), document.body); })(document.createElement('script'), document.body);
} }
// {{ end }}
</script> </script>
</body> </body>
<!-- <!--

View File

@ -378,6 +378,7 @@
console.warn('gsap library is not initialized (network error?)') console.warn('gsap library is not initialized (network error?)')
} }
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') { if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n) ((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js'; s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -386,6 +387,7 @@
p.appendChild(s); p.appendChild(s);
})(document.createElement('script'), document.body); })(document.createElement('script'), document.body);
} }
// {{ end }}
</script> </script>
</body> </body>

View File

@ -262,6 +262,7 @@
(new Matrix(document.getElementById('matrix'))).run(document.getElementById('matrix-words')); (new Matrix(document.getElementById('matrix'))).run(document.getElementById('matrix-words'));
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') { if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n) ((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js'; s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -270,6 +271,7 @@
p.appendChild(s); p.appendChild(s);
})(document.createElement('script'), document.body); })(document.createElement('script'), document.body);
} }
// {{ end }}
</script> </script>
</body> </body>
<!-- <!--

View File

@ -175,6 +175,7 @@
window.clearInterval(flickerInterval); window.clearInterval(flickerInterval);
}); });
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') { if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n) ((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js'; s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -183,6 +184,7 @@
p.appendChild(s); p.appendChild(s);
})(document.createElement('script'), document.body); })(document.createElement('script'), document.body);
} }
// {{ end }}
</script> </script>
</body> </body>
<!-- <!--

View File

@ -11,7 +11,7 @@ Creating templates is a very simple operation, even for those who know nothing a
### Error page & request data ### Error page & request data
| Signature | Description | Example | | Signature | Description | Example |
|-------------------------------------|---------------------------------------------------------------|----------------------------------------------| |--------------------------------------|---------------------------------------------------------------|----------------------------------------------|
| `{{ code }}` | Error page code | `404` | | `{{ code }}` | Error page code | `404` |
| `{{ message }}` | Error code message | `Not found` | | `{{ message }}` | Error code message | `Not found` |
| `{{ description }}` | Error code description | `The server can not find the requested page` | | `{{ description }}` | Error code description | `The server can not find the requested page` |
@ -28,6 +28,8 @@ Creating templates is a very simple operation, even for those who know nothing a
| `{{ version }}` | Application version | `2.5.0` | | `{{ version }}` | Application version | `2.5.0` |
| `{{ if show_details }}...{{ end }}` | Logical operator (server started with "show details" option?) | | | `{{ if show_details }}...{{ end }}` | Logical operator (server started with "show details" option?) | |
| `{{ if hide_details }}...{{ end }}` | Same as above, but inverted | | | `{{ if hide_details }}...{{ end }}` | Same as above, but inverted | |
| `{{ if l10n_enabled }}...{{ end }}` | Logical operator (l10n is enabled?) | |
| `{{ if l10n_disabled }}...{{ end }}` | Same as above, but inverted | |
### Modifiers ### Modifiers

View File

@ -215,6 +215,7 @@
}, 550); }, 550);
// {{ end }} // {{ end }}
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') { if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n) ((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js'; s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -223,6 +224,7 @@
p.appendChild(s); p.appendChild(s);
})(document.createElement('script'), document.body); })(document.createElement('script'), document.body);
} }
// {{ end }}
</script> </script>
</body> </body>
<!-- <!--