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) {
data := struct {
Title string
}{
Title: "Rebound",
}
s.renderTemplate(w, "index", data)
}
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,
}
}