Use MarkLongPolling instead of hard-coded route path (#37427)
This commit is contained in:
@@ -87,6 +87,7 @@ func getViteDevProxy() *httputil.ReverseProxy {
|
|||||||
// the Vite dev server port from the port file written by the viteDevServerPortPlugin.
|
// the Vite dev server port from the port file written by the viteDevServerPortPlugin.
|
||||||
// It is needed because there are container-based development, only Gitea web server's port is exposed.
|
// It is needed because there are container-based development, only Gitea web server's port is exposed.
|
||||||
func ViteDevMiddleware(next http.Handler) http.Handler {
|
func ViteDevMiddleware(next http.Handler) http.Handler {
|
||||||
|
markLongPolling := routing.MarkLongPolling()
|
||||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||||
if !isViteDevRequest(req) {
|
if !isViteDevRequest(req) {
|
||||||
next.ServeHTTP(resp, req)
|
next.ServeHTTP(resp, req)
|
||||||
@@ -97,8 +98,7 @@ func ViteDevMiddleware(next http.Handler) http.Handler {
|
|||||||
next.ServeHTTP(resp, req)
|
next.ServeHTTP(resp, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
routing.MarkLongPolling(resp, req)
|
markLongPolling(proxy).ServeHTTP(resp, req)
|
||||||
proxy.ServeHTTP(resp, req)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,18 +13,12 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/reqctx"
|
"code.gitea.io/gitea/modules/reqctx"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
|
"code.gitea.io/gitea/modules/web/types"
|
||||||
|
|
||||||
"gitea.com/go-chi/binding"
|
"gitea.com/go-chi/binding"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PreMiddlewareProvider is a special middleware provider which will be executed
|
|
||||||
// before other middlewares on the same "routing" level (AfterRouting/Group/Methods/Any, but not BeforeRouting).
|
|
||||||
// A route can do something (e.g.: set middleware options) at the place where it is declared,
|
|
||||||
// and the code will be executed before other middlewares which are added before the declaration.
|
|
||||||
// Use cases: mark a route with some meta info, set some options for middlewares, etc.
|
|
||||||
type PreMiddlewareProvider func(next http.Handler) http.Handler
|
|
||||||
|
|
||||||
// Bind binding an obj to a handler's context data
|
// Bind binding an obj to a handler's context data
|
||||||
func Bind[T any](_ T) http.HandlerFunc {
|
func Bind[T any](_ T) http.HandlerFunc {
|
||||||
return func(resp http.ResponseWriter, req *http.Request) {
|
return func(resp http.ResponseWriter, req *http.Request) {
|
||||||
@@ -112,7 +106,7 @@ func isNilOrFuncNil(v any) bool {
|
|||||||
|
|
||||||
func wrapMiddlewareAppendPre(all []middlewareProvider, middlewares []any) []middlewareProvider {
|
func wrapMiddlewareAppendPre(all []middlewareProvider, middlewares []any) []middlewareProvider {
|
||||||
for _, m := range middlewares {
|
for _, m := range middlewares {
|
||||||
if h, ok := m.(PreMiddlewareProvider); ok && h != nil {
|
if h, ok := m.(types.PreMiddlewareProvider); ok && h != nil {
|
||||||
all = append(all, toHandlerProvider(middlewareProvider(h)))
|
all = append(all, toHandlerProvider(middlewareProvider(h)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,7 +115,7 @@ func wrapMiddlewareAppendPre(all []middlewareProvider, middlewares []any) []midd
|
|||||||
|
|
||||||
func wrapMiddlewareAppendNormal(all []middlewareProvider, middlewares []any) []middlewareProvider {
|
func wrapMiddlewareAppendNormal(all []middlewareProvider, middlewares []any) []middlewareProvider {
|
||||||
for _, m := range middlewares {
|
for _, m := range middlewares {
|
||||||
if _, ok := m.(PreMiddlewareProvider); !ok && !isNilOrFuncNil(m) {
|
if _, ok := m.(types.PreMiddlewareProvider); !ok && !isNilOrFuncNil(m) {
|
||||||
all = append(all, toHandlerProvider(m))
|
all = append(all, toHandlerProvider(m))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
"code.gitea.io/gitea/modules/web/types"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -312,12 +313,12 @@ func TestPreMiddlewareProvider(t *testing.T) {
|
|||||||
root := NewRouter()
|
root := NewRouter()
|
||||||
root.BeforeRouting(h("before-root"))
|
root.BeforeRouting(h("before-root"))
|
||||||
root.AfterRouting(h("root"))
|
root.AfterRouting(h("root"))
|
||||||
root.Get("/a/1", h("mid"), PreMiddlewareProvider(p("pre-root")), h("end1"))
|
root.Get("/a/1", h("mid"), types.PreMiddlewareProvider(p("pre-root")), h("end1"))
|
||||||
|
|
||||||
sub := NewRouter()
|
sub := NewRouter()
|
||||||
sub.BeforeRouting(h("before-sub"))
|
sub.BeforeRouting(h("before-sub"))
|
||||||
sub.AfterRouting(h("sub"))
|
sub.AfterRouting(h("sub"))
|
||||||
sub.Get("/2", h("mid"), PreMiddlewareProvider(p("pre-sub")), h("end2"))
|
sub.Get("/2", h("mid"), types.PreMiddlewareProvider(p("pre-sub")), h("end2"))
|
||||||
sub.NotFound(h("not-found"))
|
sub.NotFound(h("not-found"))
|
||||||
|
|
||||||
root.Mount("/a", sub)
|
root.Mount("/a", sub)
|
||||||
|
|||||||
@@ -10,12 +10,18 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/gtprof"
|
"code.gitea.io/gitea/modules/gtprof"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/reqctx"
|
"code.gitea.io/gitea/modules/reqctx"
|
||||||
|
"code.gitea.io/gitea/modules/web/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type contextKeyType struct{}
|
type contextKeyType struct{}
|
||||||
|
|
||||||
var contextKey contextKeyType
|
var contextKey contextKeyType
|
||||||
|
|
||||||
|
func getRequestRecord(ctx context.Context) *requestRecord {
|
||||||
|
record, _ := ctx.Value(contextKey).(*requestRecord)
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
|
||||||
// RecordFuncInfo records a func info into context
|
// RecordFuncInfo records a func info into context
|
||||||
func RecordFuncInfo(ctx context.Context, funcInfo *FuncInfo) (end func()) {
|
func RecordFuncInfo(ctx context.Context, funcInfo *FuncInfo) (end func()) {
|
||||||
end = func() {}
|
end = func() {}
|
||||||
@@ -24,7 +30,7 @@ func RecordFuncInfo(ctx context.Context, funcInfo *FuncInfo) (end func()) {
|
|||||||
traceSpan, end = gtprof.GetTracer().StartInContext(reqCtx, "http.func")
|
traceSpan, end = gtprof.GetTracer().StartInContext(reqCtx, "http.func")
|
||||||
traceSpan.SetAttributeString("func", funcInfo.shortName)
|
traceSpan.SetAttributeString("func", funcInfo.shortName)
|
||||||
}
|
}
|
||||||
if record, ok := ctx.Value(contextKey).(*requestRecord); ok {
|
if record := getRequestRecord(ctx); record != nil {
|
||||||
record.lock.Lock()
|
record.lock.Lock()
|
||||||
record.funcInfo = funcInfo
|
record.funcInfo = funcInfo
|
||||||
record.lock.Unlock()
|
record.lock.Unlock()
|
||||||
@@ -32,22 +38,39 @@ func RecordFuncInfo(ctx context.Context, funcInfo *FuncInfo) (end func()) {
|
|||||||
return end
|
return end
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkLongPolling marks the request is a long-polling request, and the logger may output different message for it
|
func GetRequestRecordInfo(reqCtx context.Context) (ret struct {
|
||||||
func MarkLongPolling(resp http.ResponseWriter, req *http.Request) {
|
HasRecord bool
|
||||||
record, ok := req.Context().Value(contextKey).(*requestRecord)
|
IsLongPolling bool
|
||||||
if !ok {
|
},
|
||||||
return
|
) {
|
||||||
|
record := getRequestRecord(reqCtx)
|
||||||
|
if record == nil {
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
ret.HasRecord = true
|
||||||
|
record.lock.RLock()
|
||||||
|
ret.IsLongPolling = record.isLongPolling
|
||||||
|
record.lock.RUnlock()
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
record.lock.Lock()
|
// MarkLongPolling marks the request is a long-polling request, and the logger may output different message for it
|
||||||
record.isLongPolling = true
|
func MarkLongPolling() types.PreMiddlewareProvider {
|
||||||
record.logLevel = log.TRACE
|
return func(next http.Handler) http.Handler {
|
||||||
record.lock.Unlock()
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
record := getRequestRecord(req.Context()) // it must exist
|
||||||
|
record.lock.Lock()
|
||||||
|
record.isLongPolling = true
|
||||||
|
record.logLevel = log.TRACE
|
||||||
|
record.lock.Unlock()
|
||||||
|
next.ServeHTTP(w, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarkLogLevelTrace(resp http.ResponseWriter, req *http.Request) {
|
func MarkLogLevelTrace(resp http.ResponseWriter, req *http.Request) {
|
||||||
record, ok := req.Context().Value(contextKey).(*requestRecord)
|
record := getRequestRecord(req.Context())
|
||||||
if !ok {
|
if record == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,8 +81,8 @@ func MarkLogLevelTrace(resp http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
// UpdatePanicError updates a context's error info, a panic may be recovered by other middlewares, but we still need to know that.
|
// UpdatePanicError updates a context's error info, a panic may be recovered by other middlewares, but we still need to know that.
|
||||||
func UpdatePanicError(ctx context.Context, err error) {
|
func UpdatePanicError(ctx context.Context, err error) {
|
||||||
record, ok := ctx.Value(contextKey).(*requestRecord)
|
record := getRequestRecord(ctx)
|
||||||
if !ok {
|
if record == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
13
modules/web/types/premiddleware.go
Normal file
13
modules/web/types/premiddleware.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// PreMiddlewareProvider is a special middleware provider which will be executed
|
||||||
|
// before other middlewares on the same "routing" level (AfterRouting/Group/Methods/Any, but not BeforeRouting).
|
||||||
|
// A route can do something (e.g.: set middleware options) at the place where it is declared,
|
||||||
|
// and the code will be executed before other middlewares which are added before the declaration.
|
||||||
|
// Use cases: mark a route with some meta info, set some options for middlewares, etc.
|
||||||
|
type PreMiddlewareProvider func(next http.Handler) http.Handler
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/reqctx"
|
"code.gitea.io/gitea/modules/reqctx"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
|
"code.gitea.io/gitea/modules/web/routing"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
@@ -71,10 +72,6 @@ func isRoutePathExpensive(routePattern string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func isRoutePathForLongPolling(routePattern string) bool {
|
|
||||||
return routePattern == "/user/events"
|
|
||||||
}
|
|
||||||
|
|
||||||
func determineRequestPriority(reqCtx reqctx.RequestContext) (ret struct {
|
func determineRequestPriority(reqCtx reqctx.RequestContext) (ret struct {
|
||||||
SignedIn bool
|
SignedIn bool
|
||||||
Expensive bool
|
Expensive bool
|
||||||
@@ -86,7 +83,7 @@ func determineRequestPriority(reqCtx reqctx.RequestContext) (ret struct {
|
|||||||
ret.SignedIn = true
|
ret.SignedIn = true
|
||||||
} else {
|
} else {
|
||||||
ret.Expensive = isRoutePathExpensive(chiRoutePath)
|
ret.Expensive = isRoutePathExpensive(chiRoutePath)
|
||||||
ret.LongPolling = isRoutePathForLongPolling(chiRoutePath)
|
ret.LongPolling = routing.GetRequestRecordInfo(reqCtx).IsLongPolling
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,4 @@ func TestBlockExpensive(t *testing.T) {
|
|||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
assert.Equal(t, c.expensive, isRoutePathExpensive(c.routePath), "routePath: %s", c.routePath)
|
assert.Equal(t, c.expensive, isRoutePathExpensive(c.routePath), "routePath: %s", c.routePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.True(t, isRoutePathForLongPolling("/user/events"))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
|
"code.gitea.io/gitea/modules/web/routing"
|
||||||
|
|
||||||
"github.com/bohde/codel"
|
"github.com/bohde/codel"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
@@ -68,7 +69,7 @@ func QoS() func(next http.Handler) http.Handler {
|
|||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
|
reqRecordInfo := routing.GetRequestRecordInfo(ctx)
|
||||||
priority := requestPriority(ctx)
|
priority := requestPriority(ctx)
|
||||||
|
|
||||||
// Check if the request can begin processing.
|
// Check if the request can begin processing.
|
||||||
@@ -79,9 +80,8 @@ func QoS() func(next http.Handler) http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release long-polling immediately, so they don't always
|
// Release long-polling immediately, so they don't always take up an in-flight request
|
||||||
// take up an in-flight request
|
if reqRecordInfo.IsLongPolling {
|
||||||
if strings.Contains(req.URL.Path, "/user/events") {
|
|
||||||
c.Release()
|
c.Release()
|
||||||
} else {
|
} else {
|
||||||
defer c.Release()
|
defer c.Release()
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
"code.gitea.io/gitea/modules/web/routing"
|
"code.gitea.io/gitea/modules/web/routing"
|
||||||
|
"code.gitea.io/gitea/modules/web/types"
|
||||||
"code.gitea.io/gitea/routers/common"
|
"code.gitea.io/gitea/routers/common"
|
||||||
"code.gitea.io/gitea/routers/web/admin"
|
"code.gitea.io/gitea/routers/web/admin"
|
||||||
"code.gitea.io/gitea/routers/web/auth"
|
"code.gitea.io/gitea/routers/web/auth"
|
||||||
@@ -91,8 +92,8 @@ func optionsCorsHandler() func(next http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AuthMiddleware struct {
|
type AuthMiddleware struct {
|
||||||
AllowOAuth2 web.PreMiddlewareProvider
|
AllowOAuth2 types.PreMiddlewareProvider
|
||||||
AllowBasic web.PreMiddlewareProvider
|
AllowBasic types.PreMiddlewareProvider
|
||||||
MiddlewareHandler func(*context.Context)
|
MiddlewareHandler func(*context.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +102,7 @@ func newWebAuthMiddleware() *AuthMiddleware {
|
|||||||
type keyAllowBasic struct{}
|
type keyAllowBasic struct{}
|
||||||
webAuth := &AuthMiddleware{}
|
webAuth := &AuthMiddleware{}
|
||||||
|
|
||||||
middlewareSetContextValue := func(key, val any) web.PreMiddlewareProvider {
|
middlewareSetContextValue := func(key, val any) types.PreMiddlewareProvider {
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
dataStore := reqctx.GetRequestDataStore(r.Context())
|
dataStore := reqctx.GetRequestDataStore(r.Context())
|
||||||
@@ -588,7 +589,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
|||||||
})
|
})
|
||||||
}, reqSignOut)
|
}, reqSignOut)
|
||||||
|
|
||||||
m.Any("/user/events", routing.MarkLongPolling, events.Events)
|
m.Any("/user/events", routing.MarkLongPolling(), events.Events)
|
||||||
|
|
||||||
m.Group("/login/oauth", func() {
|
m.Group("/login/oauth", func() {
|
||||||
m.Group("", func() {
|
m.Group("", func() {
|
||||||
|
|||||||
@@ -177,6 +177,8 @@ func TestRequireSignInView(t *testing.T) {
|
|||||||
require.False(t, setting.Service.BlockAnonymousAccessExpensive)
|
require.False(t, setting.Service.BlockAnonymousAccessExpensive)
|
||||||
req := NewRequest(t, "GET", "/user2/repo1/src/branch/master")
|
req := NewRequest(t, "GET", "/user2/repo1/src/branch/master")
|
||||||
MakeRequest(t, req, http.StatusOK)
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
req = NewRequest(t, "GET", "/user/events")
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
})
|
})
|
||||||
t.Run("RequireSignInView", func(t *testing.T) {
|
t.Run("RequireSignInView", func(t *testing.T) {
|
||||||
defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)()
|
defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)()
|
||||||
@@ -192,6 +194,8 @@ func TestRequireSignInView(t *testing.T) {
|
|||||||
|
|
||||||
req := NewRequest(t, "GET", "/user2/repo1")
|
req := NewRequest(t, "GET", "/user2/repo1")
|
||||||
MakeRequest(t, req, http.StatusOK)
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
req = NewRequest(t, "GET", "/user/events")
|
||||||
|
MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
|
||||||
req = NewRequest(t, "GET", "/user2/repo1/src/branch/master")
|
req = NewRequest(t, "GET", "/user2/repo1/src/branch/master")
|
||||||
resp := MakeRequest(t, req, http.StatusSeeOther)
|
resp := MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
|||||||
Reference in New Issue
Block a user