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
 | 
						|
}
 |