package http import ( "bytes" "embed" "html/template" "io" "io/fs" "net" "net/http" "os" "path/filepath" _ "embed" "github.com/dschmidt/go-layerfs" "github.com/pkg/errors" ) //go:embed templates var embeddedTemplates embed.FS //go:embed assets var embeddedAssets embed.FS type Server struct { http *http.Server opts *Options templates template.Template } func (s *Server) serveHomepage(w http.ResponseWriter, r *http.Request) { s.renderTemplate(w, "index", s.opts.TemplateData) } func (s *Server) Serve(l net.Listener) error { templatesFilesystem, err := s.getCustomizedFilesystem(embeddedTemplates) if err != nil { return errors.WithStack(err) } if err := s.parseTemplates(templatesFilesystem); err != nil { return errors.WithStack(err) } assetsFilesystem, err := s.getCustomizedFilesystem(embeddedAssets) if err != nil { return errors.WithStack(err) } mux := http.NewServeMux() mux.Handle("/assets/", http.FileServer(http.FS(assetsFilesystem))) mux.HandleFunc("/", s.serveHomepage) httpServer := &http.Server{ Handler: mux, } s.http = httpServer if err := s.http.Serve(l); err != nil { return errors.WithStack(err) } return nil } func (s *Server) parseTemplates(fs fs.FS) error { templates, err := template.ParseFS(fs, "templates/*.html") if err != nil { return errors.WithStack(err) } s.templates = *templates return nil } func (s *Server) renderTemplate(w http.ResponseWriter, name string, data any) { var buff bytes.Buffer if err := s.templates.ExecuteTemplate(&buff, name, data); err != nil { s.log("[ERROR] %+s", errors.WithStack(err)) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if _, err := io.Copy(w, &buff); err != nil { s.log("[ERROR] %+s", errors.WithStack(err)) } } func (s *Server) log(message string, args ...any) { s.opts.Logger(message, args...) } func (s *Server) getCustomizedFilesystem(base fs.FS) (fs.FS, error) { filesystems := []fs.FS{} if s.opts.CustomDir != "" { absPath, err := filepath.Abs(s.opts.CustomDir) if err != nil { return nil, errors.WithStack(err) } filesystems = append(filesystems, os.DirFS(absPath)) } filesystems = append(filesystems, base) return layerfs.New(filesystems...), nil } func NewServer(funcs ...OptionFunc) *Server { opts := DefaultOptions() for _, fn := range funcs { fn(opts) } return &Server{ opts: opts, } }