diff options
author | wxiaoguang <wxiaoguang@gmail.com> | 2023-05-04 14:36:34 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-04 14:36:34 +0800 |
commit | 5d77691d428d5302ee4df6c2a936b8e2ea9dca7e (patch) | |
tree | 4fa6b13f2ae81c5de94d84f2288ae1f5653c011f /routers/web | |
parent | 75ea0d5dba5dbf2f84cef2d12460fdd566d43e62 (diff) | |
download | gitea-5d77691d428d5302ee4df6c2a936b8e2ea9dca7e.tar.gz gitea-5d77691d428d5302ee4df6c2a936b8e2ea9dca7e.zip |
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>
![image](https://user-images.githubusercontent.com/2114189/235444895-cecbabb8-e7dc-4360-a31c-b982d11946a7.png)
</details>
Diffstat (limited to 'routers/web')
-rw-r--r-- | routers/web/base.go | 66 | ||||
-rw-r--r-- | routers/web/web.go | 30 |
2 files changed, 13 insertions, 83 deletions
diff --git a/routers/web/base.go b/routers/web/base.go index 73607cad06..b9b5958389 100644 --- a/routers/web/base.go +++ b/routers/web/base.go @@ -4,7 +4,6 @@ package web import ( - goctx "context" "errors" "fmt" "io" @@ -13,18 +12,12 @@ import ( "path" "strings" - "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" - "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/modules/web/routing" - "code.gitea.io/gitea/services/auth" - - "gitea.com/go-chi/session" ) func storageHandler(storageSetting setting.Storage, prefix string, objStore storage.ObjectStorage) func(next http.Handler) http.Handler { @@ -110,62 +103,3 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor }) } } - -type dataStore map[string]interface{} - -func (d *dataStore) GetData() map[string]interface{} { - return *d -} - -// RecoveryWith500Page returns a middleware that recovers from any panics and writes a 500 and a log if so. -// This error will be created with the gitea 500 page. -func RecoveryWith500Page(ctx goctx.Context) func(next http.Handler) http.Handler { - rnd := templates.HTMLRenderer() - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - defer func() { - if err := recover(); err != nil { - routing.UpdatePanicError(req.Context(), err) - combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, log.Stack(2)) - log.Error("%s", combinedErr) - - sessionStore := session.GetSession(req) - - lc := middleware.Locale(w, req) - store := dataStore{ - "Language": lc.Language(), - "CurrentURL": setting.AppSubURL + req.URL.RequestURI(), - "locale": lc, - } - - // TODO: this recovery handler is usually called without Gitea's web context, so we shouldn't touch that context too much - // Otherwise, the 500 page may cause new panics, eg: cache.GetContextWithData, it makes the developer&users couldn't find the original panic - user := context.GetContextUser(req) // almost always nil - if user == nil { - // Get user from session if logged in - do not attempt to sign-in - user = auth.SessionUser(sessionStore) - } - - httpcache.SetCacheControlInHeader(w.Header(), 0, "no-transform") - w.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions) - - if !setting.IsProd || (user != nil && user.IsAdmin) { - store["ErrorMsg"] = combinedErr - } - - defer func() { - if err := recover(); err != nil { - log.Error("HTML render in Recovery handler panics again: %v", err) - } - }() - err = rnd.HTML(w, http.StatusInternalServerError, "status/500", templates.BaseVars().Merge(store)) - if err != nil { - log.Error("HTML render in Recovery handler fails again: %v", err) - } - } - }() - - next.ServeHTTP(w, req) - }) - } -} diff --git a/routers/web/web.go b/routers/web/web.go index 5357c55500..8cdc10935e 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -116,38 +116,34 @@ func Routes(ctx gocontext.Context) *web.Route { _ = templates.HTMLRenderer() - common := []any{ - common.Sessioner(), - RecoveryWith500Page(ctx), - } + var mid []any if setting.EnableGzip { h, err := gziphandler.GzipHandlerWithOpts(gziphandler.MinSize(GzipMinSize)) if err != nil { log.Fatal("GzipHandlerWithOpts failed: %v", err) } - common = append(common, h) + mid = append(mid, h) } if setting.Service.EnableCaptcha { // The captcha http.Handler should only fire on /captcha/* so we can just mount this on that url - routes.RouteMethods("/captcha/*", "GET,HEAD", append(common, captcha.Captchaer(context.GetImageCaptcha()))...) + routes.RouteMethods("/captcha/*", "GET,HEAD", append(mid, captcha.Captchaer(context.GetImageCaptcha()))...) } if setting.HasRobotsTxt { - routes.Get("/robots.txt", append(common, misc.RobotsTxt)...) + routes.Get("/robots.txt", append(mid, misc.RobotsTxt)...) } - // prometheus metrics endpoint - do not need to go through contexter if setting.Metrics.Enabled { prometheus.MustRegister(metrics.NewCollector()) - routes.Get("/metrics", append(common, Metrics)...) + routes.Get("/metrics", append(mid, Metrics)...) } routes.Get("/ssh_info", misc.SSHInfo) routes.Get("/api/healthz", healthcheck.Check) - common = append(common, context.Contexter(ctx)) + mid = append(mid, common.Sessioner(), context.Contexter()) group := buildAuthGroup() if err := group.Init(ctx); err != nil { @@ -155,23 +151,23 @@ func Routes(ctx gocontext.Context) *web.Route { } // Get user from session if logged in. - common = append(common, auth_service.Auth(group)) + mid = append(mid, auth_service.Auth(group)) // GetHead allows a HEAD request redirect to GET if HEAD method is not defined for that route - common = append(common, middleware.GetHead) + mid = append(mid, middleware.GetHead) if setting.API.EnableSwagger { // Note: The route is here but no in API routes because it renders a web page - routes.Get("/api/swagger", append(common, misc.Swagger)...) // Render V1 by default + routes.Get("/api/swagger", append(mid, misc.Swagger)...) // Render V1 by default } // TODO: These really seem like things that could be folded into Contexter or as helper functions - common = append(common, user.GetNotificationCount) - common = append(common, repo.GetActiveStopwatch) - common = append(common, goGet) + mid = append(mid, user.GetNotificationCount) + mid = append(mid, repo.GetActiveStopwatch) + mid = append(mid, goGet) others := web.NewRoute() - others.Use(common...) + others.Use(mid...) registerRoutes(others) routes.Mount("", others) return routes |