1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-22 18:28:37 +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

@@ -12,8 +12,8 @@ import (
"testing"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/web/middleware"
"github.com/stretchr/testify/assert"
)
@@ -21,7 +21,7 @@ import (
func TestRenderPanicErrorPage(t *testing.T) {
w := httptest.NewRecorder()
req := &http.Request{URL: &url.URL{}}
req = req.WithContext(middleware.WithContextData(context.Background()))
req = req.WithContext(reqctx.NewRequestContextForTest(context.Background()))
RenderPanicErrorPage(w, req, errors.New("fake panic error (for test only)"))
respContent := w.Body.String()
assert.Contains(t, respContent, `class="page-content status-page-500"`)

View File

@@ -4,16 +4,14 @@
package common
import (
go_context "context"
"fmt"
"net/http"
"strings"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/modules/web/routing"
"code.gitea.io/gitea/services/context"
@@ -24,54 +22,12 @@ import (
// ProtocolMiddlewares returns HTTP protocol related middlewares, and it provides a global panic recovery
func ProtocolMiddlewares() (handlers []any) {
// make sure chi uses EscapedPath(RawPath) as RoutePath, then "%2f" could be handled correctly
handlers = append(handlers, func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
ctx := chi.RouteContext(req.Context())
if req.URL.RawPath == "" {
ctx.RoutePath = req.URL.EscapedPath()
} else {
ctx.RoutePath = req.URL.RawPath
}
next.ServeHTTP(resp, req)
})
})
// the order is important
handlers = append(handlers, ChiRoutePathHandler()) // make sure chi has correct paths
handlers = append(handlers, RequestContextHandler()) // // prepare the context and panic recovery
// prepare the ContextData and panic recovery
handlers = append(handlers, func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
defer func() {
if err := recover(); err != nil {
RenderPanicErrorPage(resp, req, err) // it should never panic
}
}()
req = req.WithContext(middleware.WithContextData(req.Context()))
req = req.WithContext(go_context.WithValue(req.Context(), httplib.RequestContextKey, req))
next.ServeHTTP(resp, req)
})
})
// wrap the request and response, use the process context and add it to the process manager
handlers = append(handlers, func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
ctx, _, finished := process.GetManager().AddTypedContext(req.Context(), fmt.Sprintf("%s: %s", req.Method, req.RequestURI), process.RequestProcessType, true)
defer finished()
next.ServeHTTP(context.WrapResponseWriter(resp), req.WithContext(cache.WithCacheContext(ctx)))
})
})
if setting.ReverseProxyLimit > 0 {
opt := proxy.NewForwardedHeadersOptions().
WithForwardLimit(setting.ReverseProxyLimit).
ClearTrustedProxies()
for _, n := range setting.ReverseProxyTrustedProxies {
if !strings.Contains(n, "/") {
opt.AddTrustedProxy(n)
} else {
opt.AddTrustedNetwork(n)
}
}
handlers = append(handlers, proxy.ForwardedHeaders(opt))
if setting.ReverseProxyLimit > 0 && len(setting.ReverseProxyTrustedProxies) > 0 {
handlers = append(handlers, ForwardedHeadersHandler(setting.ReverseProxyLimit, setting.ReverseProxyTrustedProxies))
}
if setting.IsRouteLogEnabled() {
@@ -85,6 +41,59 @@ func ProtocolMiddlewares() (handlers []any) {
return handlers
}
func RequestContextHandler() func(h http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
profDesc := fmt.Sprintf("%s: %s", req.Method, req.RequestURI)
ctx, finished := reqctx.NewRequestContext(req.Context(), profDesc)
defer finished()
defer func() {
if err := recover(); err != nil {
RenderPanicErrorPage(resp, req, err) // it should never panic
}
}()
ds := reqctx.GetRequestDataStore(ctx)
req = req.WithContext(cache.WithCacheContext(ctx))
ds.SetContextValue(httplib.RequestContextKey, req)
ds.AddCleanUp(func() {
if req.MultipartForm != nil {
_ = req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
}
})
next.ServeHTTP(context.WrapResponseWriter(resp), req)
})
}
}
func ChiRoutePathHandler() func(h http.Handler) http.Handler {
// make sure chi uses EscapedPath(RawPath) as RoutePath, then "%2f" could be handled correctly
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
ctx := chi.RouteContext(req.Context())
if req.URL.RawPath == "" {
ctx.RoutePath = req.URL.EscapedPath()
} else {
ctx.RoutePath = req.URL.RawPath
}
next.ServeHTTP(resp, req)
})
}
}
func ForwardedHeadersHandler(limit int, trustedProxies []string) func(h http.Handler) http.Handler {
opt := proxy.NewForwardedHeadersOptions().WithForwardLimit(limit).ClearTrustedProxies()
for _, n := range trustedProxies {
if !strings.Contains(n, "/") {
opt.AddTrustedProxy(n)
} else {
opt.AddTrustedNetwork(n)
}
}
return proxy.ForwardedHeaders(opt)
}
func Sessioner() func(next http.Handler) http.Handler {
return session.Sessioner(session.Options{
Provider: setting.SessionConfig.Provider,