mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	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.
This commit is contained in:
		@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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() {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user