diff options
author | wxiaoguang <wxiaoguang@gmail.com> | 2023-04-30 20:22:23 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-30 08:22:23 -0400 |
commit | e3750370df3be1413b1526668cbee60dc2a39f03 (patch) | |
tree | cc61431d00e62df1a8be5e2f050105749ddad5e8 /modules | |
parent | 8f4dafcd4e6b0b5d307c3e060ffe908c2a96f047 (diff) | |
download | gitea-e3750370df3be1413b1526668cbee60dc2a39f03.tar.gz gitea-e3750370df3be1413b1526668cbee60dc2a39f03.zip |
Use globally shared HTMLRender (#24436)
The old `HTMLRender` is not ideal.
1. It shouldn't be initialized multiple times, it consumes a lot of
memory and is slow.
2. It shouldn't depend on short-lived requests, the `WatchLocalChanges`
needs a long-running context.
3. It doesn't make sense to use FuncsMap slice.
HTMLRender was designed to only work for GItea's specialized 400+
templates, so it's good to make it a global shared instance.
Diffstat (limited to 'modules')
-rw-r--r-- | modules/context/context.go | 2 | ||||
-rw-r--r-- | modules/context/package.go | 2 | ||||
-rw-r--r-- | modules/templates/helper.go | 10 | ||||
-rw-r--r-- | modules/templates/htmlrenderer.go | 37 | ||||
-rw-r--r-- | modules/templates/mailer.go | 7 |
5 files changed, 30 insertions, 28 deletions
diff --git a/modules/context/context.go b/modules/context/context.go index 702da8a965..cd7fcebe55 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -677,7 +677,7 @@ func getCsrfOpts() CsrfOptions { // Contexter initializes a classic context for a request. func Contexter(ctx context.Context) func(next http.Handler) http.Handler { - _, rnd := templates.HTMLRenderer(ctx) + rnd := templates.HTMLRenderer() csrfOpts := getCsrfOpts() if !setting.IsProd { CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose diff --git a/modules/context/package.go b/modules/context/package.go index 2a0159eb5c..6c418b3164 100644 --- a/modules/context/package.go +++ b/modules/context/package.go @@ -131,7 +131,7 @@ func determineAccessMode(ctx *Context) (perm.AccessMode, error) { // PackageContexter initializes a package context for a request. func PackageContexter(ctx gocontext.Context) func(next http.Handler) http.Handler { - _, rnd := templates.HTMLRenderer(ctx) + rnd := templates.HTMLRenderer() return func(next http.Handler) http.Handler { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { ctx := Context{ diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 20261eb959..4abd94d46e 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -10,7 +10,6 @@ import ( "html" "html/template" "net/url" - "regexp" "strings" "time" @@ -26,12 +25,9 @@ import ( "code.gitea.io/gitea/services/gitdiff" ) -// Used from static.go && dynamic.go -var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}[\s]*$`) - // NewFuncMap returns functions for injecting to templates -func NewFuncMap() []template.FuncMap { - return []template.FuncMap{map[string]interface{}{ +func NewFuncMap() template.FuncMap { + return map[string]interface{}{ "DumpVar": dumpVar, // ----------------------------------------------------------------- @@ -192,7 +188,7 @@ func NewFuncMap() []template.FuncMap { "FilenameIsImage": FilenameIsImage, "TabSizeClass": TabSizeClass, - }} + } } // Safe render raw as HTML diff --git a/modules/templates/htmlrenderer.go b/modules/templates/htmlrenderer.go index 2cecef5f84..d60be88727 100644 --- a/modules/templates/htmlrenderer.go +++ b/modules/templates/htmlrenderer.go @@ -6,7 +6,6 @@ package templates import ( "bufio" "bytes" - "context" "errors" "fmt" "io" @@ -15,24 +14,29 @@ import ( "regexp" "strconv" "strings" + "sync" "sync/atomic" texttemplate "text/template" "code.gitea.io/gitea/modules/assetfs" + "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates/scopedtmpl" "code.gitea.io/gitea/modules/util" ) -var rendererKey interface{} = "templatesHtmlRenderer" - type TemplateExecutor scopedtmpl.TemplateExecutor type HTMLRender struct { templates atomic.Pointer[scopedtmpl.ScopedTemplate] } +var ( + htmlRender *HTMLRender + htmlRenderOnce sync.Once +) + var ErrTemplateNotInitialized = errors.New("template system is not initialized, check your log for errors") func (h *HTMLRender) HTML(w io.Writer, status int, name string, data interface{}) error { @@ -55,14 +59,14 @@ func (h *HTMLRender) TemplateLookup(name string) (TemplateExecutor, error) { return nil, ErrTemplateNotInitialized } - return tmpls.Executor(name, NewFuncMap()[0]) + return tmpls.Executor(name, NewFuncMap()) } func (h *HTMLRender) CompileTemplates() error { assets := AssetFS() extSuffix := ".tmpl" tmpls := scopedtmpl.NewScopedTemplate() - tmpls.Funcs(NewFuncMap()[0]) + tmpls.Funcs(NewFuncMap()) files, err := ListWebTemplateAssetNames(assets) if err != nil { return nil @@ -86,20 +90,21 @@ func (h *HTMLRender) CompileTemplates() error { return nil } -// HTMLRenderer returns the current html renderer for the context or creates and stores one within the context for future use -func HTMLRenderer(ctx context.Context) (context.Context, *HTMLRender) { - if renderer, ok := ctx.Value(rendererKey).(*HTMLRender); ok { - return ctx, renderer - } +// HTMLRenderer init once and returns the globally shared html renderer +func HTMLRenderer() *HTMLRender { + htmlRenderOnce.Do(initHTMLRenderer) + return htmlRender +} +func initHTMLRenderer() { rendererType := "static" if !setting.IsProd { rendererType = "auto-reloading" } - log.Log(1, log.DEBUG, "Creating "+rendererType+" HTML Renderer") + log.Debug("Creating %s HTML Renderer", rendererType) - renderer := &HTMLRender{} - if err := renderer.CompileTemplates(); err != nil { + htmlRender = &HTMLRender{} + if err := htmlRender.CompileTemplates(); err != nil { p := &templateErrorPrettier{assets: AssetFS()} wrapFatal(p.handleFuncNotDefinedError(err)) wrapFatal(p.handleUnexpectedOperandError(err)) @@ -107,14 +112,14 @@ func HTMLRenderer(ctx context.Context) (context.Context, *HTMLRender) { wrapFatal(p.handleGenericTemplateError(err)) log.Fatal("HTMLRenderer CompileTemplates error: %v", err) } + if !setting.IsProd { - go AssetFS().WatchLocalChanges(ctx, func() { - if err := renderer.CompileTemplates(); err != nil { + go AssetFS().WatchLocalChanges(graceful.GetManager().ShutdownContext(), func() { + if err := htmlRender.CompileTemplates(); err != nil { log.Error("Template error: %v\n%s", err, log.Stack(2)) } }) } - return context.WithValue(ctx, rendererKey, renderer), renderer } func wrapFatal(msg string) { diff --git a/modules/templates/mailer.go b/modules/templates/mailer.go index 280ac0e587..ac1715fcd0 100644 --- a/modules/templates/mailer.go +++ b/modules/templates/mailer.go @@ -6,6 +6,7 @@ package templates import ( "context" "html/template" + "regexp" "strings" texttmpl "text/template" @@ -14,6 +15,8 @@ import ( "code.gitea.io/gitea/modules/setting" ) +var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}\s*$`) + // mailSubjectTextFuncMap returns functions for injecting to text templates, it's only used for mail subject func mailSubjectTextFuncMap() texttmpl.FuncMap { return texttmpl.FuncMap{ @@ -55,9 +58,7 @@ func Mailer(ctx context.Context) (*texttmpl.Template, *template.Template) { bodyTemplates := template.New("") subjectTemplates.Funcs(mailSubjectTextFuncMap()) - for _, funcs := range NewFuncMap() { - bodyTemplates.Funcs(funcs) - } + bodyTemplates.Funcs(NewFuncMap()) assetFS := AssetFS() refreshTemplates := func() { |