1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-23 02:38:35 +00:00

Refactor request context (#32956)

Introduce RequestContext: is a short-lived context that is used to store
request-specific data.

RequestContext could be used to clean form tmp files, close context git
repo, and do some tracing in the future.

Then a lot of legacy code could be removed or improved. For example:
most `ctx.Repo.GitRepo.Close()` could be removed because the git repo
could be closed when the request is done.
This commit is contained in:
wxiaoguang
2024-12-24 11:43:57 +08:00
committed by GitHub
parent 781c6df40f
commit 6d5aa9218e
34 changed files with 379 additions and 422 deletions

View File

@@ -4,7 +4,6 @@
package web
import (
goctx "context"
"fmt"
"net/http"
"reflect"
@@ -51,7 +50,6 @@ func (r *responseWriter) WriteHeader(statusCode int) {
var (
httpReqType = reflect.TypeOf((*http.Request)(nil))
respWriterType = reflect.TypeOf((*http.ResponseWriter)(nil)).Elem()
cancelFuncType = reflect.TypeOf((*goctx.CancelFunc)(nil)).Elem()
)
// preCheckHandler checks whether the handler is valid, developers could get first-time feedback, all mistakes could be found at startup
@@ -65,11 +63,8 @@ func preCheckHandler(fn reflect.Value, argsIn []reflect.Value) {
if !hasStatusProvider {
panic(fmt.Sprintf("handler should have at least one ResponseStatusProvider argument, but got %s", fn.Type()))
}
if fn.Type().NumOut() != 0 && fn.Type().NumIn() != 1 {
panic(fmt.Sprintf("handler should have no return value or only one argument, but got %s", fn.Type()))
}
if fn.Type().NumOut() == 1 && fn.Type().Out(0) != cancelFuncType {
panic(fmt.Sprintf("handler should return a cancel function, but got %s", fn.Type()))
if fn.Type().NumOut() != 0 {
panic(fmt.Sprintf("handler should have no return value other than registered ones, but got %s", fn.Type()))
}
}
@@ -105,16 +100,10 @@ func prepareHandleArgsIn(resp http.ResponseWriter, req *http.Request, fn reflect
return argsIn
}
func handleResponse(fn reflect.Value, ret []reflect.Value) goctx.CancelFunc {
if len(ret) == 1 {
if cancelFunc, ok := ret[0].Interface().(goctx.CancelFunc); ok {
return cancelFunc
}
panic(fmt.Sprintf("unsupported return type: %s", ret[0].Type()))
} else if len(ret) > 1 {
func handleResponse(fn reflect.Value, ret []reflect.Value) {
if len(ret) != 0 {
panic(fmt.Sprintf("unsupported return values: %s", fn.Type()))
}
return nil
}
func hasResponseBeenWritten(argsIn []reflect.Value) bool {
@@ -171,11 +160,8 @@ func toHandlerProvider(handler any) func(next http.Handler) http.Handler {
routing.UpdateFuncInfo(req.Context(), funcInfo)
ret := fn.Call(argsIn)
// handle the return value, and defer the cancel function if there is one
cancelFunc := handleResponse(fn, ret)
if cancelFunc != nil {
defer cancelFunc()
}
// handle the return value (no-op at the moment)
handleResponse(fn, ret)
// if the response has not been written, call the next handler
if next != nil && !hasResponseBeenWritten(argsIn) {

View File

@@ -7,46 +7,21 @@ import (
"context"
"time"
"code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting"
)
// ContextDataStore represents a data store
type ContextDataStore interface {
GetData() ContextData
}
type ContextData map[string]any
func (ds ContextData) GetData() ContextData {
return ds
}
func (ds ContextData) MergeFrom(other ContextData) ContextData {
for k, v := range other {
ds[k] = v
}
return ds
}
const ContextDataKeySignedUser = "SignedUser"
type contextDataKeyType struct{}
var contextDataKey contextDataKeyType
func WithContextData(c context.Context) context.Context {
return context.WithValue(c, contextDataKey, make(ContextData, 10))
}
func GetContextData(c context.Context) ContextData {
if ds, ok := c.Value(contextDataKey).(ContextData); ok {
return ds
func GetContextData(c context.Context) reqctx.ContextData {
if rc := reqctx.GetRequestDataStore(c); rc != nil {
return rc.GetData()
}
return nil
}
func CommonTemplateContextData() ContextData {
return ContextData{
func CommonTemplateContextData() reqctx.ContextData {
return reqctx.ContextData{
"IsLandingPageOrganizations": setting.LandingPageURL == setting.LandingPageOrganizations,
"ShowRegistrationButton": setting.Service.ShowRegistrationButton,

View File

@@ -7,11 +7,13 @@ import (
"fmt"
"html/template"
"net/url"
"code.gitea.io/gitea/modules/reqctx"
)
// Flash represents a one time data transfer between two requests.
type Flash struct {
DataStore ContextDataStore
DataStore reqctx.RequestDataStore
url.Values
ErrorMsg, WarningMsg, InfoMsg, SuccessMsg string
}

View File

@@ -10,6 +10,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/htmlutil"
"code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"
@@ -29,12 +30,12 @@ func Bind[T any](_ T) http.HandlerFunc {
}
// SetForm set the form object
func SetForm(dataStore middleware.ContextDataStore, obj any) {
func SetForm(dataStore reqctx.ContextDataProvider, obj any) {
dataStore.GetData()["__form"] = obj
}
// GetForm returns the validate form information
func GetForm(dataStore middleware.ContextDataStore) any {
func GetForm(dataStore reqctx.RequestDataStore) any {
return dataStore.GetData()["__form"]
}