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