265 lines
5.9 KiB
Go
265 lines
5.9 KiB
Go
package snap
|
|
|
|
import (
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
"git.thirdmartini.com/pub/fancylog"
|
|
"git.thirdmartini.com/pub/snap/auth"
|
|
)
|
|
|
|
type Server struct {
|
|
address string
|
|
path string
|
|
theme string
|
|
debug bool
|
|
auth auth.AuthManager
|
|
router *mux.Router
|
|
templates string
|
|
cachedTmpl *template.Template
|
|
}
|
|
|
|
type SnapBaseContent struct {
|
|
Theme string
|
|
Content interface{}
|
|
}
|
|
|
|
func (s *Server) makeContext(auth *auth.AuthData, w http.ResponseWriter, r *http.Request) *Context {
|
|
c := &Context{
|
|
r: r,
|
|
w: w,
|
|
srv: s,
|
|
auth: auth,
|
|
vars: mux.Vars(r),
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (s *Server) authenticated(auth auth.AuthManager, handle func(c *Context)) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
log.Debug("authenticated request: ", r.RequestURI)
|
|
|
|
rec, ok := auth.DoAuth(w, r)
|
|
|
|
if !ok {
|
|
http.Error(w, "Not authorized", 401)
|
|
} else {
|
|
c := s.makeContext(rec, w, r)
|
|
handle(c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) wrapper(handle func(c *Context)) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
log.Debug("request: ", r.RequestURI)
|
|
c := s.makeContext(nil, w, r)
|
|
handle(c)
|
|
|
|
// discard the rest of the body content
|
|
io.Copy(ioutil.Discard, r.Body)
|
|
defer r.Body.Close()
|
|
}
|
|
}
|
|
|
|
// This is a bit different then the standard template.parseFiles code in that it gives us hiarchial templates
|
|
// header.html
|
|
// mydirectory/service.html ...
|
|
func (s *Server) parseTemplates(t *template.Template, filenames ...string) (*template.Template, error) {
|
|
//if err := t.checkCanParse(); err != nil {
|
|
// return nil, err
|
|
//}
|
|
|
|
if len(filenames) == 0 {
|
|
// Not really a problem, but be consistent.
|
|
return nil, fmt.Errorf("html/template: no files named in call to ParseFiles")
|
|
}
|
|
for _, filename := range filenames {
|
|
b, err := ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data := string(b)
|
|
name := strings.TrimPrefix(filename, s.templates+"/")
|
|
// log.Println("Template:", name)
|
|
|
|
// First template becomes return value if not already defined,
|
|
// and we use that one for subsequent New calls to associate
|
|
// all the templates together. Also, if this file has the same name
|
|
// as t, this file becomes the contents of t, so
|
|
// t, err := New(name).Funcs(xxx).ParseFiles(name)
|
|
// works. Otherwise we create a new template associated with t.
|
|
var tmpl *template.Template
|
|
if t == nil {
|
|
t = template.New(name)
|
|
}
|
|
if name == t.Name() {
|
|
tmpl = t
|
|
} else {
|
|
tmpl = t.New(name)
|
|
}
|
|
_, err = tmpl.Parse(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
func (s *Server) loadTemplates() *template.Template {
|
|
tmpl := template.New("")
|
|
|
|
err := filepath.Walk(path.Join(s.path, s.templates), func(path string, info os.FileInfo, err error) error {
|
|
if strings.Contains(path, ".html") {
|
|
_, err := s.parseTemplates(tmpl, path)
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
/*
|
|
_, err = tmpl.ParseFiles(path)
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
*/
|
|
}
|
|
return err
|
|
})
|
|
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
return tmpl
|
|
}
|
|
|
|
func (s *Server) getTemplates() *template.Template {
|
|
if s.debug {
|
|
return s.loadTemplates()
|
|
}
|
|
|
|
if s.cachedTmpl == nil {
|
|
s.cachedTmpl = s.loadTemplates()
|
|
}
|
|
return s.cachedTmpl
|
|
}
|
|
|
|
func (s *Server) render(w http.ResponseWriter, tmpl string, content interface{}) {
|
|
err := s.getTemplates().ExecuteTemplate(w, tmpl, content)
|
|
if err != nil {
|
|
log.Warn(err)
|
|
}
|
|
}
|
|
|
|
func (s *Server) reply(w http.ResponseWriter, msg string) {
|
|
w.Write([]byte(msg))
|
|
}
|
|
|
|
func (s *Server) renderError(w http.ResponseWriter, code int, msg string) {
|
|
w.WriteHeader(code)
|
|
w.Write([]byte(msg))
|
|
}
|
|
|
|
func (s *Server) HandleFuncAuthenticated(path string, f func(c *Context)) *mux.Route {
|
|
if s.auth == nil {
|
|
return nil
|
|
}
|
|
|
|
return s.router.HandleFunc(path, s.authenticated(s.auth, f))
|
|
}
|
|
|
|
func (s *Server) HandleFuncCustomAuth(auth auth.AuthManager, path string, f func(c *Context)) *mux.Route {
|
|
if auth == nil {
|
|
log.Warn("Nil auth on", path)
|
|
return nil
|
|
}
|
|
return s.router.HandleFunc(path, s.authenticated(auth, f))
|
|
}
|
|
|
|
func (s *Server) HandleFunc(path string, f func(c *Context)) *mux.Route {
|
|
return s.router.HandleFunc(path, s.wrapper(f))
|
|
}
|
|
|
|
func (s *Server) SetDebug(enable bool) {
|
|
s.debug = enable
|
|
}
|
|
|
|
func (s *Server) EnableStatus(path string) {
|
|
}
|
|
|
|
func (s *Server) SetTheme(theme string) {
|
|
s.theme = theme
|
|
}
|
|
|
|
func (s *Server) SetTemplatePath(path string) {
|
|
s.templates = path
|
|
}
|
|
|
|
func (s *Server) Router() *mux.Router {
|
|
return s.router
|
|
}
|
|
|
|
func (s *Server) ServeTLS(keyPath string, certPath string) error {
|
|
srv := &http.Server{
|
|
Handler: s.router,
|
|
Addr: s.address,
|
|
// Good practice: enforce timeouts for servers you create!
|
|
WriteTimeout: 120 * time.Second,
|
|
ReadTimeout: 120 * time.Second,
|
|
}
|
|
|
|
return srv.ListenAndServeTLS(certPath, keyPath)
|
|
}
|
|
|
|
// Serve serve content forever
|
|
func (s *Server) Serve() error {
|
|
srv := &http.Server{
|
|
Handler: s.router,
|
|
Addr: s.address,
|
|
// Good practice: enforce timeouts for servers you create!
|
|
WriteTimeout: 120 * time.Second,
|
|
ReadTimeout: 120 * time.Second,
|
|
}
|
|
|
|
return srv.ListenAndServe()
|
|
}
|
|
|
|
func (s *Server) WithStaticFiles(prefix string, path string) *Server {
|
|
s.router.PathPrefix(prefix).Handler(http.StripPrefix(prefix, http.FileServer(http.Dir(path))))
|
|
return s
|
|
}
|
|
|
|
func (s *Server) WithTheme(themeURL string) *Server {
|
|
s.theme = themeURL
|
|
return s
|
|
}
|
|
|
|
func (s *Server) WithDebug(debugURL string) *Server {
|
|
sub := s.router.PathPrefix("/sovereign/debug").Subrouter()
|
|
setupDebugHandler(sub)
|
|
return s
|
|
}
|
|
|
|
func New(address string, path string, auth auth.AuthManager) *Server {
|
|
s := Server{
|
|
router: mux.NewRouter(),
|
|
auth: auth,
|
|
address: address,
|
|
path: path,
|
|
templates: "templates",
|
|
theme: "/static/css/default.css",
|
|
}
|
|
|
|
//s.router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(s.path+"static/"))))
|
|
return &s
|
|
}
|