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 }