mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-05-04 13:50:39 +00:00
Improve template system and panic recovery (#24461)
Partially for #24457 Major changes: 1. The old `signedUserNameStringPointerKey` is quite hacky, use `ctx.Data[SignedUser]` instead 2. Move duplicate code from `Contexter` to `CommonTemplateContextData` 3. Remove incorrect copying&pasting code `ctx.Data["Err_Password"] = true` in API handlers 4. Use one unique `RenderPanicErrorPage` for panic error page rendering 5. Move `stripSlashesMiddleware` to be the first middleware 6. Install global panic recovery handler, it works for both `install` and `web` 7. Make `500.tmpl` only depend minimal template functions/variables, avoid triggering new panics Screenshot: <details>  </details>
This commit is contained in:
parent
75ea0d5dba
commit
5d77691d42
25 changed files with 277 additions and 364 deletions
|
@ -55,7 +55,7 @@ type Render interface {
|
|||
type Context struct {
|
||||
Resp ResponseWriter
|
||||
Req *http.Request
|
||||
Data map[string]interface{} // data used by MVC templates
|
||||
Data middleware.ContextData // data used by MVC templates
|
||||
PageData map[string]interface{} // data used by JavaScript modules in one page, it's `window.config.pageData`
|
||||
Render Render
|
||||
translation.Locale
|
||||
|
@ -97,7 +97,7 @@ func (ctx *Context) TrHTMLEscapeArgs(msg string, args ...string) string {
|
|||
}
|
||||
|
||||
// GetData returns the data
|
||||
func (ctx *Context) GetData() map[string]interface{} {
|
||||
func (ctx *Context) GetData() middleware.ContextData {
|
||||
return ctx.Data
|
||||
}
|
||||
|
||||
|
@ -219,6 +219,7 @@ const tplStatus500 base.TplName = "status/500"
|
|||
// HTML calls Context.HTML and renders the template to HTTP response
|
||||
func (ctx *Context) HTML(status int, name base.TplName) {
|
||||
log.Debug("Template: %s", name)
|
||||
|
||||
tmplStartTime := time.Now()
|
||||
if !setting.IsProd {
|
||||
ctx.Data["TemplateName"] = name
|
||||
|
@ -226,13 +227,19 @@ func (ctx *Context) HTML(status int, name base.TplName) {
|
|||
ctx.Data["TemplateLoadTimes"] = func() string {
|
||||
return strconv.FormatInt(time.Since(tmplStartTime).Nanoseconds()/1e6, 10) + "ms"
|
||||
}
|
||||
if err := ctx.Render.HTML(ctx.Resp, status, string(name), templates.BaseVars().Merge(ctx.Data)); err != nil {
|
||||
if status == http.StatusInternalServerError && name == tplStatus500 {
|
||||
ctx.PlainText(http.StatusInternalServerError, "Unable to find HTML templates, the template system is not initialized, or Gitea can't find your template files.")
|
||||
return
|
||||
}
|
||||
|
||||
err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// if rendering fails, show error page
|
||||
if name != tplStatus500 {
|
||||
err = fmt.Errorf("failed to render template: %s, error: %s", name, templates.HandleTemplateRenderingError(err))
|
||||
ctx.ServerError("Render failed", err)
|
||||
ctx.ServerError("Render failed", err) // show the 500 error page
|
||||
} else {
|
||||
ctx.PlainText(http.StatusInternalServerError, "Unable to render status/500 page, the template system is broken, or Gitea can't find your template files.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -676,7 +683,7 @@ func getCsrfOpts() CsrfOptions {
|
|||
}
|
||||
|
||||
// Contexter initializes a classic context for a request.
|
||||
func Contexter(ctx context.Context) func(next http.Handler) http.Handler {
|
||||
func Contexter() func(next http.Handler) http.Handler {
|
||||
rnd := templates.HTMLRenderer()
|
||||
csrfOpts := getCsrfOpts()
|
||||
if !setting.IsProd {
|
||||
|
@ -684,34 +691,30 @@ func Contexter(ctx context.Context) func(next http.Handler) http.Handler {
|
|||
}
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
locale := middleware.Locale(resp, req)
|
||||
startTime := time.Now()
|
||||
link := setting.AppSubURL + strings.TrimSuffix(req.URL.EscapedPath(), "/")
|
||||
|
||||
ctx := Context{
|
||||
Resp: NewResponse(resp),
|
||||
Cache: mc.GetCache(),
|
||||
Locale: locale,
|
||||
Link: link,
|
||||
Locale: middleware.Locale(resp, req),
|
||||
Link: setting.AppSubURL + strings.TrimSuffix(req.URL.EscapedPath(), "/"),
|
||||
Render: rnd,
|
||||
Session: session.GetSession(req),
|
||||
Repo: &Repository{
|
||||
PullRequest: &PullRequest{},
|
||||
},
|
||||
Org: &Organization{},
|
||||
Data: map[string]interface{}{
|
||||
"CurrentURL": setting.AppSubURL + req.URL.RequestURI(),
|
||||
"PageStartTime": startTime,
|
||||
"Link": link,
|
||||
"RunModeIsProd": setting.IsProd,
|
||||
},
|
||||
Org: &Organization{},
|
||||
Data: middleware.GetContextData(req.Context()),
|
||||
}
|
||||
defer ctx.Close()
|
||||
|
||||
// PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules
|
||||
ctx.PageData = map[string]interface{}{}
|
||||
ctx.Data["PageData"] = ctx.PageData
|
||||
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
|
||||
ctx.Data["Context"] = &ctx
|
||||
ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI()
|
||||
ctx.Data["Link"] = ctx.Link
|
||||
ctx.Data["locale"] = ctx.Locale
|
||||
|
||||
// PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules
|
||||
ctx.PageData = map[string]any{}
|
||||
ctx.Data["PageData"] = ctx.PageData
|
||||
|
||||
ctx.Req = WithContext(req, &ctx)
|
||||
ctx.Csrf = PrepareCSRFProtector(csrfOpts, &ctx)
|
||||
|
@ -755,16 +758,6 @@ func Contexter(ctx context.Context) func(next http.Handler) http.Handler {
|
|||
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
|
||||
|
||||
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
|
||||
ctx.Data["IsLandingPageHome"] = setting.LandingPageURL == setting.LandingPageHome
|
||||
ctx.Data["IsLandingPageExplore"] = setting.LandingPageURL == setting.LandingPageExplore
|
||||
ctx.Data["IsLandingPageOrganizations"] = setting.LandingPageURL == setting.LandingPageOrganizations
|
||||
|
||||
ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
|
||||
ctx.Data["ShowMilestonesDashboardPage"] = setting.Service.ShowMilestonesDashboardPage
|
||||
ctx.Data["ShowFooterVersion"] = setting.Other.ShowFooterVersion
|
||||
|
||||
ctx.Data["EnableSwagger"] = setting.API.EnableSwagger
|
||||
ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn
|
||||
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
|
||||
ctx.Data["DisableStars"] = setting.Repository.DisableStars
|
||||
ctx.Data["EnableActions"] = setting.Actions.Enabled
|
||||
|
@ -777,21 +770,9 @@ func Contexter(ctx context.Context) func(next http.Handler) http.Handler {
|
|||
ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled()
|
||||
ctx.Data["UnitActionsGlobalDisabled"] = unit.TypeActions.UnitGlobalDisabled()
|
||||
|
||||
ctx.Data["locale"] = locale
|
||||
ctx.Data["AllLangs"] = translation.AllLangs()
|
||||
|
||||
next.ServeHTTP(ctx.Resp, ctx.Req)
|
||||
|
||||
// Handle adding signedUserName to the context for the AccessLogger
|
||||
usernameInterface := ctx.Data["SignedUserName"]
|
||||
identityPtrInterface := ctx.Req.Context().Value(signedUserNameStringPointerKey)
|
||||
if usernameInterface != nil && identityPtrInterface != nil {
|
||||
username := usernameInterface.(string)
|
||||
identityPtr := identityPtrInterface.(*string)
|
||||
if identityPtr != nil && username != "" {
|
||||
*identityPtr = username
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue