Init
This commit is contained in:
commit
c1c1200227
12
Dockerfile
Normal file
12
Dockerfile
Normal 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"]
|
BIN
src/errorpage-server
Executable file
BIN
src/errorpage-server
Executable file
Binary file not shown.
3
src/go.mod
Normal file
3
src/go.mod
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module git.fjla.uk/fred.boniface/errorpage-server
|
||||||
|
|
||||||
|
go 1.24.5
|
168
src/main.go
Normal file
168
src/main.go
Normal 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 couldn’t 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! You’re sending too many requests. Please slow down.",
|
||||||
|
},
|
||||||
|
}
|
70
src/static/error.css
Normal file
70
src/static/error.css
Normal 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;
|
||||||
|
}
|
||||||
|
|
88
src/templates/default.html
Normal file
88
src/templates/default.html
Normal 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
90
src/templates/error.html
Normal 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>
|
||||||
|
|
90
src/templates/maintenance.html
Normal file
90
src/templates/maintenance.html
Normal 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>
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user