gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. // Copyright 2025 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package common
  4. import (
  5. "context"
  6. "fmt"
  7. "net/http"
  8. "strings"
  9. user_model "code.gitea.io/gitea/models/user"
  10. "code.gitea.io/gitea/modules/log"
  11. "code.gitea.io/gitea/modules/setting"
  12. "code.gitea.io/gitea/modules/templates"
  13. "code.gitea.io/gitea/modules/web/middleware"
  14. giteacontext "code.gitea.io/gitea/services/context"
  15. "github.com/bohde/codel"
  16. "github.com/go-chi/chi/v5"
  17. )
  18. const tplStatus503 templates.TplName = "status/503"
  19. type Priority int
  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. const (
  33. LowPriority = Priority(-10)
  34. DefaultPriority = Priority(0)
  35. HighPriority = Priority(10)
  36. )
  37. // QoS implements quality of service for requests, based upon whether
  38. // or not the user is logged in. All traffic may get dropped, and
  39. // anonymous users are deprioritized.
  40. func QoS() func(next http.Handler) http.Handler {
  41. if !setting.Service.QoS.Enabled {
  42. return nil
  43. }
  44. maxOutstanding := setting.Service.QoS.MaxInFlightRequests
  45. if maxOutstanding <= 0 {
  46. maxOutstanding = 10
  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. return func(next http.Handler) http.Handler {
  58. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  59. ctx := req.Context()
  60. priority := requestPriority(ctx)
  61. // Check if the request can begin processing.
  62. err := c.Acquire(ctx, int(priority))
  63. if err != nil {
  64. log.Error("QoS error, dropping request of priority %s: %v", priority, err)
  65. renderServiceUnavailable(w, req)
  66. return
  67. }
  68. // Release long-polling immediately, so they don't always
  69. // take up an in-flight request
  70. if strings.Contains(req.URL.Path, "/user/events") {
  71. c.Release()
  72. } else {
  73. defer c.Release()
  74. }
  75. next.ServeHTTP(w, req)
  76. })
  77. }
  78. }
  79. // requestPriority assigns a priority value for a request based upon
  80. // whether the user is logged in and how expensive the endpoint is
  81. func requestPriority(ctx context.Context) Priority {
  82. // If the user is logged in, assign high priority.
  83. data := middleware.GetContextData(ctx)
  84. if _, ok := data[middleware.ContextDataKeySignedUser].(*user_model.User); ok {
  85. return HighPriority
  86. }
  87. rctx := chi.RouteContext(ctx)
  88. if rctx == nil {
  89. return DefaultPriority
  90. }
  91. // If we're operating in the context of a repo, assign low priority
  92. routePattern := rctx.RoutePattern()
  93. if strings.HasPrefix(routePattern, "/{username}/{reponame}/") {
  94. return LowPriority
  95. }
  96. return DefaultPriority
  97. }
  98. // renderServiceUnavailable will render an HTTP 503 Service
  99. // Unavailable page, providing HTML if the client accepts it.
  100. func renderServiceUnavailable(w http.ResponseWriter, req *http.Request) {
  101. acceptsHTML := false
  102. for _, part := range req.Header["Accept"] {
  103. if strings.Contains(part, "text/html") {
  104. acceptsHTML = true
  105. break
  106. }
  107. }
  108. // If the client doesn't accept HTML, then render a plain text response
  109. if !acceptsHTML {
  110. http.Error(w, "503 Service Unavailable", http.StatusServiceUnavailable)
  111. return
  112. }
  113. tmplCtx := giteacontext.TemplateContext{}
  114. tmplCtx["Locale"] = middleware.Locale(w, req)
  115. ctxData := middleware.GetContextData(req.Context())
  116. err := templates.HTMLRenderer().HTML(w, http.StatusServiceUnavailable, tplStatus503, ctxData, tmplCtx)
  117. if err != nil {
  118. log.Error("Error occurs again when rendering service unavailable page: %v", err)
  119. w.WriteHeader(http.StatusInternalServerError)
  120. _, _ = w.Write([]byte("Internal server error, please collect error logs and report to Gitea issue tracker"))
  121. }
  122. }