Compare commits

...

9 Commits

32 changed files with 249 additions and 164 deletions

View File

@ -12,7 +12,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: peter-evans/dockerhub-description@v2 # Action page: <https://github.com/peter-evans/dockerhub-description>
- uses: peter-evans/dockerhub-description@v3 # Action page: <https://github.com/peter-evans/dockerhub-description>
with:
username: ${{ secrets.DOCKER_LOGIN }}
password: ${{ secrets.DOCKER_USER_PASSWORD }}

View File

@ -9,17 +9,13 @@ jobs:
name: Purge jsDelivr CDN cache
runs-on: ubuntu-20.04
steps:
- uses: fjogeleit/http-request-action@v1 # Action page: <https://github.com/fjogeleit/http-request-action>
with: {method: 'GET', url: 'https://purge.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'}
- uses: fjogeleit/http-request-action@v1
with: {method: 'GET', url: 'https://purge.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.js'}
- uses: fjogeleit/http-request-action@v1
with: {method: 'GET', url: 'https://purge.jsdelivr.net/gh/tarampampam/error-pages@latest/l10n/l10n.min.js'}
- uses: fjogeleit/http-request-action@v1
with: {method: 'GET', url: 'https://purge.jsdelivr.net/gh/tarampampam/error-pages@latest/l10n/l10n.js'}
- uses: gacts/purge-jsdelivr-cache@v1 # Action page: <https://github.com/gacts/purge-jsdelivr-cache>
with:
url: |
https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.js
https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js
https://cdn.jsdelivr.net/gh/tarampampam/error-pages@latest/l10n/l10n.js
https://cdn.jsdelivr.net/gh/tarampampam/error-pages@latest/l10n/l10n.min.js
build:
name: Build for ${{ matrix.os }} (${{ matrix.arch }})
@ -30,7 +26,7 @@ jobs:
os: [linux, darwin] # linux, freebsd, darwin, windows
arch: [amd64] # amd64, 386
steps:
- uses: actions/setup-go@v2
- uses: actions/setup-go@v3
with: {go-version: 1.18.0}
- uses: actions/checkout@v3
@ -91,8 +87,12 @@ jobs:
build-args: "APP_VERSION=${{ steps.slug.outputs.version }}"
tags: |
tarampampam/error-pages:${{ steps.slug.outputs.version }}
tarampampam/error-pages:${{ steps.slug.outputs.version-major }}.${{ steps.slug.outputs.version-minor }}
tarampampam/error-pages:${{ steps.slug.outputs.version-major }}
tarampampam/error-pages:latest
ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version }}
ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version-major }}.${{ steps.slug.outputs.version-minor }}
ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version-major }}
ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:latest
demo:

View File

@ -24,7 +24,7 @@ jobs: # Docs: <https://git.io/JvxXE>
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v2
- uses: actions/setup-go@v3
with: {go-version: 1.17} # On v1.18 I had an error "panic: load embedded ruleguard rules: rules/rules.go:13: can't load fmt"
- name: Run linter
@ -68,14 +68,14 @@ jobs: # Docs: <https://git.io/JvxXE>
name: Unit tests
runs-on: ubuntu-20.04
steps:
- uses: actions/setup-go@v2
- uses: actions/setup-go@v3
with: {go-version: 1.18}
- uses: actions/checkout@v3
with: {fetch-depth: 2} # Fixes codecov error 'Issue detecting commit SHA'
- name: Go modules Cache # Docs: <https://git.io/JfAKn#go---modules>
uses: actions/cache@v2
uses: actions/cache@v3
id: go-cache
with:
path: ~/go/pkg/mod
@ -104,7 +104,7 @@ jobs: # Docs: <https://git.io/JvxXE>
arch: [amd64] # amd64, 386
needs: [golangci-lint, go-test, validate-config-file]
steps:
- uses: actions/setup-go@v2
- uses: actions/setup-go@v3
with: {go-version: 1.18}
- uses: actions/checkout@v3
@ -113,7 +113,7 @@ jobs: # Docs: <https://git.io/JvxXE>
id: slug
- name: Go modules Cache # Docs: <https://git.io/JfAKn#go---modules>
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}

View File

