diff --git a/cmd/koushin/main.go b/cmd/koushin/main.go index a0382dd..eae340e 100644 --- a/cmd/koushin/main.go +++ b/cmd/koushin/main.go @@ -48,7 +48,6 @@ func main() { signal.Notify(sigs, syscall.SIGUSR1) go func() { for range sigs { - e.Logger.Printf("Reloading server") if err := s.Reload(); err != nil { e.Logger.Errorf("Failed to reload server: %v", err) } diff --git a/server.go b/server.go index b20b314..71a891b 100644 --- a/server.go +++ b/server.go @@ -5,6 +5,7 @@ import ( "net/http" "net/url" "strings" + "sync" "time" "github.com/labstack/echo/v4" @@ -14,21 +15,23 @@ const cookieName = "koushin_session" // Server holds all the koushin server state. type Server struct { - renderer *renderer + e *echo.Echo Sessions *SessionManager - Plugins []Plugin + + mutex sync.RWMutex // used for server reload + plugins []Plugin imap struct { host string tls bool insecure bool } - smtp struct { host string tls bool insecure bool } + defaultTheme string } func (s *Server) parseIMAPURL(imapURL string) error { @@ -73,19 +76,53 @@ func (s *Server) parseSMTPURL(smtpURL string) error { return nil } +func (s *Server) load() error { + plugins := append([]Plugin(nil), plugins...) + for _, p := range plugins { + s.e.Logger.Printf("Registered plugin '%v'", p.Name()) + } + + luaPlugins, err := loadAllLuaPlugins(s.e.Logger) + if err != nil { + return fmt.Errorf("failed to load plugins: %v", err) + } + plugins = append(plugins, luaPlugins...) + + renderer := newRenderer(s.e.Logger, s.defaultTheme) + if err := renderer.Load(plugins); err != nil { + return fmt.Errorf("failed to load templates: %v", err) + } + + // Once we've loaded plugins and templates from disk (which can take time), + // swap them in the Server struct + s.mutex.Lock() + defer s.mutex.Unlock() + + s.plugins = plugins + s.e.Renderer = renderer + + for _, p := range plugins { + p.SetRoutes(s.e.Group("")) + } + + return nil +} + +// Reload loads Lua plugins and templates from disk. func (s *Server) Reload() error { - return s.renderer.reload(s.Plugins) + s.e.Logger.Printf("Reloading server") + return s.load() } -func newServer(imapURL, smtpURL string) (*Server, error) { - s := &Server{} +func newServer(e *echo.Echo, options *Options) (*Server, error) { + s := &Server{e: e, defaultTheme: options.Theme} - if err := s.parseIMAPURL(imapURL); err != nil { + if err := s.parseIMAPURL(options.IMAPURL); err != nil { return nil, err } - if smtpURL != "" { - if err := s.parseSMTPURL(smtpURL); err != nil { + if options.SMTPURL != "" { + if err := s.parseSMTPURL(options.SMTPURL); err != nil { return nil, err } } @@ -139,26 +176,13 @@ type Options struct { // New creates a new server. func New(e *echo.Echo, options *Options) (*Server, error) { - s, err := newServer(options.IMAPURL, options.SMTPURL) + s, err := newServer(e, options) if err != nil { return nil, err } - s.Plugins = append([]Plugin(nil), plugins...) - for _, p := range s.Plugins { - e.Logger.Printf("Registered plugin '%v'", p.Name()) - } - - luaPlugins, err := loadAllLuaPlugins(e.Logger) - if err != nil { - return nil, fmt.Errorf("failed to load plugins: %v", err) - } - s.Plugins = append(s.Plugins, luaPlugins...) - - s.renderer = newRenderer(e.Logger, options.Theme) - e.Renderer = s.renderer - if err := s.renderer.reload(s.Plugins); err != nil { - return nil, fmt.Errorf("failed to load templates: %v", err) + if err := s.load(); err != nil { + return nil, err } e.HTTPErrorHandler = func(err error, c echo.Context) { @@ -172,6 +196,15 @@ func New(e *echo.Echo, options *Options) (*Server, error) { c.String(code, err.Error()) } + e.Pre(func(next echo.HandlerFunc) echo.HandlerFunc { + return func(ectx echo.Context) error { + s.mutex.RLock() + err := next(ectx) + s.mutex.RUnlock() + return err + } + }) + e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { return func(ectx echo.Context) error { ectx.Response().Header().Set("Content-Security-Policy", "default-src 'self'") @@ -211,9 +244,5 @@ func New(e *echo.Echo, options *Options) (*Server, error) { e.Static("/themes", "themes") - for _, p := range s.Plugins { - p.SetRoutes(e.Group("")) - } - return s, nil } diff --git a/template.go b/template.go index 3502a3b..c8c3d6a 100644 --- a/template.go +++ b/template.go @@ -6,7 +6,6 @@ import ( "io" "io/ioutil" "os" - "sync" "github.com/labstack/echo/v4" ) @@ -81,19 +80,15 @@ type renderer struct { logger echo.Logger defaultTheme string - mutex sync.RWMutex base *template.Template themes map[string]*template.Template } func (r *renderer) Render(w io.Writer, name string, data interface{}, ectx echo.Context) error { - r.mutex.RLock() - defer r.mutex.RUnlock() - // ectx is the raw *echo.context, not our own *Context ctx := ectx.Get("context").(*Context) - for _, plugin := range ctx.Server.Plugins { + for _, plugin := range ctx.Server.plugins { if err := plugin.Inject(ctx, name, data.(RenderData)); err != nil { return fmt.Errorf("failed to run plugin '%v': %v", plugin.Name(), err) } @@ -121,7 +116,7 @@ func loadTheme(name string, base *template.Template) (*template.Template, error) return theme, nil } -func (r *renderer) reload(plugins []Plugin) error { +func (r *renderer) Load(plugins []Plugin) error { base := template.New("") for _, p := range plugins { @@ -155,10 +150,8 @@ func (r *renderer) reload(plugins []Plugin) error { } } - r.mutex.Lock() r.base = base r.themes = themes - r.mutex.Unlock() return nil }