status_code_ranges.go 4.6 KB

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