| 
 | 1 | +// Copyright 2025 The Gitea Authors. All rights reserved.  | 
 | 2 | +// SPDX-License-Identifier: MIT  | 
 | 3 | + | 
 | 4 | +package common  | 
 | 5 | + | 
 | 6 | +import (  | 
 | 7 | +	"fmt"  | 
 | 8 | +	"net/http"  | 
 | 9 | +	"strings"  | 
 | 10 | + | 
 | 11 | +	user_model "code.gitea.io/gitea/models/user"  | 
 | 12 | +	"code.gitea.io/gitea/modules/setting"  | 
 | 13 | +	"code.gitea.io/gitea/modules/web/middleware"  | 
 | 14 | + | 
 | 15 | +	"github.com/bohde/codel"  | 
 | 16 | +)  | 
 | 17 | + | 
 | 18 | +type Priority int  | 
 | 19 | + | 
 | 20 | +func (p Priority) String() string {  | 
 | 21 | +	switch p {  | 
 | 22 | +	case HighPriority:  | 
 | 23 | +		return "high"  | 
 | 24 | +	case DefaultPriority:  | 
 | 25 | +		return "default"  | 
 | 26 | +	case LowPriority:  | 
 | 27 | +		return "low"  | 
 | 28 | +	default:  | 
 | 29 | +		return fmt.Sprintf("%d", p)  | 
 | 30 | +	}  | 
 | 31 | +}  | 
 | 32 | + | 
 | 33 | +const (  | 
 | 34 | +	LowPriority     = Priority(-10)  | 
 | 35 | +	DefaultPriority = Priority(0)  | 
 | 36 | +	HighPriority    = Priority(10)  | 
 | 37 | +)  | 
 | 38 | + | 
 | 39 | +// QoS implements quality of service for requests, based upon whether  | 
 | 40 | +// or not the user is logged in. All traffic may get dropped, and  | 
 | 41 | +// anonymous users are deprioritized.  | 
 | 42 | +func QoS() func(next http.Handler) http.Handler {  | 
 | 43 | +	maxOutstanding := setting.Service.QoS.MaxInFlightRequests  | 
 | 44 | +	if maxOutstanding <= 0 {  | 
 | 45 | +		maxOutstanding = 10  | 
 | 46 | +	}  | 
 | 47 | + | 
 | 48 | +	c := codel.NewPriority(codel.Options{  | 
 | 49 | +		// The maximum number of waiting requests.  | 
 | 50 | +		MaxPending: setting.Service.QoS.MaxWaitingRequests,  | 
 | 51 | +		// The maximum number of in-flight requests.  | 
 | 52 | +		MaxOutstanding: maxOutstanding,  | 
 | 53 | +		// The target latency that a blocked request should wait  | 
 | 54 | +		// for. After this, it might be dropped.  | 
 | 55 | +		TargetLatency: setting.Service.QoS.TargetWaitTime,  | 
 | 56 | +	})  | 
 | 57 | + | 
 | 58 | +	return func(next http.Handler) http.Handler {  | 
 | 59 | +		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {  | 
 | 60 | +			ctx := req.Context()  | 
 | 61 | + | 
 | 62 | +			priority := DefaultPriority  | 
 | 63 | + | 
 | 64 | +			// If the user is logged in, assign high priority.  | 
 | 65 | +			data := middleware.GetContextData(req.Context())  | 
 | 66 | +			if _, ok := data[middleware.ContextDataKeySignedUser].(*user_model.User); ok {  | 
 | 67 | +				priority = HighPriority  | 
 | 68 | +			} else if IsGitContents(req.URL.Path) {  | 
 | 69 | +				// Otherwise, if the path would is accessing git contents directly, mark as low priority  | 
 | 70 | +				priority = LowPriority  | 
 | 71 | +			}  | 
 | 72 | + | 
 | 73 | +			// Check if the request can begin processing.  | 
 | 74 | +			err := c.Acquire(ctx, int(priority))  | 
 | 75 | +			if err != nil {  | 
 | 76 | +				// If it failed, the service is over capacity and should error  | 
 | 77 | +				http.Error(w, "Service Unavailable", http.StatusServiceUnavailable)  | 
 | 78 | +				return  | 
 | 79 | +			}  | 
 | 80 | + | 
 | 81 | +			// Release long-polling immediately, so they don't always  | 
 | 82 | +			// take up an in-flight request  | 
 | 83 | +			if strings.Contains(req.URL.Path, "/user/events") {  | 
 | 84 | +				c.Release()  | 
 | 85 | +			} else {  | 
 | 86 | +				defer c.Release()  | 
 | 87 | +			}  | 
 | 88 | + | 
 | 89 | +			next.ServeHTTP(w, req)  | 
 | 90 | +		})  | 
 | 91 | +	}  | 
 | 92 | +}  | 
 | 93 | + | 
 | 94 | +func IsGitContents(path string) bool {  | 
 | 95 | +	parts := []string{  | 
 | 96 | +		"refs",  | 
 | 97 | +		"archive",  | 
 | 98 | +		"commit",  | 
 | 99 | +		"graph",  | 
 | 100 | +		"blame",  | 
 | 101 | +		"branches",  | 
 | 102 | +		"tags",  | 
 | 103 | +		"labels",  | 
 | 104 | +		"stars",  | 
 | 105 | +		"search",  | 
 | 106 | +		"activity",  | 
 | 107 | +		"wiki",  | 
 | 108 | +		"watchers",  | 
 | 109 | +		"compare",  | 
 | 110 | +		"raw",  | 
 | 111 | +		"src",  | 
 | 112 | +		"commits",  | 
 | 113 | +	}  | 
 | 114 | + | 
 | 115 | +	for _, p := range parts {  | 
 | 116 | +		if strings.Contains(path, p) {  | 
 | 117 | +			return true  | 
 | 118 | +		}  | 
 | 119 | +	}  | 
 | 120 | +	return false  | 
 | 121 | +}  | 
0 commit comments