package app import ( "embed" "fmt" "html/template" "log" "net/http" "path" "path/filepath" "strings" ) //go:embed embeded/tmpl/*.html var tmplFS embed.FS //go:embed embeded/static/* var staticFS embed.FS const tmplEmbedPath = "embeded/tmpl" const staticEmbedPath = "embeded/static" const redirectStatusCode = 301 var resourceHandlers = map[string]func(http.ResponseWriter, *http.Request, string){ "strat": httpHandleStrategyBoardAlias, "markdown": httpHandleMarkdown, "serve": httpHandleFile, "redirect": httpHandleRedirect, } var baseHandlers = []func(http.ResponseWriter, *http.Request, []string) bool{ httpBaseHandleUser, httpBaseHandleGCSS, httpBaseHandleForm, httpBaseHandleAPI, httpBaseHandleCharacter, httpBaseHandleStrategyBoard, httpBaseHandleDocument, httpBaseHandleResourceAliasAlias, httpBaseHandleStatic, } func httpHandleErrorText(w http.ResponseWriter, err error) { w.Header().Add("Content-Type", "text/plain") statusCode := getErrorHttpStatusCode(err) log.Printf(" >> %d %s", statusCode, err.Error()) w.WriteHeader(statusCode) fmt.Fprintf(w, "%d %s", statusCode, http.StatusText(statusCode)) } func httpHandleError(w http.ResponseWriter, err error) { statusCode := getErrorHttpStatusCode(err) t, perr := template.ParseFS(tmplFS, path.Join(tmplEmbedPath, "_*.html"), path.Join(tmplEmbedPath, "error.html")) if perr != nil { log.Printf("WARNING: %s", perr.Error()) httpHandleErrorText(w, err) return } log.Printf(" >> %d %s", statusCode, err.Error()) w.Header().Add("Content-Type", "text/html") w.WriteHeader(statusCode) if err := t.ExecuteTemplate(w, "error.html", map[string]any{ "statusCode": statusCode, "statusText": http.StatusText(statusCode), "error": getErrorHttpMessage(err), }); err != nil { statusCode = getErrorHttpStatusCode(err) fmt.Fprintf(w, "%d %s", statusCode, http.StatusText(statusCode)) return } } func httpHandleRedirect(w http.ResponseWriter, r *http.Request, redirect string) { log.Printf(" >> %d redirect to %s", redirectStatusCode, redirect) w.Header().Add("Location", redirect) w.WriteHeader(redirectStatusCode) } func httpHandleFile(w http.ResponseWriter, r *http.Request, file string) { log.Printf(" >> serve file %s", file) ext := filepath.Ext(file) mime := "text/plain" switch ext { case ".png": mime = "image/png" w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") case ".html": mime = "text/html" case ".gcss": httpBaseHandleGCSS(w, r, []string{file}) return } w.Header().Set("Content-Type", mime) http.ServeFileFS(w, r, staticFS, strings.ToLower(filepath.Join(staticEmbedPath, file))) } func httpHandleTemplate(w http.ResponseWriter, name string, data map[string]any) { name = strings.TrimSuffix(name, ".html") log.Printf(" >> serve template %s", name) w.Header().Add("Content-Type", "text/html") t, err := template.ParseFS(tmplFS, path.Join(tmplEmbedPath, "_*.html"), path.Join(tmplEmbedPath, name+".html")) if err != nil { httpHandleError(w, err) return } if err := t.ExecuteTemplate(w, name+".html", data); err != nil { statusCode := getErrorHttpStatusCode(err) log.Printf(" >> %d %s", statusCode, err.Error()) fmt.Fprintf(w, "%d %s", statusCode, http.StatusText(statusCode)) return } } func httpHandleStrategyBoardAlias(w http.ResponseWriter, r *http.Request, resource string) { httpBaseHandleStrategyBoard(w, r, []string{"b", resource}) } func httpBaseHandleResourceAliasAlias(w http.ResponseWriter, r *http.Request, path []string) bool { // fetch resource alias from resources.yaml alias := path[len(path)-1] resource := resourceList[alias] // fetch resource alias from database if input not found in yaml if resource == "" { resourceRecord, err := fetchResourceRecordByAlias(db, alias) if err != nil { return false } resource = resourceRecord.Data } // find and execute handler function handlerName := strings.Split(resource, ":")[0] handlerFunc := resourceHandlers[handlerName] if handlerFunc != nil { log.Printf(" >> handle '%s'", resource) handlerFunc(w, r, stripHandlerAlias(resource)) return true } // assume redirect otherwise httpHandleRedirect(w, r, stripHandlerAlias(resource)) return true } func httpBaseHandleStatic(w http.ResponseWriter, r *http.Request, path []string) bool { if path[0] != "static" { return false } httpHandleFile(w, r, filepath.Join(path[1:]...)) return true } func httpHandleMain(w http.ResponseWriter, r *http.Request) { clientID := getUserClientID(r) path := strings.Split(strings.Trim(r.URL.Path, "/"), "/") log.Printf("[%s] %s %s", clientID[:4], r.Method, r.URL.Path) // iterate handlers until one compatible with current path is found for _, handler := range baseHandlers { if handler(w, r, path) { return } } // no handlers httpHandleError(w, errHttpNotFound) }