package snap import ( "fmt" "html/template" "io" "io/ioutil" "net/http" "os" "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(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 }