Skip to content

Commit

Permalink
Use route rather than use thus reducing the number of stack frames (#…
Browse files Browse the repository at this point in the history
…15301)

Since the move to Chi the number of stack frames has proliferated somewhat catastrophically and we're up to 96 frames with multiple tests of the url outside of a trie which is inefficient.

This PR reduces the number of stack frames by 6 through careful use of Route, moves Captcha into its own router so that it only fires on Captcha routes, similarly for avatars and repo-avatars.

The robots.txt, / and apple-touch-icon.png are moved out of requiring Contexter.

It moves access logger higher in the stack frame because there is no reason why it can't be higher.

Extract from #15186
Contains #15292
  • Loading branch information
zeripath authored May 4, 2021
1 parent ab77a24 commit 47fd156
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 46 deletions.
6 changes: 6 additions & 0 deletions modules/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,7 @@ func Contexter() func(next http.Handler) http.Handler {
log.Debug("Session ID: %s", ctx.Session.ID())
log.Debug("CSRF Token: %v", ctx.Data["CsrfToken"])

// 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
Expand All @@ -708,6 +709,11 @@ func Contexter() func(next http.Handler) http.Handler {

ctx.Data["ManifestData"] = setting.ManifestData

ctx.Data["UnitWikiGlobalDisabled"] = models.UnitTypeWiki.UnitGlobalDisabled()
ctx.Data["UnitIssuesGlobalDisabled"] = models.UnitTypeIssues.UnitGlobalDisabled()
ctx.Data["UnitPullsGlobalDisabled"] = models.UnitTypePullRequests.UnitGlobalDisabled()
ctx.Data["UnitProjectsGlobalDisabled"] = models.UnitTypeProjects.UnitGlobalDisabled()

ctx.Data["i18n"] = locale
ctx.Data["Tr"] = i18n.Tr
ctx.Data["Lang"] = locale.Language()
Expand Down
4 changes: 0 additions & 4 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,10 +572,6 @@ func Routes() *web.Route {
}
m.Use(context.APIContexter())

if setting.EnableAccessLog {
m.Use(context.AccessLogger())
}

m.Use(context.ToggleAPI(&context.ToggleOptions{
SignInRequired: setting.Service.RequireSignInView,
}))
Expand Down
93 changes: 51 additions & 42 deletions routers/routes/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ func commonMiddlewares() []func(http.Handler) http.Handler {
handlers = append(handlers, LoggerHandler(setting.RouterLogLevel))
}
}
if setting.EnableAccessLog {
handlers = append(handlers, context.AccessLogger())
}

handlers = append(handlers, func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
Expand Down Expand Up @@ -128,9 +131,9 @@ func NormalRoutes() *web.Route {

// WebRoutes returns all web routes
func WebRoutes() *web.Route {
r := web.NewRoute()
routes := web.NewRoute()

r.Use(session.Sessioner(session.Options{
routes.Use(session.Sessioner(session.Options{
Provider: setting.SessionConfig.Provider,
ProviderConfig: setting.SessionConfig.ProviderConfig,
CookieName: setting.SessionConfig.CookieName,
Expand All @@ -141,93 +144,99 @@ func WebRoutes() *web.Route {
Domain: setting.SessionConfig.Domain,
}))

r.Use(Recovery())
routes.Use(Recovery())

r.Use(public.Custom(
// TODO: we should consider if there is a way to mount these using r.Route as at present
// these two handlers mean that every request has to hit these "filesystems" twice
// before finally getting to the router. It allows them to override any matching router below.
routes.Use(public.Custom(
&public.Options{
SkipLogging: setting.DisableRouterLog,
},
))
r.Use(public.Static(
routes.Use(public.Static(
&public.Options{
Directory: path.Join(setting.StaticRootPath, "public"),
SkipLogging: setting.DisableRouterLog,
Prefix: "/assets",
},
))

r.Use(storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
r.Use(storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
// We use r.Route here over r.Use because this prevents requests that are not for avatars having to go through this additional handler
routes.Route("/avatars", "GET, HEAD", storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
routes.Route("/repo-avatars", "GET, HEAD", storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))

// for health check - doeesn't need to be passed through gzip handler
routes.Head("/", func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
})

// this png is very likely to always be below the limit for gzip so it doesn't need to pass through gzip
routes.Get("/apple-touch-icon.png", func(w http.ResponseWriter, req *http.Request) {
http.Redirect(w, req, path.Join(setting.StaticURLPrefix, "img/apple-touch-icon.png"), 301)
})

gob.Register(&u2f.Challenge{})

common := []interface{}{}

if setting.EnableGzip {
h, err := gziphandler.GzipHandlerWithOpts(gziphandler.MinSize(GzipMinSize))
if err != nil {
log.Fatal("GzipHandlerWithOpts failed: %v", err)
}
r.Use(h)
common = append(common, h)
}

mailer.InitMailRender(templates.Mailer())

if setting.Service.EnableCaptcha {
r.Use(captcha.Captchaer(context.GetImageCaptcha()))
}
// Removed: toolbox.Toolboxer middleware will provide debug informations which seems unnecessary
r.Use(context.Contexter())
// GetHead allows a HEAD request redirect to GET if HEAD method is not defined for that route
r.Use(middleware.GetHead)

if setting.EnableAccessLog {
r.Use(context.AccessLogger())
// The captcha http.Handler should only fire on /captcha/* so we can just mount this on that url
routes.Route("/captcha/*", "GET,HEAD", append(common, captcha.Captchaer(context.GetImageCaptcha()))...)
}

r.Use(user.GetNotificationCount)
r.Use(repo.GetActiveStopwatch)
r.Use(func(ctx *context.Context) {
ctx.Data["UnitWikiGlobalDisabled"] = models.UnitTypeWiki.UnitGlobalDisabled()
ctx.Data["UnitIssuesGlobalDisabled"] = models.UnitTypeIssues.UnitGlobalDisabled()
ctx.Data["UnitPullsGlobalDisabled"] = models.UnitTypePullRequests.UnitGlobalDisabled()
ctx.Data["UnitProjectsGlobalDisabled"] = models.UnitTypeProjects.UnitGlobalDisabled()
})

// for health check
r.Head("/", func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
})

if setting.HasRobotsTxt {
r.Get("/robots.txt", func(w http.ResponseWriter, req *http.Request) {
routes.Get("/robots.txt", append(common, func(w http.ResponseWriter, req *http.Request) {
filePath := path.Join(setting.CustomPath, "robots.txt")
fi, err := os.Stat(filePath)
if err == nil && httpcache.HandleTimeCache(req, w, fi) {
return
}
http.ServeFile(w, req, filePath)
})
})...)
}

r.Get("/apple-touch-icon.png", func(w http.ResponseWriter, req *http.Request) {
http.Redirect(w, req, path.Join(setting.StaticURLPrefix, "img/apple-touch-icon.png"), 301)
})

// prometheus metrics endpoint
// prometheus metrics endpoint - do not need to go through contexter
if setting.Metrics.Enabled {
c := metrics.NewCollector()
prometheus.MustRegister(c)

r.Get("/metrics", routers.Metrics)
routes.Get("/metrics", append(common, routers.Metrics)...)
}

// Removed: toolbox.Toolboxer middleware will provide debug informations which seems unnecessary
common = append(common, context.Contexter())

// GetHead allows a HEAD request redirect to GET if HEAD method is not defined for that route
common = append(common, middleware.GetHead)

if setting.API.EnableSwagger {
// Note: The route moved from apiroutes because it's in fact want to render a web page
r.Get("/api/swagger", misc.Swagger) // Render V1 by default
routes.Get("/api/swagger", append(common, misc.Swagger)...) // Render V1 by default
}

RegisterRoutes(r)
// 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)

return r
others := web.NewRoute()
for _, middle := range common {
others.Use(middle)
}

RegisterRoutes(others)
routes.Mount("", others)
return routes
}

func goGet(ctx *context.Context) {
Expand Down

0 comments on commit 47fd156

Please sign in to comment.