This commit is contained in:
Fred Boniface 2025-07-24 20:44:28 +01:00
commit c1c1200227
10 changed files with 521 additions and 0 deletions

12
Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM golang:1.24.5-alpine AS builder
WORKDIR /app
COPY src/ ./
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o server
FROM scratch
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]

0
README.md Normal file
View File

BIN
main Executable file

Binary file not shown.

BIN
src/errorpage-server Executable file

Binary file not shown.

3
src/go.mod Normal file
View File

@ -0,0 +1,3 @@
module git.fjla.uk/fred.boniface/errorpage-server
go 1.24.5

168
src/main.go Normal file
View File

@ -0,0 +1,168 @@
package main
import (
"embed"
"fmt"
"html/template"
"log"
"net/http"
"regexp"
"strconv"
"strings"
)
//go:embed templates/*.html
var templatesFS embed.FS
//go:embed static/*
var staticFiles embed.FS
type ErrorPageData struct {
StatusCode int
Title string
Description string
}
type NonErrorPageData struct {
Hostname string
}
func main() {
tmpl, err := template.ParseFS(templatesFS, "templates/*.html")
if err != nil {
log.Fatalf("Failed to parse templates: %v", err)
}
http.HandleFunc("/", defaultHandler(tmpl))
http.HandleFunc("/error/", errorHandler(tmpl))
http.HandleFunc("/maintenance", maintenanceHandler(tmpl))
log.Println("Starting Server at :6660")
log.Fatal(http.ListenAndServe(":6660", nil))
}
func defaultHandler(tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
hostname := r.Host
data := NonErrorPageData{
Hostname: hostname,
}
w.WriteHeader(200)
if err := tmpl.ExecuteTemplate(w, "default.html", data); err != nil {
log.Printf("Template execution error: %v", err)
}
}
}
func maintenanceHandler(tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
hostname := r.Host
data := NonErrorPageData{
Hostname: hostname,
}
w.WriteHeader(200)
if err := tmpl.ExecuteTemplate(w, "maintenance.html", data); err != nil {
log.Printf("Template execution error: %v", err)
}
}
}
func errorHandler(tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
const prefix = "/error/"
const suffix = ".html"
codeStr := strings.TrimPrefix(path, prefix)
codeStr = strings.TrimSuffix(codeStr, suffix)
match, _ := regexp.MatchString(`^\d{3}$`, codeStr)
var statusCode int
if match {
code, err := strconv.Atoi(codeStr)
if err == nil {
statusCode = code
} else {
statusCode = 503
}
} else {
statusCode = 503
}
msg, ok := statusMessages[statusCode]
if !ok {
msg = struct {
Title string
Description string
}{
Title: fmt.Sprintf("Error %d", statusCode),
Description: "Something went wrong on our end — but we're not sure what. Try again later.",
}
}
data := ErrorPageData{
StatusCode: statusCode,
Title: msg.Title,
Description: msg.Description,
}
w.WriteHeader(data.StatusCode)
err := tmpl.ExecuteTemplate(w, "error.html", data)
if err != nil {
log.Printf("Template Execution Error: %v", err)
}
}
}
var statusMessages = map[int]struct {
Title string
Description string
}{
400: {
"Oops! Something's not quite right.",
"We couldn't understand your request. Maybe check the URL or try again?",
},
401: {
"Hold on — who goes there?",
"You need to be logged in to see this page.",
},
403: {
"Access Denied",
"Looks like you don't have permission to access this content.",
},
404: {
"Page Not Found",
"We looked everywhere but couldnt find that page. Double-check the link?",
},
500: {
"Something went wrong",
"The server ran into a problem. Try again in a bit — we're probably already fixing it!",
},
502: {
"Bad Gateway",
"We got a strange response from an upstream server. Not your fault!",
},
503: {
"Service Unavailable",
"The service is down for maintenance or having a hiccup. Please check back soon.",
},
504: {
"Gateway Timeout",
"Something took too long to respond. You can try refreshing in a moment.",
},
418: {
"I'm a teapot ☕",
"This server refuses to brew coffee because it is, in fact, a teapot.",
},
429: {
"Too Many Requests",
"Whoa there! Youre sending too many requests. Please slow down.",
},
}

70
src/static/error.css Normal file
View File

