forked from forgejo/forgejo
Decouple the different contexts from each other (#24786)
Replace #16455 Close #21803 Mixing different Gitea contexts together causes some problems: 1. Unable to respond proper content when error occurs, eg: Web should respond HTML while API should respond JSON 2. Unclear dependency, eg: it's unclear when Context is used in APIContext, which fields should be initialized, which methods are necessary. To make things clear, this PR introduces a Base context, it only provides basic Req/Resp/Data features. This PR mainly moves code. There are still many legacy problems and TODOs in code, leave unrelated changes to future PRs.
This commit is contained in:
parent
6ba4f89723
commit
6b33152b7d
57 changed files with 885 additions and 781 deletions
|
@ -5,7 +5,6 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"html"
|
||||
"html/template"
|
||||
"io"
|
||||
|
@ -36,38 +35,27 @@ type Render interface {
|
|||
|
||||
// Context represents context of a request.
|
||||
type Context struct {
|
||||
Resp ResponseWriter
|
||||
Req *http.Request
|
||||
Render Render
|
||||
*Base
|
||||
|
||||
Data middleware.ContextData // data used by MVC templates
|
||||
PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData`
|
||||
Render Render
|
||||
PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData`
|
||||
|
||||
Locale translation.Locale
|
||||
Cache cache.Cache
|
||||
Csrf CSRFProtector
|
||||
Flash *middleware.Flash
|
||||
Session session.Store
|
||||
|
||||
Link string // current request URL (without query string)
|
||||
Doer *user_model.User
|
||||
Link string // current request URL (without query string)
|
||||
|
||||
Doer *user_model.User // current signed-in user
|
||||
IsSigned bool
|
||||
IsBasicAuth bool
|
||||
|
||||
ContextUser *user_model.User
|
||||
Repo *Repository
|
||||
Org *Organization
|
||||
Package *Package
|
||||
}
|
||||
ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer
|
||||
|
||||
// Close frees all resources hold by Context
|
||||
func (ctx *Context) Close() error {
|
||||
var err error
|
||||
if ctx.Req != nil && ctx.Req.MultipartForm != nil {
|
||||
err = ctx.Req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
|
||||
}
|
||||
// TODO: close opened repo, and more
|
||||
return err
|
||||
Repo *Repository
|
||||
Org *Organization
|
||||
Package *Package
|
||||
}
|
||||
|
||||
// TrHTMLEscapeArgs runs ".Locale.Tr()" but pre-escapes all arguments with html.EscapeString.
|
||||
|
@ -80,55 +68,30 @@ func (ctx *Context) TrHTMLEscapeArgs(msg string, args ...string) string {
|
|||
return ctx.Locale.Tr(msg, trArgs...)
|
||||
}
|
||||
|
||||
func (ctx *Context) Tr(msg string, args ...any) string {
|
||||
return ctx.Locale.Tr(msg, args...)
|
||||
}
|
||||
|
||||
func (ctx *Context) TrN(cnt any, key1, keyN string, args ...any) string {
|
||||
return ctx.Locale.TrN(cnt, key1, keyN, args...)
|
||||
}
|
||||
|
||||
// Deadline is part of the interface for context.Context and we pass this to the request context
|
||||
func (ctx *Context) Deadline() (deadline time.Time, ok bool) {
|
||||
return ctx.Req.Context().Deadline()
|
||||
}
|
||||
|
||||
// Done is part of the interface for context.Context and we pass this to the request context
|
||||
func (ctx *Context) Done() <-chan struct{} {
|
||||
return ctx.Req.Context().Done()
|
||||
}
|
||||
|
||||
// Err is part of the interface for context.Context and we pass this to the request context
|
||||
func (ctx *Context) Err() error {
|
||||
return ctx.Req.Context().Err()
|
||||
}
|
||||
|
||||
// Value is part of the interface for context.Context and we pass this to the request context
|
||||
func (ctx *Context) Value(key interface{}) interface{} {
|
||||
if key == git.RepositoryContextKey && ctx.Repo != nil {
|
||||
return ctx.Repo.GitRepo
|
||||
}
|
||||
if key == translation.ContextKey && ctx.Locale != nil {
|
||||
return ctx.Locale
|
||||
}
|
||||
return ctx.Req.Context().Value(key)
|
||||
}
|
||||
|
||||
type contextKeyType struct{}
|
||||
|
||||
var contextKey interface{} = contextKeyType{}
|
||||
|
||||
// WithContext set up install context in request
|
||||
func WithContext(req *http.Request, ctx *Context) *http.Request {
|
||||
return req.WithContext(context.WithValue(req.Context(), contextKey, ctx))
|
||||
func GetContext(req *http.Request) *Context {
|
||||
ctx, _ := req.Context().Value(contextKey).(*Context)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// GetContext retrieves install context from request
|
||||
func GetContext(req *http.Request) *Context {
|
||||
if ctx, ok := req.Context().Value(contextKey).(*Context); ok {
|
||||
return ctx
|
||||
// ValidateContext is a special context for form validation middleware. It may be different from other contexts.
|
||||
type ValidateContext struct {
|
||||
*Base
|
||||
}
|
||||
|
||||
// GetValidateContext gets a context for middleware form validation
|
||||
func GetValidateContext(req *http.Request) (ctx *ValidateContext) {
|
||||
if ctxAPI, ok := req.Context().Value(apiContextKey).(*APIContext); ok {
|
||||
ctx = &ValidateContext{Base: ctxAPI.Base}
|
||||
} else if ctxWeb, ok := req.Context().Value(contextKey).(*Context); ok {
|
||||
ctx = &ValidateContext{Base: ctxWeb.Base}
|
||||
} else {
|
||||
panic("invalid context, expect either APIContext or Context")
|
||||
}
|
||||
return nil
|
||||
return ctx
|
||||
}
|
||||
|
||||
// Contexter initializes a classic context for a request.
|
||||
|
@ -150,20 +113,17 @@ func Contexter() func(next http.Handler) http.Handler {
|
|||
}
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
ctx := Context{
|
||||
Resp: NewResponse(resp),
|
||||
base, baseCleanUp := NewBaseContext(resp, req)
|
||||
ctx := &Context{
|
||||
Base: base,
|
||||
Cache: mc.GetCache(),
|
||||
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: middleware.GetContextData(req.Context()),
|
||||
Repo: &Repository{PullRequest: &PullRequest{}},
|
||||
Org: &Organization{},
|
||||
}
|
||||
defer ctx.Close()
|
||||
defer baseCleanUp()
|
||||
|
||||
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
|
||||
ctx.Data["Context"] = &ctx
|
||||
|
@ -175,15 +135,17 @@ func Contexter() func(next http.Handler) http.Handler {
|
|||
ctx.PageData = map[string]any{}
|
||||
ctx.Data["PageData"] = ctx.PageData
|
||||
|
||||
ctx.Req = WithContext(req, &ctx)
|
||||
ctx.Csrf = PrepareCSRFProtector(csrfOpts, &ctx)
|
||||
ctx.Base.AppendContextValue(contextKey, ctx)
|
||||
ctx.Base.AppendContextValueFunc(git.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })
|
||||
|
||||
ctx.Csrf = PrepareCSRFProtector(csrfOpts, ctx)
|
||||
|
||||
// Get the last flash message from cookie
|
||||
lastFlashCookie := middleware.GetSiteCookie(ctx.Req, CookieNameFlash)
|
||||
if vals, _ := url.ParseQuery(lastFlashCookie); len(vals) > 0 {
|
||||
// store last Flash message into the template data, to render it
|
||||
ctx.Data["Flash"] = &middleware.Flash{
|
||||
DataStore: &ctx,
|
||||
DataStore: ctx,
|
||||
Values: vals,
|
||||
ErrorMsg: vals.Get("error"),
|
||||
SuccessMsg: vals.Get("success"),
|
||||
|
@ -193,7 +155,7 @@ func Contexter() func(next http.Handler) http.Handler {
|
|||
}
|
||||
|
||||
// prepare an empty Flash message for current request
|
||||
ctx.Flash = &middleware.Flash{DataStore: &ctx, Values: url.Values{}}
|
||||
ctx.Flash = &middleware.Flash{DataStore: ctx, Values: url.Values{}}
|
||||
ctx.Resp.Before(func(resp ResponseWriter) {
|
||||
if val := ctx.Flash.Encode(); val != "" {
|
||||
middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, val, 0)
|
||||
|
@ -235,3 +197,24 @@ func Contexter() func(next http.Handler) http.Handler {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// HasError returns true if error occurs in form validation.
|
||||
// Attention: this function changes ctx.Data and ctx.Flash
|
||||
func (ctx *Context) HasError() bool {
|
||||
hasErr, ok := ctx.Data["HasError"]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
ctx.Flash.ErrorMsg = ctx.GetErrMsg()
|
||||
ctx.Data["Flash"] = ctx.Flash
|
||||
return hasErr.(bool)
|
||||
}
|
||||
|
||||
// GetErrMsg returns error message in form validation.
|
||||
func (ctx *Context) GetErrMsg() string {
|
||||
msg, _ := ctx.Data["ErrorMsg"].(string)
|
||||
if msg == "" {
|
||||
msg = "invalid form data"
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue