2024-07-11 14:12:22 -05:00

216 lines
5.5 KiB
Go

package main
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"html/template"
"log/slog"
"net/http"
"os"
"os/signal"
"regexp"
"strconv"
"strings"
"syscall"
"time"
"example.com/go-performance/assets"
"example.com/go-performance/internal/config"
)
const (
cTcss string = "text/css"
cTxpem string = "application/x-pem-file"
cTpdf string = "application/pdf"
cTmpeg string = "audio/mpeg"
cTwoff string = "font/woff"
cTwoff2 string = "font/woff2"
cTpng string = "image/png"
cTjpeg string = "image/jpg"
cTjs string = "text/javascript"
cTjson string = "application/json"
cTplain string = "text/plain"
cTraw string = "text/raw"
cThtml string = "text/html"
)
type webErrStruct struct {
Error bool `json:"error" yaml:"error"`
ErrorMsg string `json:"error_message" yaml:"errorMessage"`
}
var (
validFiles map[string]string = map[string]string{
"/images/game.png": cTpng,
}
cfg config.Config
)
func forever(log *slog.Logger) {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
sig := <-c
log.Warn("Shutting down, triggered by signal", "signal", sig)
}
func main() {
// initialize all parameters
cfg = config.Init()
// configure shutdown sequence
defer func() {
cfg.Log.Info("Shutdown sequence complete")
}()
// start webserver
go httpServer(cfg.Log)
forever(cfg.Log)
}
func httpServer(log *slog.Logger) {
path := http.NewServeMux()
connection := &http.Server{
Addr: cfg.WebServerIP + ":" + strconv.FormatInt(int64(cfg.WebServerPort), 10),
Handler: path,
ReadTimeout: time.Duration(cfg.WebServerReadTimeout) * time.Second,
WriteTimeout: time.Duration(cfg.WebServerWriteTimeout) * time.Second,
IdleTimeout: time.Duration(cfg.WebServerIdleTimeout) * time.Second,
}
path.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { webRoot(log, w, r) })
if err := connection.ListenAndServe(); err != nil {
log.Error("Unable to create webserver", "function", "httpServer", "error", err)
panic(err)
}
}
func isValidReq(file string) (string, error) {
for f, t := range validFiles {
if file == f {
return t, nil
}
}
return "", fmt.Errorf("Invalid file requested: %s", file)
}
func webRoot(log *slog.Logger, w http.ResponseWriter, r *http.Request) {
httpAccessLog(log, r)
if strings.ToLower(r.Method) != "get" {
log.Debug("Request made using the wrong method", "function", "webRoot", "url", r.URL.Path, "expected-method", "GET", "requested-method", r.Method)
tmpltError(log, w, http.StatusBadRequest, "Invalid http method.")
return
}
if r.URL.Path == "/" {
tmpltWebRoot(log, w)
return
}
cType, err := isValidReq(r.URL.Path)
if err != nil {
log.Debug("Request not found", "function", "webRoot", "url", r.URL.Path)
tmpltStatusNotFound(log, w, r.URL.Path)
return
}
w.Header().Add("Content-Type", cType)
o, err := assets.EmbedData.ReadFile("html" + r.URL.Path)
if err != nil {
log.Error("Unable to read local embedded file", "function", "webRoot", "error", err)
tmpltError(log, w, http.StatusInternalServerError, "Server unable to retrieve file data.")
return
}
if regexp.MustCompile(`gzip`).Match([]byte(r.Header.Get("Accept-Encoding"))) {
w.Header().Add("Content-Encoding", "gzip")
gw := gzip.NewWriter(w)
defer gw.Close()
gw.Write(o) //nolint: errcheck
} else {
w.Write(o) //nolint: errcheck
}
}
func httpAccessLog(log *slog.Logger, req *http.Request) {
log.Debug("AccessLog", "method", req.Method, "remote-addr", req.RemoteAddr, "uri", req.RequestURI)
}
func tmpltWebRoot(log *slog.Logger, w http.ResponseWriter) {
tmplt, err := template.ParseFS(
assets.EmbedData,
"html/index.tplt",
)
if err != nil {
log.Debug("Unable to parse HTML template", "function", "tmpltWebRoot", "error", err)
tmpltError(log, w, http.StatusInternalServerError, "Template Parse Error.")
return
}
var msgBuffer bytes.Buffer
if err := tmplt.Execute(&msgBuffer, struct {
Time string
Title string
Version string
}{
Time: time.Now().In(cfg.TZLocal).Format(cfg.TimeFormat),
Title: "Performance Test",
Version: "v1.0.0",
}); err != nil {
log.Debug("Unable to execute HTML template", "function", "tmpltWebRoot", "error", err)
tmpltError(log, w, http.StatusInternalServerError, "Template Parse Error.")
return
}
w.Write(msgBuffer.Bytes()) //nolint: errcheck
}
func tmpltError(log *slog.Logger, w http.ResponseWriter, serverStatus int, message string) {
var (
output []byte
o = webErrStruct{
Error: true,
ErrorMsg: message,
}
err error
)
w.Header().Add("Content-Type", "application/json")
output, err = json.MarshalIndent(o, "", " ")
if err != nil {
log.Warn("Unable to marshal error", "function", "tmpltError", "error", err)
w.WriteHeader(serverStatus)
w.Write(output) //nolint:errcheck
}
}
func tmpltStatusNotFound(log *slog.Logger, w http.ResponseWriter, path string) {
tmplt, err := template.ParseFS(assets.EmbedData, "html/file-not-found.tplt")
if err != nil {
log.Debug("Unable to parse HTML template", "function", "tmpltStatusNotFound", "error", err)
tmpltError(log, w, http.StatusInternalServerError, "Template Parse Error.")
return
}
var msgBuffer bytes.Buffer
if err := tmplt.Execute(&msgBuffer, struct {
Title string
ErrorCode int
}{
Title: path,
ErrorCode: http.StatusNotFound,
}); err != nil {
log.Debug("Unable to execute HTML template", "function", "tmpltStatusNotFound", "error", err)
tmpltError(log, w, http.StatusInternalServerError, "Template Parse Error.")
return
}
w.Write(msgBuffer.Bytes()) //nolint: errcheck
}