loading up the forgejo repo on tangled to test page performance
at forgejo 8.5 kB view raw
1// Copyright 2014 The Gogs Authors. All rights reserved. 2// Copyright 2020 The Gitea Authors. All rights reserved. 3// SPDX-License-Identifier: MIT 4 5package context 6 7import ( 8 "context" 9 "encoding/hex" 10 "fmt" 11 "html/template" 12 "io" 13 "net/http" 14 "net/url" 15 "strings" 16 "time" 17 18 "forgejo.org/models/unit" 19 user_model "forgejo.org/models/user" 20 mc "forgejo.org/modules/cache" 21 "forgejo.org/modules/gitrepo" 22 "forgejo.org/modules/httpcache" 23 "forgejo.org/modules/setting" 24 "forgejo.org/modules/templates" 25 "forgejo.org/modules/translation" 26 "forgejo.org/modules/web" 27 "forgejo.org/modules/web/middleware" 28 web_types "forgejo.org/modules/web/types" 29 30 "code.forgejo.org/go-chi/cache" 31 "code.forgejo.org/go-chi/session" 32) 33 34// Render represents a template render 35type Render interface { 36 TemplateLookup(tmpl string, templateCtx context.Context) (templates.TemplateExecutor, error) 37 HTML(w io.Writer, status int, name string, data any, templateCtx context.Context) error 38} 39 40// Context represents context of a request. 41type Context struct { 42 *Base 43 44 TemplateContext TemplateContext 45 46 Render Render 47 PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData` 48 49 Cache cache.Cache 50 Csrf CSRFProtector 51 Flash *middleware.Flash 52 Session session.Store 53 54 Link string // current request URL (without query string) 55 56 Doer *user_model.User // current signed-in user 57 IsSigned bool 58 IsBasicAuth bool 59 60 ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer 61 62 Repo *Repository 63 Org *Organization 64 Package *Package 65} 66 67type TemplateContext map[string]any 68 69func init() { 70 web.RegisterResponseStatusProvider[*Context](func(req *http.Request) web_types.ResponseStatusProvider { 71 return req.Context().Value(WebContextKey).(*Context) 72 }) 73} 74 75type webContextKeyType struct{} 76 77var WebContextKey = webContextKeyType{} 78 79func GetWebContext(req *http.Request) *Context { 80 ctx, _ := req.Context().Value(WebContextKey).(*Context) 81 return ctx 82} 83 84// ValidateContext is a special context for form validation middleware. It may be different from other contexts. 85type ValidateContext struct { 86 *Base 87} 88 89// GetValidateContext gets a context for middleware form validation 90func GetValidateContext(req *http.Request) (ctx *ValidateContext) { 91 if ctxAPI, ok := req.Context().Value(apiContextKey).(*APIContext); ok { 92 ctx = &ValidateContext{Base: ctxAPI.Base} 93 } else if ctxWeb, ok := req.Context().Value(WebContextKey).(*Context); ok { 94 ctx = &ValidateContext{Base: ctxWeb.Base} 95 } else { 96 panic("invalid context, expect either APIContext or Context") 97 } 98 return ctx 99} 100 101func NewTemplateContextForWeb(ctx *Context) TemplateContext { 102 tmplCtx := NewTemplateContext(ctx) 103 tmplCtx["Locale"] = ctx.Locale 104 tmplCtx["AvatarUtils"] = templates.NewAvatarUtils(ctx) 105 return tmplCtx 106} 107 108func NewWebContext(base *Base, render Render, session session.Store) *Context { 109 ctx := &Context{ 110 Base: base, 111 Render: render, 112 Session: session, 113 114 Cache: mc.GetCache(), 115 Link: setting.AppSubURL + strings.TrimSuffix(base.Req.URL.EscapedPath(), "/"), 116 Repo: &Repository{PullRequest: &PullRequest{}}, 117 Org: &Organization{}, 118 } 119 ctx.TemplateContext = NewTemplateContextForWeb(ctx) 120 ctx.Flash = &middleware.Flash{DataStore: ctx, Values: url.Values{}} 121 return ctx 122} 123 124// Contexter initializes a classic context for a request. 125func Contexter() func(next http.Handler) http.Handler { 126 rnd := templates.HTMLRenderer() 127 csrfOpts := CsrfOptions{ 128 Secret: hex.EncodeToString(setting.GetGeneralTokenSigningSecret()), 129 Cookie: setting.CSRFCookieName, 130 Secure: setting.SessionConfig.Secure, 131 CookieHTTPOnly: setting.CSRFCookieHTTPOnly, 132 CookieDomain: setting.SessionConfig.Domain, 133 CookiePath: setting.SessionConfig.CookiePath, 134 SameSite: setting.SessionConfig.SameSite, 135 } 136 if !setting.IsProd { 137 CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose 138 } 139 return func(next http.Handler) http.Handler { 140 return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { 141 base, baseCleanUp := NewBaseContext(resp, req) 142 defer baseCleanUp() 143 ctx := NewWebContext(base, rnd, session.GetSession(req)) 144 145 ctx.Data.MergeFrom(middleware.CommonTemplateContextData()) 146 ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this 147 ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI() 148 ctx.Data["Link"] = ctx.Link 149 150 // PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules 151 ctx.PageData = map[string]any{} 152 ctx.Data["PageData"] = ctx.PageData 153 154 ctx.AppendContextValue(WebContextKey, ctx) 155 ctx.AppendContextValueFunc(gitrepo.RepositoryContextKey, func() any { return ctx.Repo.GitRepo }) 156 157 ctx.Csrf = NewCSRFProtector(csrfOpts) 158 159 // Get the last flash message from cookie 160 lastFlashCookie := middleware.GetSiteCookie(ctx.Req, CookieNameFlash) 161 if vals, _ := url.ParseQuery(lastFlashCookie); len(vals) > 0 { 162 // store last Flash message into the template data, to render it 163 ctx.Data["Flash"] = &middleware.Flash{ 164 DataStore: ctx, 165 Values: vals, 166 ErrorMsg: vals.Get("error"), 167 SuccessMsg: vals.Get("success"), 168 InfoMsg: vals.Get("info"), 169 WarningMsg: vals.Get("warning"), 170 } 171 } 172 173 // if there are new messages in the ctx.Flash, write them into cookie 174 ctx.Resp.Before(func(resp ResponseWriter) { 175 if val := ctx.Flash.Encode(); val != "" { 176 middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, val, 0) 177 } else if lastFlashCookie != "" { 178 middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, "", -1) 179 } 180 }) 181 182 // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid. 183 if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") { 184 if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size 185 ctx.ServerError("ParseMultipartForm", err) 186 return 187 } 188 } 189 190 httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform") 191 ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions) 192 193 ctx.Data["SystemConfig"] = setting.Config() 194 195 // FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these 196 ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations 197 ctx.Data["DisableStars"] = setting.Repository.DisableStars 198 ctx.Data["DisableForks"] = setting.Repository.DisableForks 199 ctx.Data["EnableActions"] = setting.Actions.Enabled 200 201 ctx.Data["ManifestData"] = setting.ManifestData 202 203 ctx.Data["UnitWikiGlobalDisabled"] = unit.TypeWiki.UnitGlobalDisabled() 204 ctx.Data["UnitIssuesGlobalDisabled"] = unit.TypeIssues.UnitGlobalDisabled() 205 ctx.Data["UnitPullsGlobalDisabled"] = unit.TypePullRequests.UnitGlobalDisabled() 206 ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled() 207 ctx.Data["UnitActionsGlobalDisabled"] = unit.TypeActions.UnitGlobalDisabled() 208 209 ctx.Data["AllLangs"] = translation.AllLangs() 210 211 next.ServeHTTP(ctx.Resp, ctx.Req) 212 }) 213 } 214} 215 216// HasError returns true if error occurs in form validation. 217// Attention: this function changes ctx.Data and ctx.Flash 218func (ctx *Context) HasError() bool { 219 hasErr, ok := ctx.Data["HasError"] 220 if !ok { 221 return false 222 } 223 ctx.Flash.ErrorMsg = ctx.GetErrMsg() 224 ctx.Data["Flash"] = ctx.Flash 225 return hasErr.(bool) 226} 227 228// GetErrMsg returns error message in form validation. 229func (ctx *Context) GetErrMsg() string { 230 msg, _ := ctx.Data["ErrorMsg"].(string) 231 if msg == "" { 232 msg = "invalid form data" 233 } 234 return msg 235} 236 237func (ctx *Context) JSONRedirect(redirect string) { 238 ctx.JSON(http.StatusOK, map[string]any{"redirect": redirect}) 239} 240 241func (ctx *Context) JSONOK() { 242 ctx.JSON(http.StatusOK, map[string]any{"ok": true}) // this is only a dummy response, frontend seldom uses it 243} 244 245func (ctx *Context) JSONError(msg any) { 246 switch v := msg.(type) { 247 case string: 248 ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "text"}) 249 case template.HTML: 250 ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "html"}) 251 default: 252 panic(fmt.Sprintf("unsupported type: %T", msg)) 253 } 254}