216 lines
5.5 KiB
Go
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
|
|
}
|