violet/error-pages/error-pages.go

150 lines
3.6 KiB
Go
Raw Normal View History

2023-04-22 18:11:21 +01:00
package error_pages
import (
"fmt"
2024-05-13 19:33:33 +01:00
"github.com/1f349/violet/logger"
2024-04-20 16:17:32 +01:00
"github.com/mrmelon54/rescheduler"
2023-04-22 22:18:39 +01:00
"io/fs"
2023-04-22 18:11:21 +01:00
"net/http"
2023-04-22 22:18:39 +01:00
"path/filepath"
"strconv"
"strings"
2023-04-22 18:11:21 +01:00
"sync"
)
2024-05-13 19:33:33 +01:00
var Logger = logger.Logger.WithPrefix("Violet Error Pages")
2023-04-22 18:11:21 +01:00
// ErrorPages stores the custom error pages and is called by the servers to
// output meaningful pages for HTTP error codes
type ErrorPages struct {
s *sync.RWMutex
m map[int]func(rw http.ResponseWriter)
generic func(rw http.ResponseWriter, code int)
2023-04-22 22:18:39 +01:00
dir fs.FS
r *rescheduler.Rescheduler
2023-04-22 18:11:21 +01:00
}
2023-04-24 01:35:23 +01:00
// New creates a new error pages generator
2023-04-22 22:18:39 +01:00
func New(dir fs.FS) *ErrorPages {
e := &ErrorPages{
2023-04-22 18:11:21 +01:00
s: &sync.RWMutex{},
m: make(map[int]func(rw http.ResponseWriter)),
2023-04-24 01:35:23 +01:00
// generic error page writer
2023-04-22 18:11:21 +01:00
generic: func(rw http.ResponseWriter, code int) {
2023-04-24 01:35:23 +01:00
// if status text is empty then the code is unknown
2023-04-22 18:11:21 +01:00
a := http.StatusText(code)
2023-06-03 19:33:06 +01:00
fmt.Printf("%d - %s\n", code, a)
2023-04-22 18:11:21 +01:00
if a != "" {
2023-04-24 01:35:23 +01:00
// output in "xxx Error Text" format
2023-04-22 18:11:21 +01:00
http.Error(rw, fmt.Sprintf("%d %s\n", code, a), code)
return
}
2023-04-24 01:35:23 +01:00
// output the code and generic unknown message
2023-04-22 18:11:21 +01:00
http.Error(rw, fmt.Sprintf("%d Unknown Error Code\n", code), code)
},
dir: dir,
}
e.r = rescheduler.NewRescheduler(e.threadCompile)
return e
2023-04-22 18:11:21 +01:00
}
2023-04-24 01:35:23 +01:00
// ServeError writes the error page for the given code to the response writer
2023-04-22 18:11:21 +01:00
func (e *ErrorPages) ServeError(rw http.ResponseWriter, code int) {
2023-04-24 01:35:23 +01:00
// read lock for safety
2023-04-22 18:11:21 +01:00
e.s.RLock()
defer e.s.RUnlock()
2023-04-24 01:35:23 +01:00
// use the custom error page if it exists
2023-04-22 18:11:21 +01:00
if p, ok := e.m[code]; ok {
p(rw)
return
}
2023-04-24 01:35:23 +01:00
// otherwise use the generic error page
2023-04-22 18:11:21 +01:00
e.generic(rw, code)
}
2023-04-22 22:18:39 +01:00
// Compile loads the error pages the certificates and keys from the directories.
//
// This method makes use of the rescheduler instead of just ignoring multiple
// calls.
2023-04-22 22:18:39 +01:00
func (e *ErrorPages) Compile() {
e.r.Run()
}
2023-04-22 22:18:39 +01:00
func (e *ErrorPages) threadCompile() {
// new map
errorPageMap := make(map[int]func(rw http.ResponseWriter))
// compile map and check errors
if e.dir != nil {
err := e.internalCompile(errorPageMap)
if err != nil {
2024-05-13 19:33:33 +01:00
Logger.Info("Compile failed", "err", err)
return
2023-04-22 22:18:39 +01:00
}
}
2023-04-24 01:35:23 +01:00
// lock while replacing the map
e.s.Lock()
e.m = errorPageMap
e.s.Unlock()
2023-04-22 22:18:39 +01:00
}
func (e *ErrorPages) internalCompile(m map[int]func(rw http.ResponseWriter)) error {
// try to read dir
2023-06-04 22:28:48 +01:00
files, err := fs.ReadDir(e.dir, ".")
2023-04-22 22:18:39 +01:00
if err != nil {
return fmt.Errorf("failed to read error pages dir: %w", err)
}
2024-05-13 19:33:33 +01:00
Logger.Info("Compiling lookup table", "page count", len(files))
2023-04-22 22:18:39 +01:00
// find and load error pages
for _, i := range files {
// skip dirs
if i.IsDir() {
continue
}
// get file name and extension
name := i.Name()
ext := filepath.Ext(name)
// if the extension is not 'html' then ignore the file
2023-06-04 22:28:48 +01:00
if ext != ".html" {
2024-05-13 19:33:33 +01:00
Logger.Warn("Ignoring non '.html' file in error pages directory", "name", name)
continue
}
// if the name can't be
nameInt, err := strconv.Atoi(strings.TrimSuffix(name, ".html"))
if err != nil {
2024-05-13 19:33:33 +01:00
Logger.Warn("Ignoring invalid error page in error pages directory", "name", name)
continue
}
2023-04-24 01:35:23 +01:00
// check if code is in range 100-599
if nameInt < 100 || nameInt >= 600 {
2024-05-13 19:33:33 +01:00
Logger.Warn("Ignoring invalid error page in error pages directory must be 100-599", "name", name)
2023-04-22 22:18:39 +01:00
continue
}
// try to read html file
htmlData, err := fs.ReadFile(e.dir, name)
if err != nil {
return fmt.Errorf("failed to read html file '%s': %w", name, err)
}
2023-04-24 01:35:23 +01:00
// create a callback function to write the page
m[nameInt] = func(rw http.ResponseWriter) {
rw.Header().Set("Content-Type", "text/html; encoding=utf-8")
rw.WriteHeader(nameInt)
_, _ = rw.Write(htmlData)
}
2023-04-22 22:18:39 +01:00
}
// well no errors happened
return nil
}