@ -0,0 +1,70 @@
/* Reset */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
background-color: #f5f7fa;
color: #2e3a59;
display: flex;
justify-content: center;
align-items: center;
padding: 1rem;
}
.error-container {
max-width: 360px;
text-align: center;
padding: 2rem;
background-color: #fff;
border: 1px solid #d1d9e6;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(46, 58, 89, 0.1);
}
.error-code {
font-size: 6rem;
font-weight: 700;
color: #6c757d;
margin-bottom: 1rem;
user-select: none;
}
.error-message h1 {
font-size: 1.75rem;
margin-bottom: 0.5rem;
font-weight: 600;
}
.error-message p {
font-size: 1rem;
margin-bottom: 1.5rem;
color: #495057;
line-height: 1.4;
}
.btn {
display: inline-block;
padding: 0.6rem 1.6rem;
font-weight: 600;
font-size: 1rem;
color: #fff;
background: linear-gradient(90deg, #4a90e2, #357ABD);
border-radius: 8px;
text-decoration: none;
transition: background 0.3s ease;
box-shadow: 0 4px 12px rgba(53, 122, 189, 0.4);
}
.btn:hover,
.btn:focus {
background: linear-gradient(90deg, #357ABD, #2c5a9c);
box-shadow: 0 6px 20px rgba(44, 90, 156, 0.6);
outline: none;
}

View File

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Does not exist</title>
<style>/* Reset */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
background-color: #f5f7fa;
color: #2e3a59;
display: flex;
justify-content: center;
align-items: center;
padding: 1rem;
}
.error-container {
max-width: 360px;
text-align: center;
padding: 2rem;
background-color: #fff;
border: 1px solid #d1d9e6;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(46, 58, 89, 0.1);
}
.error-code {
font-size: 6rem;
font-weight: 700;
color: #6c757d;
margin-bottom: 1rem;
user-select: none;
}
.error-message h1 {
font-size: 1.75rem;
margin-bottom: 0.5rem;
font-weight: 600;
}
.error-message p {
font-size: 1rem;
margin-bottom: 1.5rem;
color: #495057;
line-height: 1.4;
}
.btn {
display: inline-block;
padding: 0.6rem 1.6rem;
font-weight: 600;
font-size: 1rem;
color: #fff;
background: linear-gradient(90deg, #4a90e2, #357ABD);
border-radius: 8px;
text-decoration: none;
transition: background 0.3s ease;
box-shadow: 0 4px 12px rgba(53, 122, 189, 0.4);
}
.btn:hover,
.btn:focus {
background: linear-gradient(90deg, #357ABD, #2c5a9c);
box-shadow: 0 6px 20px rgba(44, 90, 156, 0.6);
outline: none;
}</style>
</head>
<body>
<main class="error-container" role="main" aria-labelledby="error-title" aria-describedby="error-desc">
<div class="error-code" aria-hidden="true">🌐</div>
<div class="error-message">
<h1 id="error-title">Does not exist</h1>
<p id="error-desc">
No website exists at {{ .Hostname }}. Check the address and try again.
</p>
</div>
</main>
</body>
</html>

90
src/templates/error.html Normal file
View File

@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ .Title }}</title>
<style>/* Reset */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
background-color: #f5f7fa;
color: #2e3a59;
display: flex;
justify-content: center;
align-items: center;
padding: 1rem;
}
.error-container {
max-width: 360px;
text-align: center;
padding: 2rem;
background-color: #fff;
border: 1px solid #d1d9e6;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(46, 58, 89, 0.1);
}
.error-code {
font-size: 6rem;
font-weight: 700;
color: #6c757d;
margin-bottom: 1rem;
user-select: none;
}
.error-message h1 {
font-size: 1.75rem;
margin-bottom: 0.5rem;
font-weight: 600;
}
.error-message p {
font-size: 1rem;
margin-bottom: 1.5rem;
color: #495057;
line-height: 1.4;
}
.btn {
display: inline-block;
padding: 0.6rem 1.6rem;
font-weight: 600;
font-size: 1rem;
color: #fff;
background: linear-gradient(90deg, #4a90e2, #357ABD);
border-radius: 8px;
text-decoration: none;
transition: background 0.3s ease;
box-shadow: 0 4px 12px rgba(53, 122, 189, 0.4);
}
.btn:hover,
.btn:focus {
background: linear-gradient(90deg, #357ABD, #2c5a9c);
box-shadow: 0 6px 20px rgba(44, 90, 156, 0.6);
outline: none;
}</style>
</head>
<body>
<main class="error-container" role="main" aria-labelledby="error-title" aria-describedby="error-desc">
<div class="error-code" aria-hidden="true">{{ .StatusCode }}</div>
<div class="error-message">
<h1 id="error-title">{{ .Title }}</h1>
<p id="error-desc">
{{ .Description }}
</p>
<a href="/" class="btn">Return to Homepage</a>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Under Maintenance</title>
<link rel="stylesheet" href="static/error.css" />
<style>/* Reset */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
background-color: #f5f7fa;
color: #2e3a59;
display: flex;
justify-content: center;
align-items: center;
padding: 1rem;
}
.error-container {
max-width: 360px;
text-align: center;
padding: 2rem;
background-color: #fff;
border: 1px solid #d1d9e6;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(46, 58, 89, 0.1);
}
.error-code {
font-size: 6rem;
font-weight: 700;
color: #6c757d;
margin-bottom: 1rem;
user-select: none;
}
.error-message h1 {
font-size: 1.75rem;
margin-bottom: 0.5rem;
font-weight: 600;
}
.error-message p {
font-size: 1rem;
margin-bottom: 1.5rem;
color: #495057;
line-height: 1.4;
}
.btn {
display: inline-block;
padding: 0.6rem 1.6rem;
font-weight: 600;
font-size: 1rem;
color: #fff;
background: linear-gradient(90deg, #4a90e2, #357ABD);
border-radius: 8px;
text-decoration: none;
transition: background 0.3s ease;
box-shadow: 0 4px 12px rgba(53, 122, 189, 0.4);
}
.btn:hover,
.btn:focus {
background: linear-gradient(90deg, #357ABD, #2c5a9c);
box-shadow: 0 6px 20px rgba(44, 90, 156, 0.6);
outline: none;
}</style>
</head>
<body>
<main class="error-container" role="main" aria-labelledby="error-title" aria-describedby="error-desc">
<div class="error-code" aria-hidden="true">🛠️</div>
<div class="error-message">
<h1 id="error-title">Under Maintenance</h1>
<p id="error-desc">
{{ .Hostname }} is under scheduled maintenance. Check back later.
</p>
</div>
</main>
</body>
</html>