@ -4,6 +4,19 @@ All notable changes to this package will be documented in this file.
The format is based on [Keep a Changelog][keepachangelog] and this project adheres to [Semantic Versioning][semver].
## v2.13.0
### Added
- Possibility to disable error pages auto-localization (using `--disable-l10n` flag for the `serve` & `build` commands or environment variable `DISABLE_L10N`) [#91]
### Fixed
- User UID/GID changed to the numeric values in the dockerfile [#92]
[#92]:https://github.com/tarampampam/error-pages/issues/92
[#91]:https://github.com/tarampampam/error-pages/issues/91
## v2.12.1
### Fixed

View File

@ -60,7 +60,7 @@ LABEL \
COPY --from=builder /tmp/rootfs /
# Use an unprivileged user
USER appuser:appuser
USER 10001:10001
WORKDIR /opt
@ -68,7 +68,8 @@ ENV LISTEN_PORT="8080" \
TEMPLATE_NAME="ghost" \
DEFAULT_ERROR_PAGE="404" \
DEFAULT_HTTP_CODE="404" \
SHOW_DETAILS="false"
SHOW_DETAILS="false" \
DISABLE_L10N="false"
# Docs: <https://docs.docker.com/engine/reference/builder/#healthcheck>
HEALTHCHECK --interval=7s --timeout=2s CMD ["/bin/error-pages", "healthcheck", "--log-json"]

4
go.mod
View File

@ -4,7 +4,7 @@ go 1.18
require (
github.com/a8m/envsubst v1.3.0
github.com/fasthttp/router v1.4.6
github.com/fasthttp/router v1.4.7
github.com/fatih/color v1.13.0
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d
github.com/pkg/errors v0.9.1
@ -32,7 +32,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899 // indirect
github.com/savsgio/gotils v0.0.0-20220323135742-7576ce6963fd // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect

13
go.sum
View File

@ -65,8 +65,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fasthttp/router v1.4.6 h1:KfETdHGBnvoBfBHeRe/8TVYz8Bp/mASBVC5UXO9CpZI=
github.com/fasthttp/router v1.4.6/go.mod h1:Iv800u3hYFNuBBcmJNs/VBVpub+JfBihGBp5spSocbw=
github.com/fasthttp/router v1.4.7 h1:G0kCNSx859t9U9akXANfSnYDmXngETipVPZfGGnGO8g=
github.com/fasthttp/router v1.4.7/go.mod h1:auS9NLoeFXaVcw1lHqe+LDLbb26QidGKtAQDZJ6jAMI=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@ -151,7 +151,6 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d h1:cVtBfNW5XTHiKQe7jDaDBSh/EVM4XLPutLAGboIXuM0=
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.14.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -207,8 +206,8 @@ github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899 h1:Orn7s+r1raRTBKLSc9DmbktTT04sL+vkzsbRD2Q8rOI=
github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899/go.mod h1:oejLrk1Y/5zOF+c/aHtXqn3TFlzzbAgPWg8zBiAHDas=
github.com/savsgio/gotils v0.0.0-20220323135742-7576ce6963fd h1:3URMJjR2af28gZjgZf5zJreZfq8EqXnRMj5fV2XdwqI=
github.com/savsgio/gotils v0.0.0-20220323135742-7576ce6963fd/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
@ -226,7 +225,6 @@ github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMT
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.33.0/go.mod h1:KJRK/MXx0J+yd0c5hlR+s1tIHD72sniU8ZJjl97LIw4=
github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4=
github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
@ -253,7 +251,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -317,7 +314,6 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -377,7 +373,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@ -15,6 +15,7 @@ import (
func NewCommand(log *zap.Logger, configFile *string) *cobra.Command {
var (
generateIndex bool
disableL10n bool
cfg *config.Config
)
@ -39,7 +40,7 @@ func NewCommand(log *zap.Logger, configFile *string) *cobra.Command {
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",
)
cmd.Flags().BoolVarP(
&disableL10n,
"disable-l10n", "",
false,
"disable error pages localization",
)
return cmd
}
@ -60,7 +68,7 @@ const (
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 {
return errors.New("no loaded templates")
}
@ -92,6 +100,7 @@ func run(log *zap.Logger, cfg *config.Config, outDirectoryPath string, generateI
Message: page.Message(),
Description: page.Description(),
ShowRequestDetails: false,
L10nDisabled: disableL10n,
})
if renderingErr != nil {
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")
}
if err = f.overrideUsingEnv(cmd.Flags()); err != nil {
if err = f.OverrideUsingEnv(cmd.Flags()); err != nil {
return err
}
@ -38,18 +38,18 @@ func NewCommand(ctx context.Context, log *zap.Logger, configFile *string) *cobra
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
}
// 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 (
ctx, cancel = context.WithCancel(parentCtx) // serve context creation
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 (
templateNames = cfg.TemplateNames()
picker interface{ Pick() string }
opt = f.ToOptions()
)
switch f.template.name {
switch opt.Template.Name {
case useRandomTemplate:
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)
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()))
picker = pick.NewStringsSlice([]string{t.Name()}, pick.First)
} 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
server := appHttp.NewServer(log)
// register server routes, middlewares, etc.
if err := server.Register(
cfg,
picker,
f.defaultErrorPage,
f.defaultHTTPCode,
f.showDetails,
proxyHTTPHeaders,
); err != nil {
if err := server.Register(cfg, picker, opt); err != nil {
return err
}
@ -131,15 +124,16 @@ func run(parentCtx context.Context, log *zap.Logger, f flags, cfg *config.Config
defer close(errCh)
log.Info("Server starting",
zap.String("addr", f.listen.ip),
zap.Uint16("port", f.listen.port),
zap.String("default error page", f.defaultErrorPage),
zap.Uint16("default HTTP response code", f.defaultHTTPCode),
zap.Strings("proxy headers", proxyHTTPHeaders),
zap.Bool("show request details", f.showDetails),
zap.String("addr", f.Listen.IP),
zap.Uint16("port", f.Listen.Port),
zap.String("default error page", opt.Default.PageCode),
zap.Uint16("default HTTP response code", opt.Default.HTTPCode),
zap.Strings("proxy headers", opt.ProxyHTTPHeaders),
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
}
}(startingErrCh)

View File

@ -9,60 +9,26 @@ import (
"github.com/spf13/pflag"
"github.com/tarampampam/error-pages/internal/env"
"github.com/tarampampam/error-pages/internal/options"
)
type flags struct {
listen struct {
ip string
port uint16
Listen struct {
IP string
Port uint16
}
template struct {
name string
}
l10n struct {
disabled bool
}
defaultErrorPage string
defaultHTTPCode uint16
showDetails bool
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 (
listenFlagName = "listen"
portFlagName = "port"
@ -71,6 +37,7 @@ const (
defaultHTTPCodeFlagName = "default-http-code"
showDetailsFlagName = "show-details"
proxyHTTPHeadersFlagName = "proxy-headers"
disableL10nFlagName = "disable-l10n"
)
const (
@ -80,18 +47,18 @@ const (
useRandomTemplateHourly = "random-hourly"
)
func (f *flags) init(flagSet *pflag.FlagSet) {
func (f *flags) Init(flagSet *pflag.FlagSet) {
flagSet.StringVarP(
&f.listen.ip,
&f.Listen.IP,
listenFlagName, "l",
"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(
&f.listen.port,
&f.Listen.Port,
portFlagName, "p",
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(
&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),
)
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) {
// flag was NOT defined using CLI (flags should have maximal priority)
if !flag.Changed { //nolint:nestif
switch flag.Name {
case listenFlagName:
if envVar, exists := env.ListenAddr.Lookup(); exists {
f.listen.ip = strings.TrimSpace(envVar)
f.Listen.IP = strings.TrimSpace(envVar)
}
case portFlagName:
if envVar, exists := env.ListenPort.Lookup(); exists {
if p, err := strconv.ParseUint(envVar, 10, 16); err == nil { //nolint:gomnd
f.listen.port = uint16(p)
f.Listen.Port = uint16(p)
} else {
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 {
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
}
func (f *flags) validate() error {
if net.ParseIP(f.listen.ip) == nil {
return fmt.Errorf("wrong IP address [%s] for listening", f.listen.ip)
func (f *flags) Validate() error {
if net.ParseIP(f.Listen.IP) == nil {
return fmt.Errorf("wrong IP address [%s] for listening", f.Listen.IP)
}
if f.defaultHTTPCode > 599 { //nolint:gomnd
@ -204,3 +184,52 @@ func (f *flags) validate() error {
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
ShowDetails envVariable = "SHOW_DETAILS" // show request details in 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.

View File

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

View File

@ -4,6 +4,7 @@ import (
"strconv"
"github.com/tarampampam/error-pages/internal/config"
"github.com/tarampampam/error-pages/internal/options"
"github.com/tarampampam/error-pages/internal/tpl"
"github.com/valyala/fasthttp"
)
@ -24,8 +25,7 @@ func RespondWithErrorPage( //nolint:funlen,gocyclo
rdr renderer,
pageCode string,
httpCode int,
showRequestDetails bool,
proxyHeaders []string,
opt options.ErrorPage,
) {
ctx.Response.Header.Set("X-Robots-Tag", "noindex") // block Search indexing
@ -33,10 +33,14 @@ func RespondWithErrorPage( //nolint:funlen,gocyclo
clientWant = ClientWantFormat(ctx)
json, canJSON = cfg.JSONFormat()
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.Namespace = string(ctx.Request.Header.Peek(Namespace))
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
for _, headerToProxy := range proxyHeaders {
for _, headerToProxy := range opt.ProxyHTTPHeaders {
if reqHeader := ctx.Request.Header.Peek(headerToProxy); len(reqHeader) > 0 {
ctx.Response.Header.SetBytesV(headerToProxy, reqHeader)
}

View File

@ -3,6 +3,7 @@ package errorpage
import (
"github.com/tarampampam/error-pages/internal/config"
"github.com/tarampampam/error-pages/internal/http/core"
"github.com/tarampampam/error-pages/internal/options"
"github.com/tarampampam/error-pages/internal/tpl"
"github.com/valyala/fasthttp"
)
@ -19,18 +20,12 @@ type (
)
// NewHandler creates handler for error pages serving.
func NewHandler(
cfg *config.Config,
p templatePicker,
rdr renderer,
showRequestDetails bool,
proxyHTTPHeaders []string,
) fasthttp.RequestHandler {
func NewHandler(cfg *config.Config, p templatePicker, rdr renderer, opt options.ErrorPage) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
core.SetClientFormat(ctx, core.PlainTextContentType) // default content type
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
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
_, _ = 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/http/core"
"github.com/tarampampam/error-pages/internal/options"
"github.com/tarampampam/error-pages/internal/tpl"
"github.com/valyala/fasthttp"
)
@ -21,23 +22,15 @@ type (
)
// NewHandler creates handler for the index page serving.
func NewHandler(
cfg *config.Config,
p templatePicker,
rdr renderer,
defaultPageCode string,
defaultHTTPCode uint16,
showRequestDetails bool,
proxyHTTPHeaders []string,
) fasthttp.RequestHandler {
func NewHandler(cfg *config.Config, p templatePicker, rdr renderer, opt options.ErrorPage) fasthttp.RequestHandler {
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 {
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"
versionHandler "github.com/tarampampam/error-pages/internal/http/handlers/version"
"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/version"
"github.com/valyala/fasthttp"
@ -66,14 +67,7 @@ type templatePicker interface {
// Register server routes, middlewares, etc.
// Router docs: <https://github.com/fasthttp/router>
func (s *Server) Register(
cfg *config.Config,
templatePicker templatePicker,
defaultPageCode string,
defaultHTTPCode uint16,
showDetails bool,
proxyHTTPHeaders []string,
) error {
func (s *Server) Register(cfg *config.Config, templatePicker templatePicker, opt options.ErrorPage) error {
reg, m := metrics.NewRegistry(), metrics.NewMetrics()
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.router.GET("/", indexHandler.NewHandler(cfg, templatePicker, s.rdr, defaultPageCode, defaultHTTPCode, showDetails, proxyHTTPHeaders)) //nolint:lll
s.router.GET("/{code}.html", errorpageHandler.NewHandler(cfg, templatePicker, s.rdr, 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, opt))
s.router.GET("/version", versionHandler.NewHandler(version.Version()))
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"`
ForwardedFor string `token:"forwarded_for"`
Host string `token:"host"`
L10nDisabled bool
ShowRequestDetails bool
}

View File

@ -140,8 +140,10 @@ func (tr *TemplateRenderer) Render(content []byte, props Properties) ([]byte, er
}
var funcMap = template.FuncMap{
"show_details": func() bool { return props.ShowRequestDetails },
"hide_details": func() bool { return !props.ShowRequestDetails },
"show_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

View File

@ -56,6 +56,17 @@ func Test_Render(t *testing.T) {
giveProps: tpl.Properties{Code: "201", Message: "lorem ipsum"},
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) {
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') {
((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';
@ -233,6 +234,7 @@
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--

View File

@ -104,6 +104,7 @@
</div>
</div>
<script>
// {{ if l10n_enabled }}
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.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -112,6 +113,7 @@
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--

View File

@ -254,6 +254,7 @@
console.warn('Cannot parse the error code:', errorCode);
}
// {{ if l10n_enabled }}
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.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -262,6 +263,7 @@
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
<!--
Error {{ code }}: {{ message }}

View File

@ -98,6 +98,7 @@
</div>
</div>
<script>
// {{ if l10n_enabled }}
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.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -106,6 +107,7 @@
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--

View File

@ -163,6 +163,7 @@
{{ end }}
</div>
<script>
// {{ if l10n_enabled }}
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.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -171,6 +172,7 @@
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--

View File

@ -82,6 +82,7 @@
</div>
</div>
<script>
// {{ if l10n_enabled }}
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.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -90,6 +91,7 @@
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--

View File

@ -84,6 +84,7 @@
</div>
</div>
<script>
// {{ if l10n_enabled }}
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.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -92,6 +93,7 @@
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--

View File

@ -378,6 +378,7 @@
console.warn('gsap library is not initialized (network error?)')
}
// {{ if l10n_enabled }}
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.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -386,6 +387,7 @@
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>

View File

@ -262,6 +262,7 @@
(new Matrix(document.getElementById('matrix'))).run(document.getElementById('matrix-words'));
// {{ if l10n_enabled }}
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.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -270,6 +271,7 @@
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--

View File

@ -175,6 +175,7 @@
window.clearInterval(flickerInterval);
});
// {{ if l10n_enabled }}
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.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -183,6 +184,7 @@
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--

View File

@ -10,24 +10,26 @@ Creating templates is a very simple operation, even for those who know nothing a
### Error page & request data
| Signature | Description | Example |
|-------------------------------------|---------------------------------------------------------------|----------------------------------------------|
| `{{ code }}` | Error page code | `404` |
| `{{ message }}` | Error code message | `Not found` |
| `{{ description }}` | Error code description | `The server can not find the requested page` |
| `{{ original_uri }}` | `X-Original-URI` header value | `/foo1/bar2` |
| `{{ namespace }}` | `X-Namespace` header value | `foo` |
| `{{ ingress_name }}` | `X-Ingress-Name` header value | `bar` |
| `{{ service_name }}` | `X-Service-Name` header value | `baz` |
| `{{ service_port }}` | `X-Service-Port` header value | `8080` |
| `{{ request_id }}` | `X-Request-ID` header value | `12AB34CD56EF78` |
| `{{ forwarded_for }}` | `X-Forwarded-For` header value | `203.0.113.195, 70.41.3.18` |
| `{{ host }}` | `Host` header value | `example.com` |
| `{{ now.Unix }}` | Current timestamp (e.g. in Unix format) | `1643621927` |
| `{{ hostname }}` | OS hostname | `ab12cd34ef56` |
| `{{ version }}` | Application version | `2.5.0` |
| `{{ if show_details }}...{{ end }}` | Logical operator (server started with "show details" option?) | |
| `{{ if hide_details }}...{{ end }}` | Same as above, but inverted | |
| Signature | Description | Example |
|--------------------------------------|---------------------------------------------------------------|----------------------------------------------|
| `{{ code }}` | Error page code | `404` |
| `{{ message }}` | Error code message | `Not found` |
| `{{ description }}` | Error code description | `The server can not find the requested page` |
| `{{ original_uri }}` | `X-Original-URI` header value | `/foo1/bar2` |
| `{{ namespace }}` | `X-Namespace` header value | `foo` |
| `{{ ingress_name }}` | `X-Ingress-Name` header value | `bar` |
| `{{ service_name }}` | `X-Service-Name` header value | `baz` |
| `{{ service_port }}` | `X-Service-Port` header value | `8080` |
| `{{ request_id }}` | `X-Request-ID` header value | `12AB34CD56EF78` |
| `{{ forwarded_for }}` | `X-Forwarded-For` header value | `203.0.113.195, 70.41.3.18` |
| `{{ host }}` | `Host` header value | `example.com` |
| `{{ now.Unix }}` | Current timestamp (e.g. in Unix format) | `1643621927` |
| `{{ hostname }}` | OS hostname | `ab12cd34ef56` |
| `{{ version }}` | Application version | `2.5.0` |
| `{{ if show_details }}...{{ end }}` | Logical operator (server started with "show details" option?) | |
| `{{ 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

View File

@ -215,6 +215,7 @@
}, 550);
// {{ end }}
// {{ if l10n_enabled }}
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.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
@ -223,6 +224,7 @@
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--