Skip to content

use route pattern #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 52 additions & 58 deletions routers/common/qos.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,27 @@
package common

import (
"fmt"
"net/http"
"slices"
"strings"

user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"

"github.com/bohde/codel"
"github.com/go-chi/chi/v5"
)

type Priority int

func (p Priority) String() string {
switch p {
case HighPriority:
return "high"
case DefaultPriority:
return "default"
case LowPriority:
return "low"
default:
return fmt.Sprintf("%d", p)
// QoS implements quality of service for requests, based upon
// whether the user is logged in. All traffic may get dropped,
// and anonymous users are deprioritized.
func QoS() func(next http.Handler) http.Handler {
if !setting.Service.QoS.Enabled {
return nil
}
}

const (
LowPriority = Priority(-10)
DefaultPriority = Priority(0)
HighPriority = Priority(10)
)

// QoS implements quality of service for requests, based upon whether
// or not the user is logged in. All traffic may get dropped, and
// anonymous users are deprioritized.
func QoS() func(next http.Handler) http.Handler {
maxOutstanding := setting.Service.QoS.MaxInFlightRequests
if maxOutstanding <= 0 {
maxOutstanding = 10
Expand All @@ -57,30 +42,18 @@ func QoS() func(next http.Handler) http.Handler {

return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()

priority := DefaultPriority

// If the user is logged in, assign high priority.
data := middleware.GetContextData(req.Context())
if _, ok := data[middleware.ContextDataKeySignedUser].(*user_model.User); ok {
priority = HighPriority
} else if IsGitContents(req.URL.Path) {
// Otherwise, if the path would is accessing git contents directly, mark as low priority
priority = LowPriority
}
priority, longPolling := determineRequestPriority(reqctx.FromContext(req.Context()))

// Check if the request can begin processing.
err := c.Acquire(ctx, int(priority))
err := c.Acquire(req.Context(), priority)
if err != nil {
// If it failed, the service is over capacity and should error
http.Error(w, "Service Unavailable", http.StatusServiceUnavailable)
http.Error(w, "Service Unavailable (QoS)", http.StatusServiceUnavailable)
return
}

// Release long-polling immediately, so they don't always
// take up an in-flight request
if strings.Contains(req.URL.Path, "/user/events") {
if longPolling {
// Release long-polling immediately, so they don't always take up an in-flight request
c.Release()
} else {
defer c.Release()
Expand All @@ -91,31 +64,52 @@ func QoS() func(next http.Handler) http.Handler {
}
}

func IsGitContents(path string) bool {
func isRoutePathLowPriority(routePattern string) bool {
subPath, ok := strings.CutPrefix(routePattern, "/{username}/{reponame}/")
if !ok {
return false
}
subPath, _, _ = strings.Cut(subPath, "/")
parts := []string{
"refs",
"activity",
"archive",
"commit",
"graph",
"blame",
"branches",
"tags",
"labels",
"stars",
"search",
"activity",
"wiki",
"watchers",
"commit",
"commits",
"compare",
"graph",
"labels",
"media",
"raw",
"search",
"src",
"commits",
"stars",
"tags",
"watchers",
"wiki",
}
return slices.Contains(parts, subPath)
}

func isRoutePathForLongPolling(routePattern string) bool {
return routePattern == "/user/events"
}

// TODO: add some tests

for _, p := range parts {
if strings.Contains(path, p) {
return true
}
func determineRequestPriority(reqCtx reqctx.RequestContext) (priority int, longPolling bool) {
const priorityLow = -10
const priorityHigh = 10

chiRoutePath := chi.RouteContext(reqCtx).RoutePattern()
if _, ok := reqCtx.GetData()[middleware.ContextDataKeySignedUser].(*user_model.User); ok {
// If the user is logged in, assign high priority.
priority = priorityHigh
} else if isRoutePathLowPriority(chiRoutePath) {
// Otherwise, if the path would is accessing git contents directly, mark as low priority
priority = priorityLow
}
return false

return priority, isRoutePathForLongPolling(chiRoutePath)
}
16 changes: 6 additions & 10 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,10 +272,6 @@ func Routes() *web.Router {
// Get user from session if logged in.
mid = append(mid, webAuth(buildAuthGroup()))

if setting.Service.QoS.Enabled {
mid = append(mid, common.QoS())
}

// GetHead allows a HEAD request redirect to GET if HEAD method is not defined for that route
mid = append(mid, chi_middleware.GetHead)

Expand All @@ -289,17 +285,17 @@ func Routes() *web.Router {
mid = append(mid, repo.GetActiveStopwatch)
mid = append(mid, goGet)

others := web.NewRouter()
others.Use(mid...)
registerRoutes(others)
routes.Mount("", others)
webRoutes := web.NewRouter()
webRoutes.Use(mid...)
webRoutes.Group("", func() { registerWebRoutes(webRoutes) }, common.QoS())

routes.Mount("", webRoutes)
return routes
}

var optSignInIgnoreCsrf = verifyAuthWithOptions(&common.VerifyOptions{DisableCSRF: true})

// registerRoutes register routes
func registerRoutes(m *web.Router) {
func registerWebRoutes(m *web.Router) {
// required to be signed in or signed out
reqSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true})
reqSignOut := verifyAuthWithOptions(&common.VerifyOptions{SignOutRequired: true})
Expand Down