From 2776c41e0d2adf2ee4b220d2730a7b53c8a8fd50 Mon Sep 17 00:00:00 2001 From: Paramtamtam <7326800+tarampampam@users.noreply.github.com> Date: Fri, 28 Jun 2024 16:25:43 +0400 Subject: [PATCH] =?UTF-8?q?wip:=20=F0=9F=94=95=20temporary=20commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/cli/serve/command.go | 37 ++- internal/http/handlers/error_page/handler.go | 7 +- internal/http/handlers/static/favicon.ico | Bin 0 -> 15086 bytes internal/http/handlers/static/handler.go | 27 ++ internal/http/handlers/static/handler_test.go | 80 ++++++ internal/http/server.go | 11 +- templates/app-down.html | 2 +- templates/cats.html | 2 +- templates/connection.html | 2 +- templates/ghost.html | 2 +- templates/hacker-terminal.html | 2 +- templates/l7.html | 2 +- templates/lost-in-space.html | 2 +- templates/shuffle.html | 267 ++++++++++++++++++ 14 files changed, 430 insertions(+), 13 deletions(-) create mode 100644 internal/http/handlers/static/favicon.ico create mode 100644 internal/http/handlers/static/handler.go create mode 100644 internal/http/handlers/static/handler_test.go create mode 100644 templates/shuffle.html diff --git a/internal/cli/serve/command.go b/internal/cli/serve/command.go index e4562e5..c49c637 100644 --- a/internal/cli/serve/command.go +++ b/internal/cli/serve/command.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "strings" "time" @@ -109,7 +110,7 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy OnlyOnce: true, } proxyHeadersListFlag = cli.StringFlag{ - Name: "proxy-headers", + Name: "proxy-headers", // TODO: add support for the "*" wildcard Usage: "listed here HTTP headers will be proxied from the original request to the error page response " + "(comma-separated list)", Value: strings.Join(cfg.ProxyHeaders, ","), @@ -235,6 +236,11 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy } } + // check if there are any templates available to render error pages + if len(cfg.Templates.Names()) == 0 { + return errors.New("no templates available to render error pages") + } + // if the rotation mode is set to random-on-startup, pick a random template (ignore the user-provided // template name) if cfg.RotationMode == config.RotationModeRandomOnStartup { @@ -291,7 +297,7 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy } // Run current command. -func (cmd *command) Run(ctx context.Context, log *logger.Logger, cfg *config.Config) error { +func (cmd *command) Run(ctx context.Context, log *logger.Logger, cfg *config.Config) error { //nolint:funlen var srv = appHttp.NewServer(ctx, log) if err := srv.Register(cfg); err != nil { @@ -301,6 +307,33 @@ func (cmd *command) Run(ctx context.Context, log *logger.Logger, cfg *config.Con var startingErrCh = make(chan error, 1) // channel for server starting error defer close(startingErrCh) + // to track the frequency of each template's use, we send a simple GET request to the GoatCounter API + // (https://goatcounter.com, https://github.com/arp242/goatcounter) to increment the counter. this service is + // free and does not require an API key. no private data is sent, as shown in the URL below. this is necessary + // to render a badge displaying the number of template usages on the error-pages repository README file :D + // + // badge code example: + // ![Used times](https://img.shields.io/badge/dynamic/json? + // url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Flost-in-space.json + // &query=%24.count&label=Used%20times) + go func() { + req, reqErr := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf( + "https://error-pages.goatcounter.com/count?event=true&p=/use-template/%s", url.QueryEscape(cfg.TemplateName), + ), http.NoBody) + if reqErr != nil { + return + } + + resp, respErr := (&http.Client{Timeout: 10 * time.Second}).Do(req) //nolint:mnd // don't care about the response + if respErr != nil { + log.Debug("Cannot send a request to increment the template usage counter", logger.Error(respErr)) + + return + } else if resp != nil { + _ = resp.Body.Close() + } + }() + // start HTTP server in separate goroutine go func(errCh chan<- error) { var now = time.Now() diff --git a/internal/http/handlers/error_page/handler.go b/internal/http/handlers/error_page/handler.go index 2122cc2..64dd62e 100644 --- a/internal/http/handlers/error_page/handler.go +++ b/internal/http/handlers/error_page/handler.go @@ -53,9 +53,12 @@ func New(cfg *config.Config, log *logger.Logger) http.Handler { //nolint:funlen, // disallow indexing of the error pages w.Header().Set("X-Robots-Tag", "noindex") - if code >= 500 && code < 600 { + switch code { + case http.StatusRequestTimeout, http.StatusTooEarly, http.StatusTooManyRequests, + http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable, + http.StatusGatewayTimeout: // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After - // tell the client (search crawler) to retry the request after 120 seconds, it makes sense for the 5xx errors + // tell the client (search crawler) to retry the request after 120 seconds w.Header().Set("Retry-After", "120") } diff --git a/internal/http/handlers/static/favicon.ico b/internal/http/handlers/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1eb79d59e52bf88b59b536b47f772025ccdae307 GIT binary patch literal 15086 zcmd5@d$d$l89zvdww6Jq)gKktz*nV`z}&kM1@0V{A}>o2LwTx&h0DrZtBA+7Obs>G zqW=QokqGk2H0&YOwEz*3=an~j(~|dVHRqhUb7$`E_uFUo%$YOiv1jhR(phWIK9BwF z@ArNC`}X78dkmwC(beeF$3R|VthmZB5{6;a)a0Lg7{(F2>w%o#r+&mRoCorSMAFUxOAOv9c}Qz#P*)S`oQ>4GHhI2NRbt3r8^$CUmlzO@=~qY^3@o!K}&xdspaSJaqm+=j%LJo?6pBj<$aQ{L!oGU;XRP(oZL#k2rau zuow86_8DM#Fj0R_yB*MOBhVjSFufPBtG?K@PXNQHrEsNA&o16RK& z8Q`NmP-cy;ulBD`iGG7qf_bI#R!%E@`Va7lPld?1&;?8?5sRJFJ&}%C&J{p7{zS_FU9Gr%VRgI= z3j42@WF&S)UzrTjj-I+{(9yy?1AR?T;E&Zku@B4Zy{%1gck_Y_!-Ly_*at|x1A9v; zEuS{B9mZ+4gK2ZVh*`IL0sH;XeKyK=$bSUdxp70Q@MBkKD8urAc2IhszZ5nTfL+mV zd^0QVywBF~B`XKa(}+Hi{O_f>kL`1)`-Z2}CGD>GdUL-FMeMh}FN(h~%hX z`}5-i=6@sgg&Y%MJC6T*&@*MY<&KnC3>jK9WO(s+87VLOnW#%)XFtq#4&q(@&f>aZ zD$+cien-(u8HQS7@eA=Xs7T722kdfj$#Wr(w- z&cU9?V>%l!x7$-GewKIJ0k)*k4nZ=sQtvNSqxYgNf&BfsFaYy_MHR|Gn^_6$ zgJ|y+*-Q~TbqN1G3;RD?js6vll40qJjG(>iTx(xx6@x;?IaRM-ZlyZ{Kj$;-S9S5P zdAU`4z7#D7@uv}IDs30tXQlh!JdeI8VqEpN32mQ!E8C#`_EWw8()KIGu4LgDCuL}d z572+nf3DZnw<=fA`?AXPuCS|I`Z3#~S{Y1dBkccIm5yIh?;6;3$FG%D$>5G(=z}H! zzgAp|dgr=nkp_0Re{U7+m!jsJYzvlUe_GiXEawjo0sr+#&B`{ya$@HiOQ)}ww!PL} z&#-S=sNvso9swCDn7{o8xcg!L{4vTLf4mZ4FLRzamBA+hK;qvUtf<{zrYSwVBYK>o0PxD$?%U&?i!5c!|cF) zFV`;*6FV>9uR+pUKMoyZiS>=;GN?JfAG^Y#WLOZ=4zF*_is6W7Xw9GHeuHzhi{AYO z{)RXkA{N$v`{+^`6n14Z3bQ{~w&Y*WNbG1^E$rBD0M=jh9dx{~ey0S8om_=AvDRFa ze1_&_pv^9a&BS3>vXsaGU#bi{?aDp~zx|R|_v#*UJ&ZQcj$hFq9%uY&q~4m(E5of= z$1#3YN$dyM7{7W<;q~W`jq$_SzQ~J%Z4vKp<^D*T%4ntCyb9f4?#JzY2L3)svA(Qi z_|S61d@$eP)@$qq{3GEDRnJR1Tqe^#2~1;Rf#(bKuRq8Ai;1B5KiI!;xPS4jMDo1$ z{sqN=uW+0TH4s_hV8;JyeXwN=xx&KDnnDkZdSKNjDyRKeL z)!d`~xF>@c&~M(u?4K2yJ2`>7L(kK!ezm zvl{ddAl2afMn(Ng7`95^0|U>`@GQ(Y0DpODW*+40D~ zjOR-$<>xOiL~}PG45N^;hB1)O=M1Ae^G3ty%De&Rh?uhi{IoHjZ5SvH0>el%?`aqy z17R_5=5sg0kU4siMm~G;20p`L{5fBDRB|glKqXQy z1wA$9^``U)f}SW;&~skzJf~HIGzh71POAqVs<`Tgv|zk|Et}4Lu#x$&k1ZJg5MOoA zVdd}NLcS1XFute1NSZ2Gm5|h$8S=$C-gz2zbF9+X&p^xq>jls}1KJzwhGeS30ouz^ z$8^a0ujnx?Vhl9x_ff|z)b;5S!D1dj!@Z|>@Bmpt~kf>zA;4z~eRO;WJ)6 zK;|xZKNIa8cz%t%@1AVOo=IRi#tOzxu_M;p@;uwm5=p1v7m&RVc)u5fnRQNl$QB0< z<;2@Nvf>-#gX?BFt~obt$%;eo=ERg~!R1^Y7GhlPnHA^@vO5@`%JuH$tzxrVw`av& z6H4n=ytz-Uu2>AFJuhKe=wF;((EnaPf7VHzQ*QsY6?#w@6z@$Q-m3gz=waN0rC5u6 zEUf#SYmg>G_hoTb+7ZtUY`F=kSj?0F2Py&SSf_B9g2z2O+D<}K~bZi4L{3zJ{H zcQIIEbBG?i7_{V8wr<*|;rA)UCn_g~QLqR4VA$2PCs(k`sJe>ZtV|{R>EBWOcUQ+> zOaG&A9_x*_!ev)F*|sw)Mo)~fyQ2Qj!2Y%u^`U&Dm`lBb_ht>=m1E$XCdU6Fc<*dN zpA30}u5IwYSUs5b3iLlyLi-=IcbMU}-tU!V2PztlZ+ zaszNh_6K43wBDQcZt#eV9|2o!r(lr0(*u;OQ8_Sj?-X<44qJcx1#_=?)sB4#yPOfJ#n!gG`v>(6$_IqOWh z{=O1*;_Sb@5cPAd4Ij48Gw~kpwjy7IGK^jI!unf_uQ=CTh=wjQe1K5GK*S+>GPKZ) zC(_sBWRo5)QxqrZt_65Vz%T7-i%W)EK!40h*QM;D*`dtiy?PC z+G7#YVYgkVcX_`UI!L0-M^RP3tSI?kq#2$oo3T4oKPKn*Pv=;ualalekpmELf z4rMIF0o^)?8P8njNZMKxdzIKT_S+BN@muk|^-D=>+9v2v@IAkCk8kgRoOpDGBl|n{ zdEfi7BlhgiiP=x3MP0FuQ?QHMKr83@#77?Q>~qJ}<0sn04}Ri^M}FjpqsQFC&#aew zkVRRn=Rtp@h$$s$jvaT)pFYzjPMvn|Pn>KMkIi!YG9ZRh8oN<=Nvp22=i0;%XS(uu zd4ZPy_bZA0gwZQ~1kjh@|NeBy>&2hPaXcJk literal 0 HcmV?d00001 diff --git a/internal/http/handlers/static/handler.go b/internal/http/handlers/static/handler.go new file mode 100644 index 0000000..5d57228 --- /dev/null +++ b/internal/http/handlers/static/handler.go @@ -0,0 +1,27 @@ +package static + +import ( + _ "embed" + "net/http" +) + +//go:embed favicon.ico +var Favicon []byte + +// New creates a new handler that returns the provided content for GET and HEAD requests. +func New(content []byte) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + w.Header().Set("Content-Type", http.DetectContentType(content)) + w.WriteHeader(http.StatusOK) + _, _ = w.Write(content) + + case http.MethodHead: + w.WriteHeader(http.StatusOK) + + default: + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + } + }) +} diff --git a/internal/http/handlers/static/handler_test.go b/internal/http/handlers/static/handler_test.go new file mode 100644 index 0000000..e1fde0b --- /dev/null +++ b/internal/http/handlers/static/handler_test.go @@ -0,0 +1,80 @@ +package static_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + + "gh.tarampamp.am/error-pages/internal/http/handlers/static" +) + +func TestServeHTTP(t *testing.T) { + t.Parallel() + + var handler = static.New([]byte{1, 2, 3}) + + t.Run("get", func(t *testing.T) { + var ( + req = httptest.NewRequest(http.MethodGet, "http://testing", http.NoBody) + rr = httptest.NewRecorder() + ) + + handler.ServeHTTP(rr, req) + + assert.Equal(t, rr.Header().Get("Content-Type"), "application/octet-stream") + assert.Equal(t, rr.Code, http.StatusOK) + assert.Equal(t, rr.Body.Bytes(), []byte{1, 2, 3}) + }) + + t.Run("head", func(t *testing.T) { + var ( + req = httptest.NewRequest(http.MethodHead, "http://testing", http.NoBody) + rr = httptest.NewRecorder() + ) + + handler.ServeHTTP(rr, req) + + assert.Equal(t, rr.Code, http.StatusOK) + assert.Empty(t, rr.Header().Get("Content-Type")) + assert.Empty(t, rr.Body.Bytes()) + }) + + t.Run("method not allowed", func(t *testing.T) { + for _, method := range []string{ + http.MethodDelete, + http.MethodPatch, + http.MethodPost, + http.MethodPut, + } { + var ( + req = httptest.NewRequest(method, "http://testing", http.NoBody) + rr = httptest.NewRecorder() + ) + + handler.ServeHTTP(rr, req) + + assert.Equal(t, rr.Header().Get("Content-Type"), "text/plain; charset=utf-8") + assert.Equal(t, rr.Code, http.StatusMethodNotAllowed) + assert.Equal(t, "Method Not Allowed\n", rr.Body.String()) + } + }) +} + +func TestServeHTTP_Favicon(t *testing.T) { + t.Parallel() + + var ( + handler = static.New(static.Favicon) + + req = httptest.NewRequest(http.MethodGet, "http://testing", http.NoBody) + rr = httptest.NewRecorder() + ) + + handler.ServeHTTP(rr, req) + + assert.Equal(t, rr.Header().Get("Content-Type"), "image/x-icon") + assert.Equal(t, rr.Code, http.StatusOK) + assert.Equal(t, rr.Body.Bytes(), static.Favicon) +} diff --git a/internal/http/server.go b/internal/http/server.go index 0265483..1ebd028 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -12,6 +12,7 @@ import ( "gh.tarampamp.am/error-pages/internal/config" ep "gh.tarampamp.am/error-pages/internal/http/handlers/error_page" "gh.tarampamp.am/error-pages/internal/http/handlers/live" + "gh.tarampamp.am/error-pages/internal/http/handlers/static" "gh.tarampamp.am/error-pages/internal/http/handlers/version" "gh.tarampamp.am/error-pages/internal/http/middleware/logreq" "gh.tarampamp.am/error-pages/internal/logger" @@ -50,6 +51,7 @@ func (s *Server) Register(cfg *config.Config) error { liveHandler = live.New() versionHandler = version.New(appmeta.Version()) errorPagesHandler = ep.New(cfg, s.log) + faviconHandler = static.New(static.Favicon) ) s.server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -64,6 +66,10 @@ func (s *Server) Register(cfg *config.Config) error { case url == "/version": versionHandler.ServeHTTP(w, r) + // favicon.ico endpoint + case url == "/favicon.ico": + faviconHandler.ServeHTTP(w, r) + // error pages endpoints: // - / // - /{code}.html @@ -87,8 +93,9 @@ func (s *Server) Register(cfg *config.Config) error { // apply middleware s.server.Handler = logreq.New(s.log, func(r *http.Request) bool { - // skip logging healthcheck requests - return strings.Contains(strings.ToLower(r.UserAgent()), "healthcheck") + // skip logging healthcheck and .ico (favicon) requests + return strings.Contains(strings.ToLower(r.UserAgent()), "healthcheck") || + strings.HasSuffix(r.URL.Path, ".ico") })(s.server.Handler) return nil diff --git a/templates/app-down.html b/templates/app-down.html index b2a7f87..2179fcd 100644 --- a/templates/app-down.html +++ b/templates/app-down.html @@ -6,7 +6,7 @@ {{ message }} - + diff --git a/templates/cats.html b/templates/cats.html index 0ef5440..ce92b85 100644 --- a/templates/cats.html +++ b/templates/cats.html @@ -6,7 +6,7 @@ {{ message }} - + diff --git a/templates/connection.html b/templates/connection.html index 59ea3a9..c53f999 100644 --- a/templates/connection.html +++ b/templates/connection.html @@ -6,7 +6,7 @@ {{ code }} | {{ message }} - + diff --git a/templates/ghost.html b/templates/ghost.html index 609d621..edb3020 100644 --- a/templates/ghost.html +++ b/templates/ghost.html @@ -6,7 +6,7 @@ {{ code }}: {{ message }} - + diff --git a/templates/hacker-terminal.html b/templates/hacker-terminal.html index 74f50ef..821776e 100644 --- a/templates/hacker-terminal.html +++ b/templates/hacker-terminal.html @@ -6,7 +6,7 @@ {{ message }} - + diff --git a/templates/l7.html b/templates/l7.html index db57017..0c6767d 100644 --- a/templates/l7.html +++ b/templates/l7.html @@ -6,7 +6,7 @@ {{ message }} - + diff --git a/templates/lost-in-space.html b/templates/lost-in-space.html index a2d9c1c..a92cc0b 100644 --- a/templates/lost-in-space.html +++ b/templates/lost-in-space.html @@ -6,7 +6,7 @@ {{ message }} - + diff --git a/templates/shuffle.html b/templates/shuffle.html new file mode 100644 index 0000000..65f3674 --- /dev/null +++ b/templates/shuffle.html @@ -0,0 +1,267 @@ + + + + + + {{ code }} - {{ message }} + + + + + + + + + + + + + +
+
+
+

{{ code }}: {{ message }}

+

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + +