commit c1c1200227d8bbb44b44bdb7073635bacbea87dc Author: Fred Boniface Date: Thu Jul 24 20:44:28 2025 +0100 Init diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bf48c9e --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/main b/main new file mode 100755 index 0000000..0ccf078 Binary files /dev/null and b/main differ diff --git a/src/errorpage-server b/src/errorpage-server new file mode 100755 index 0000000..caf3f4d Binary files /dev/null and b/src/errorpage-server differ diff --git a/src/go.mod b/src/go.mod new file mode 100644 index 0000000..e53dffc --- /dev/null +++ b/src/go.mod @@ -0,0 +1,3 @@ +module git.fjla.uk/fred.boniface/errorpage-server + +go 1.24.5 diff --git a/src/main.go b/src/main.go new file mode 100644 index 0000000..693d25e --- /dev/null +++ b/src/main.go @@ -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.", + }, +} diff --git a/src/static/error.css b/src/static/error.css new file mode 100644 index 0000000..59448c2 --- /dev/null +++ b/src/static/error.css @@ -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; +} + diff --git a/src/templates/default.html b/src/templates/default.html new file mode 100644 index 0000000..0ef7bda --- /dev/null +++ b/src/templates/default.html @@ -0,0 +1,88 @@ + + + + + + Does not exist + + + +
+ +
+

Does not exist

+

+ No website exists at {{ .Hostname }}. Check the address and try again. +

+
+
+ + \ No newline at end of file diff --git a/src/templates/error.html b/src/templates/error.html new file mode 100644 index 0000000..b45c40a --- /dev/null +++ b/src/templates/error.html @@ -0,0 +1,90 @@ + + + + + + {{ .Title }} + + + +
+ +
+

{{ .Title }}

+

+ {{ .Description }} +

+ Return to Homepage +
+
+ + + diff --git a/src/templates/maintenance.html b/src/templates/maintenance.html new file mode 100644 index 0000000..3063bdf --- /dev/null +++ b/src/templates/maintenance.html @@ -0,0 +1,90 @@ + + + + + + Under Maintenance + + + + +
+ +
+

Under Maintenance

+

+ {{ .Hostname }} is under scheduled maintenance. Check back later. +

+
+
+ + +