From d40e8879d191253bf5a8d1ba1dbfecaf97cadd47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=D0=B0ramtamt=D0=B0m?= <7326800+tarampampam@users.noreply.github.com> Date: Fri, 7 Apr 2023 14:42:00 +0400 Subject: [PATCH] feat: Non-existing pages now return styled `404` status page (with `404` status code) (#189) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🎨 style(CHANGELOG.md): add UNRELEASED section 🔧 chore(notfound/handler.go): refactor to use core.RespondWithErrorPage 🔧 chore(http/server.go): pass config, templatePicker, renderer and options to notfoundHandler 🔧 chore(hurl/404.hurl): update test to expect HTML response instead of plain text * 📝 docs(CHANGELOG.md): fix typo in changed section * 🔥 chore(CHANGELOG.md): release version 2.22.0 🔥 chore(flags.go): remove unused code for serve command --- CHANGELOG.md | 8 + internal/cli/serve/flags.go | 220 --------------------- internal/http/handlers/notfound/handler.go | 22 ++- internal/http/server.go | 2 +- test/hurl/404.hurl | 4 +- 5 files changed, 29 insertions(+), 227 deletions(-) delete mode 100644 internal/cli/serve/flags.go diff --git a/CHANGELOG.md b/CHANGELOG.md index a800273..4643f38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ 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.22.0 + +### Changed + +- Non-existing pages now return styled `404` status page (with `404` status code) [#188] + +[#188]:https://github.com/tarampampam/error-pages/issues/188 + ## v2.21.0 ### Changed diff --git a/internal/cli/serve/flags.go b/internal/cli/serve/flags.go deleted file mode 100644 index b407204..0000000 --- a/internal/cli/serve/flags.go +++ /dev/null @@ -1,220 +0,0 @@ -package serve - -// import ( -// "fmt" -// "net" -// "sort" -// "strconv" -// "strings" -// -// "github.com/spf13/pflag" -// -// "gh.tarampamp.am/error-pages/internal/env" -// "gh.tarampamp.am/error-pages/internal/options" -// ) -// -// type flags struct { -// Listen struct { -// IP string -// Port uint16 -// } -// template struct { -// name string -// } -// l10n struct { -// disabled bool -// } -// defaultErrorPage string -// defaultHTTPCode uint16 -// showDetails bool -// proxyHTTPHeaders string // comma-separated -// } -// -// -// -// func (f *flags) Init(flagSet *pflag.FlagSet) { -// flagSet.StringVarP( -// &f.Listen.IP, -// listenFlagName, "l", -// "0.0.0.0", -// fmt.Sprintf("IP address to Listen on [$%s]", env.ListenAddr), -// ) -// flagSet.Uint16VarP( -// &f.Listen.Port, -// portFlagName, "p", -// 8080, //nolint:gomnd // must be same as default healthcheck `--port` flag value -// fmt.Sprintf("TCP prt number [$%s]", env.ListenPort), -// ) -// flagSet.StringVarP( -// &f.template.name, -// templateNameFlagName, "t", -// "", -// fmt.Sprintf( -// "template name (set \"%s\" to use a randomized or \"%s\" to use a randomized template on each request "+ -// "or \"%s/%s\" daily/hourly randomized) [$%s]", -// useRandomTemplate, -// useRandomTemplateOnEachRequest, -// useRandomTemplateDaily, -// useRandomTemplateHourly, -// env.TemplateName, -// ), -// ) -// flagSet.StringVarP( -// &f.defaultErrorPage, -// defaultErrorPageFlagName, "", -// "404", -// fmt.Sprintf("default error page [$%s]", env.DefaultErrorPage), -// ) -// flagSet.Uint16VarP( -// &f.defaultHTTPCode, -// defaultHTTPCodeFlagName, "", -// 404, //nolint:gomnd -// fmt.Sprintf("default HTTP response code [$%s]", env.DefaultHTTPCode), -// ) -// flagSet.BoolVarP( -// &f.showDetails, -// showDetailsFlagName, "", -// false, -// fmt.Sprintf("show request details in response [$%s]", env.ShowDetails), -// ) -// flagSet.StringVarP( -// &f.proxyHTTPHeaders, -// proxyHTTPHeadersFlagName, "", -// "", -// 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 -// 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) -// } -// -// case portFlagName: -// if envVar, exists := env.ListenPort.Lookup(); exists { -// if p, err := strconv.ParseUint(envVar, 10, 16); err == nil { -// f.Listen.Port = uint16(p) -// } else { -// lastErr = fmt.Errorf("wrong TCP port environment variable [%s] value", envVar) -// } -// } -// -// case templateNameFlagName: -// if envVar, exists := env.TemplateName.Lookup(); exists { -// f.template.name = strings.TrimSpace(envVar) -// } -// -// case defaultErrorPageFlagName: -// if envVar, exists := env.DefaultErrorPage.Lookup(); exists { -// f.defaultErrorPage = strings.TrimSpace(envVar) -// } -// -// case defaultHTTPCodeFlagName: -// if envVar, exists := env.DefaultHTTPCode.Lookup(); exists { -// if code, err := strconv.ParseUint(envVar, 10, 16); err == nil { -// f.defaultHTTPCode = uint16(code) -// } else { -// lastErr = fmt.Errorf("wrong default HTTP response code environment variable [%s] value", envVar) -// } -// } -// -// case showDetailsFlagName: -// if envVar, exists := env.ShowDetails.Lookup(); exists { -// if b, err := strconv.ParseBool(envVar); err == nil { -// f.showDetails = b -// } -// } -// -// case proxyHTTPHeadersFlagName: -// 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 -// } -// } -// } -// } -// }) -// -// 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) -// } -// -// if f.defaultHTTPCode > 599 { //nolint:gomnd -// return fmt.Errorf("wrong default HTTP response code [%d]", f.defaultHTTPCode) -// } -// -// if strings.ContainsRune(f.proxyHTTPHeaders, ' ') { -// return fmt.Errorf("whitespaces in the HTTP headers for proxying [%s] are not allowed", f.proxyHTTPHeaders) -// } -// -// 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 -// } diff --git a/internal/http/handlers/notfound/handler.go b/internal/http/handlers/notfound/handler.go index d531cfa..e57db41 100644 --- a/internal/http/handlers/notfound/handler.go +++ b/internal/http/handlers/notfound/handler.go @@ -2,13 +2,27 @@ package notfound import ( "github.com/valyala/fasthttp" + + "gh.tarampamp.am/error-pages/internal/config" + "gh.tarampamp.am/error-pages/internal/http/core" + "gh.tarampamp.am/error-pages/internal/options" + "gh.tarampamp.am/error-pages/internal/tpl" +) + +type ( + templatePicker interface { + // Pick the template name for responding. + Pick() string + } + + renderer interface { + Render(content []byte, props tpl.Properties) ([]byte, error) + } ) // NewHandler creates handler missing requests handling. -func NewHandler() fasthttp.RequestHandler { +func NewHandler(cfg *config.Config, p templatePicker, rdr renderer, opt options.ErrorPage) fasthttp.RequestHandler { return func(ctx *fasthttp.RequestCtx) { - ctx.SetContentType("text/plain; charset=utf-8") - ctx.SetStatusCode(fasthttp.StatusNotFound) - _, _ = ctx.WriteString("Wrong request URL. Error pages are available at the following URLs: /{code}.html") + core.RespondWithErrorPage(ctx, cfg, p, rdr, "404", fasthttp.StatusNotFound, opt) } } diff --git a/internal/http/server.go b/internal/http/server.go index 3dfb525..4863062 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -88,7 +88,7 @@ func (s *Server) Register(cfg *config.Config, templatePicker templatePicker, opt s.router.GET("/metrics", metricsHandler.NewHandler(reg)) - s.router.NotFound = notfoundHandler.NewHandler() + s.router.NotFound = notfoundHandler.NewHandler(cfg, templatePicker, s.rdr, opt) return nil } diff --git a/test/hurl/404.hurl b/test/hurl/404.hurl index 8574199..fe8e0a1 100644 --- a/test/hurl/404.hurl +++ b/test/hurl/404.hurl @@ -3,5 +3,5 @@ GET http://{{ host }}:{{ port }}/not-found HTTP/* 404 [Asserts] -header "Content-Type" contains "text/plain" -body contains "Wrong request URL" +header "Content-Type" contains "text/html" +body contains "The server can not find the requested page"