Skip to content

Commit 47fd156

Browse files
authored
Use route rather than use thus reducing the number of stack frames (#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
1 parent ab77a24 commit 47fd156

File tree

3 files changed

+57
-46
lines changed

3 files changed

+57
-46
lines changed

modules/context/context.go

+6
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,7 @@ func Contexter() func(next http.Handler) http.Handler {
692692
log.Debug("Session ID: %s", ctx.Session.ID())
693693
log.Debug("CSRF Token: %v", ctx.Data["CsrfToken"])
694694

695+
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
695696
ctx.Data["IsLandingPageHome"] = setting.LandingPageURL == setting.LandingPageHome
696697
ctx.Data["IsLandingPageExplore"] = setting.LandingPageURL == setting.LandingPageExplore
697698
ctx.Data["IsLandingPageOrganizations"] = setting.LandingPageURL == setting.LandingPageOrganizations
@@ -708,6 +709,11 @@ func Contexter() func(next http.Handler) http.Handler {
708709

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

712+
ctx.Data["UnitWikiGlobalDisabled"] = models.UnitTypeWiki.UnitGlobalDisabled()
713+
ctx.Data["UnitIssuesGlobalDisabled"] = models.UnitTypeIssues.UnitGlobalDisabled()
714+
ctx.Data["UnitPullsGlobalDisabled"] = models.UnitTypePullRequests.UnitGlobalDisabled()
715+
ctx.Data["UnitProjectsGlobalDisabled"] = models.UnitTypeProjects.UnitGlobalDisabled()
716+
711717
ctx.Data["i18n"] = locale
712718
ctx.Data["Tr"] = i18n.Tr
713719
ctx.Data["Lang"] = locale.Language()

routers/api/v1/api.go

-4
Original file line numberDiff line numberDiff line change
@@ -572,10 +572,6 @@ func Routes() *web.Route {
572572
}
573573
m.Use(context.APIContexter())
574574

575-
if setting.EnableAccessLog {
576-
m.Use(context.AccessLogger())
577-
}
578-
579575
m.Use(context.ToggleAPI(&context.ToggleOptions{
580576
SignInRequired: setting.Service.RequireSignInView,
581577
}))

routers/routes/web.go

+51-42
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ func commonMiddlewares() []func(http.Handler) http.Handler {
8989
handlers = append(handlers, LoggerHandler(setting.RouterLogLevel))
9090
}
9191
}
92+
if setting.EnableAccessLog {
93+
handlers = append(handlers, context.AccessLogger())
94+
}
9295

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

129132
// WebRoutes returns all web routes
130133
func WebRoutes() *web.Route {
131-
r := web.NewRoute()
134+
routes := web.NewRoute()
132135

133-
r.Use(session.Sessioner(session.Options{
136+
routes.Use(session.Sessioner(session.Options{
134137
Provider: setting.SessionConfig.Provider,
135138
ProviderConfig: setting.SessionConfig.ProviderConfig,
136139
CookieName: setting.SessionConfig.CookieName,
@@ -141,93 +144,99 @@ func WebRoutes() *web.Route {
141144
Domain: setting.SessionConfig.Domain,
142145
}))
143146

144-
r.Use(Recovery())
147+
routes.Use(Recovery())
145148

146-
r.Use(public.Custom(
149+
// TODO: we should consider if there is a way to mount these using r.Route as at present
150+
// these two handlers mean that every request has to hit these "filesystems" twice
151+
// before finally getting to the router. It allows them to override any matching router below.
152+
routes.Use(public.Custom(
147153
&public.Options{
148154
SkipLogging: setting.DisableRouterLog,
149155
},
150156
))
151-
r.Use(public.Static(
157+
routes.Use(public.Static(
152158
&public.Options{
153159
Directory: path.Join(setting.StaticRootPath, "public"),
154160
SkipLogging: setting.DisableRouterLog,
155161
Prefix: "/assets",
156162
},
157163
))
158164

159-
r.Use(storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
160-
r.Use(storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
165+
// We use r.Route here over r.Use because this prevents requests that are not for avatars having to go through this additional handler
166+
routes.Route("/avatars", "GET, HEAD", storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
167+
routes.Route("/repo-avatars", "GET, HEAD", storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
168+
169+
// for health check - doeesn't need to be passed through gzip handler
170+
routes.Head("/", func(w http.ResponseWriter, req *http.Request) {
171+
w.WriteHeader(http.StatusOK)
172+
})
173+
174+
// this png is very likely to always be below the limit for gzip so it doesn't need to pass through gzip
175+
routes.Get("/apple-touch-icon.png", func(w http.ResponseWriter, req *http.Request) {
176+
http.Redirect(w, req, path.Join(setting.StaticURLPrefix, "img/apple-touch-icon.png"), 301)
177+
})
161178

162179
gob.Register(&u2f.Challenge{})
163180

181+
common := []interface{}{}
182+
164183
if setting.EnableGzip {
165184
h, err := gziphandler.GzipHandlerWithOpts(gziphandler.MinSize(GzipMinSize))
166185
if err != nil {
167186
log.Fatal("GzipHandlerWithOpts failed: %v", err)
168187
}
169-
r.Use(h)
188+
common = append(common, h)
170189
}
171190

172191
mailer.InitMailRender(templates.Mailer())
173192

174193
if setting.Service.EnableCaptcha {
175-
r.Use(captcha.Captchaer(context.GetImageCaptcha()))
176-
}
177-
// Removed: toolbox.Toolboxer middleware will provide debug informations which seems unnecessary
178-
r.Use(context.Contexter())
179-
// GetHead allows a HEAD request redirect to GET if HEAD method is not defined for that route
180-
r.Use(middleware.GetHead)
181-
182-
if setting.EnableAccessLog {
183-
r.Use(context.AccessLogger())
194+
// The captcha http.Handler should only fire on /captcha/* so we can just mount this on that url
195+
routes.Route("/captcha/*", "GET,HEAD", append(common, captcha.Captchaer(context.GetImageCaptcha()))...)
184196
}
185197

186-
r.Use(user.GetNotificationCount)
187-
r.Use(repo.GetActiveStopwatch)
188-
r.Use(func(ctx *context.Context) {
189-
ctx.Data["UnitWikiGlobalDisabled"] = models.UnitTypeWiki.UnitGlobalDisabled()
190-
ctx.Data["UnitIssuesGlobalDisabled"] = models.UnitTypeIssues.UnitGlobalDisabled()
191-
ctx.Data["UnitPullsGlobalDisabled"] = models.UnitTypePullRequests.UnitGlobalDisabled()
192-
ctx.Data["UnitProjectsGlobalDisabled"] = models.UnitTypeProjects.UnitGlobalDisabled()
193-
})
194-
195-
// for health check
196-
r.Head("/", func(w http.ResponseWriter, req *http.Request) {
197-
w.WriteHeader(http.StatusOK)
198-
})
199-
200198
if setting.HasRobotsTxt {
201-
r.Get("/robots.txt", func(w http.ResponseWriter, req *http.Request) {
199+
routes.Get("/robots.txt", append(common, func(w http.ResponseWriter, req *http.Request) {
202200
filePath := path.Join(setting.CustomPath, "robots.txt")
203201
fi, err := os.Stat(filePath)
204202
if err == nil && httpcache.HandleTimeCache(req, w, fi) {
205203
return
206204
}
207205
http.ServeFile(w, req, filePath)
208-
})
206+
})...)
209207
}
210208

211-
r.Get("/apple-touch-icon.png", func(w http.ResponseWriter, req *http.Request) {
212-
http.Redirect(w, req, path.Join(setting.StaticURLPrefix, "img/apple-touch-icon.png"), 301)
213-
})
214-
215-
// prometheus metrics endpoint
209+
// prometheus metrics endpoint - do not need to go through contexter
216210
if setting.Metrics.Enabled {
217211
c := metrics.NewCollector()
218212
prometheus.MustRegister(c)
219213

220-
r.Get("/metrics", routers.Metrics)
214+
routes.Get("/metrics", append(common, routers.Metrics)...)
221215
}
222216

217+
// Removed: toolbox.Toolboxer middleware will provide debug informations which seems unnecessary
218+
common = append(common, context.Contexter())
219+
220+
// GetHead allows a HEAD request redirect to GET if HEAD method is not defined for that route
221+
common = append(common, middleware.GetHead)
222+
223223
if setting.API.EnableSwagger {
224224
// Note: The route moved from apiroutes because it's in fact want to render a web page
225-
r.Get("/api/swagger", misc.Swagger) // Render V1 by default
225+
routes.Get("/api/swagger", append(common, misc.Swagger)...) // Render V1 by default
226226
}
227227

228-
RegisterRoutes(r)
228+
// TODO: These really seem like things that could be folded into Contexter or as helper functions
229+
common = append(common, user.GetNotificationCount)
230+
common = append(common, repo.GetActiveStopwatch)
229231

230-
return r
232+
others := web.NewRoute()
233+
for _, middle := range common {
234+
others.Use(middle)
235+
}
236+
237+
RegisterRoutes(others)
238+
routes.Mount("", others)
239+
return routes
231240
}
232241

233242
func goGet(ctx *context.Context) {

0 commit comments

Comments
 (0)