status_code_ranges.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. package operation_setting
  2. import (
  3. "fmt"
  4. "sort"
  5. "strconv"
  6. "strings"
  7. "github.com/QuantumNous/new-api/types"
  8. )
  9. type StatusCodeRange struct {
  10. Start int
  11. End int
  12. }
  13. var AutomaticDisableStatusCodeRanges = []StatusCodeRange{{Start: 401, End: 401}}
  14. // Default behavior matches legacy hardcoded retry rules in controller/relay.go shouldRetry:
  15. // retry for 1xx, 3xx, 4xx(except 400/408), 5xx(except 504/524), and no retry for 2xx.
  16. var AutomaticRetryStatusCodeRanges = []StatusCodeRange{
  17. {Start: 100, End: 199},
  18. {Start: 300, End: 399},
  19. {Start: 401, End: 407},
  20. {Start: 409, End: 499},
  21. {Start: 500, End: 503},
  22. {Start: 505, End: 523},
  23. {Start: 525, End: 599},
  24. }
  25. var alwaysSkipRetryStatusCodes = map[int]struct{}{
  26. 504: {},
  27. 524: {},
  28. }
  29. var alwaysSkipRetryCodes = map[types.ErrorCode]struct{}{
  30. types.ErrorCodeBadResponseBody: {},
  31. }
  32. func AutomaticDisableStatusCodesToString() string {
  33. return statusCodeRangesToString(AutomaticDisableStatusCodeRanges)
  34. }
  35. func AutomaticDisableStatusCodesFromString(s string) error {
  36. ranges, err := ParseHTTPStatusCodeRanges(s)
  37. if err != nil {
  38. return err
  39. }
  40. AutomaticDisableStatusCodeRanges = ranges
  41. return nil
  42. }
  43. func ShouldDisableByStatusCode(code int) bool {
  44. return shouldMatchStatusCodeRanges(AutomaticDisableStatusCodeRanges, code)
  45. }
  46. func AutomaticRetryStatusCodesToString() string {
  47. return statusCodeRangesToString(AutomaticRetryStatusCodeRanges)
  48. }
  49. func AutomaticRetryStatusCodesFromString(s string) error {
  50. ranges, err := ParseHTTPStatusCodeRanges(s)
  51. if err != nil {
  52. return err
  53. }
  54. AutomaticRetryStatusCodeRanges = ranges
  55. return nil
  56. }
  57. func IsAlwaysSkipRetryStatusCode(code int) bool {
  58. _, exists := alwaysSkipRetryStatusCodes[code]
  59. return exists
  60. }
  61. func IsAlwaysSkipRetryCode(errorCode types.ErrorCode) bool {
  62. _, exists := alwaysSkipRetryCodes[errorCode]
  63. return exists
  64. }
  65. func ShouldRetryByStatusCode(code int) bool {
  66. if IsAlwaysSkipRetryStatusCode(code) {
  67. return false
  68. }
  69. return shouldMatchStatusCodeRanges(AutomaticRetryStatusCodeRanges, code)
  70. }
  71. func statusCodeRangesToString(ranges []StatusCodeRange) string {
  72. if len(ranges) == 0 {
  73. return ""
  74. }
  75. parts := make([]string, 0, len(ranges))
  76. for _, r := range ranges {
  77. if r.Start == r.End {
  78. parts = append(parts, strconv.Itoa(r.Start))
  79. continue
  80. }
  81. parts = append(parts, fmt.Sprintf("%d-%d", r.Start, r.End))
  82. }
  83. return strings.Join(parts, ",")
  84. }
  85. func shouldMatchStatusCodeRanges(ranges []StatusCodeRange, code int) bool {
  86. if code < 100 || code > 599 {
  87. return false
  88. }
  89. for _, r := range ranges {
  90. if code < r.Start {
  91. return false
  92. }
  93. if code <= r.End {
  94. return true
  95. }
  96. }
  97. return false
  98. }
  99. func ParseHTTPStatusCodeRanges(input string) ([]StatusCodeRange, error) {
  100. input = strings.TrimSpace(input)
  101. if input == "" {
  102. return nil, nil
  103. }
  104. input = strings.NewReplacer(",", ",").Replace(input)
  105. segments := strings.Split(input, ",")
  106. var ranges []StatusCodeRange
  107. var invalid []string
  108. for _, seg := range segments {
  109. seg = strings.TrimSpace(seg)
  110. if seg == "" {
  111. continue
  112. }
  113. r, err := parseHTTPStatusCodeToken(seg)
  114. if err != nil {
  115. invalid = append(invalid, seg)
  116. continue
  117. }
  118. ranges = append(ranges, r)
  119. }
  120. if len(invalid) > 0 {
  121. return nil, fmt.Errorf("invalid http status code rules: %s", strings.Join(invalid, ", "))
  122. }
  123. if len(ranges) == 0 {
  124. return nil, nil
  125. }
  126. sort.Slice(ranges, func(i, j int) bool {
  127. if ranges[i].Start == ranges[j].Start {
  128. return ranges[i].End < ranges[j].End
  129. }
  130. return ranges[i].Start < ranges[j].Start
  131. })
  132. merged := []StatusCodeRange{ranges[0]}
  133. for _, r := range ranges[1:] {
  134. last := &merged[len(merged)-1]
  135. if r.Start <= last.End+1 {
  136. if r.End > last.End {
  137. last.End = r.End
  138. }
  139. continue
  140. }
  141. merged = append(merged, r)
  142. }
  143. return merged, nil
  144. }
  145. func parseHTTPStatusCodeToken(token string) (StatusCodeRange, error) {
  146. token = strings.TrimSpace(token)
  147. token = strings.ReplaceAll(token, " ", "")
  148. if token == "" {
  149. return StatusCodeRange{}, fmt.Errorf("empty token")
  150. }
  151. if strings.Contains(token, "-") {
  152. parts := strings.Split(token, "-")
  153. if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
  154. return StatusCodeRange{}, fmt.Errorf("invalid range token: %s", token)
  155. }
  156. start, err := strconv.Atoi(parts[0])
  157. if err != nil {
  158. return StatusCodeRange{}, fmt.Errorf("invalid range start: %s", token)
  159. }
  160. end, err := strconv.Atoi(parts[1])
  161. if err != nil {
  162. return StatusCodeRange{}, fmt.Errorf("invalid range end: %s", token)
  163. }
  164. if start > end {
  165. return StatusCodeRange{}, fmt.Errorf("range start > end: %s", token)
  166. }
  167. if start < 100 || end > 599 {
  168. return StatusCodeRange{}, fmt.Errorf("range out of bounds: %s", token)
  169. }
  170. return StatusCodeRange{Start: start, End: end}, nil
  171. }
  172. code, err := strconv.Atoi(token)
  173. if err != nil {
  174. return StatusCodeRange{}, fmt.Errorf("invalid status code: %s", token)
  175. }
  176. if code < 100 || code > 599 {
  177. return StatusCodeRange{}, fmt.Errorf("status code out of bounds: %s", token)
  178. }
  179. return StatusCodeRange{Start: code, End: code}, nil
  180. }