status_code_ranges.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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. func AutomaticDisableStatusCodesToString() string {
  25. return statusCodeRangesToString(AutomaticDisableStatusCodeRanges)
  26. }
  27. func AutomaticDisableStatusCodesFromString(s string) error {
  28. ranges, err := ParseHTTPStatusCodeRanges(s)
  29. if err != nil {
  30. return err
  31. }
  32. AutomaticDisableStatusCodeRanges = ranges
  33. return nil
  34. }
  35. func ShouldDisableByStatusCode(code int) bool {
  36. return shouldMatchStatusCodeRanges(AutomaticDisableStatusCodeRanges, code)
  37. }
  38. func AutomaticRetryStatusCodesToString() string {
  39. return statusCodeRangesToString(AutomaticRetryStatusCodeRanges)
  40. }
  41. func AutomaticRetryStatusCodesFromString(s string) error {
  42. ranges, err := ParseHTTPStatusCodeRanges(s)
  43. if err != nil {
  44. return err
  45. }
  46. AutomaticRetryStatusCodeRanges = ranges
  47. return nil
  48. }
  49. func ShouldRetryByStatusCode(code int) bool {
  50. return shouldMatchStatusCodeRanges(AutomaticRetryStatusCodeRanges, code)
  51. }
  52. func statusCodeRangesToString(ranges []StatusCodeRange) string {
  53. if len(ranges) == 0 {
  54. return ""
  55. }
  56. parts := make([]string, 0, len(ranges))
  57. for _, r := range ranges {
  58. if r.Start == r.End {
  59. parts = append(parts, strconv.Itoa(r.Start))
  60. continue
  61. }
  62. parts = append(parts, fmt.Sprintf("%d-%d", r.Start, r.End))
  63. }
  64. return strings.Join(parts, ",")
  65. }
  66. func shouldMatchStatusCodeRanges(ranges []StatusCodeRange, code int) bool {
  67. if code < 100 || code > 599 {
  68. return false
  69. }
  70. for _, r := range ranges {
  71. if code < r.Start {
  72. return false
  73. }
  74. if code <= r.End {
  75. return true
  76. }
  77. }
  78. return false
  79. }
  80. func ParseHTTPStatusCodeRanges(input string) ([]StatusCodeRange, error) {
  81. input = strings.TrimSpace(input)
  82. if input == "" {
  83. return nil, nil
  84. }
  85. input = strings.NewReplacer(",", ",").Replace(input)
  86. segments := strings.Split(input, ",")
  87. var ranges []StatusCodeRange
  88. var invalid []string
  89. for _, seg := range segments {
  90. seg = strings.TrimSpace(seg)
  91. if seg == "" {
  92. continue
  93. }
  94. r, err := parseHTTPStatusCodeToken(seg)
  95. if err != nil {
  96. invalid = append(invalid, seg)
  97. continue
  98. }
  99. ranges = append(ranges, r)
  100. }
  101. if len(invalid) > 0 {
  102. return nil, fmt.Errorf("invalid http status code rules: %s", strings.Join(invalid, ", "))
  103. }
  104. if len(ranges) == 0 {
  105. return nil, nil
  106. }
  107. sort.Slice(ranges, func(i, j int) bool {
  108. if ranges[i].Start == ranges[j].Start {
  109. return ranges[i].End < ranges[j].End
  110. }
  111. return ranges[i].Start < ranges[j].Start
  112. })
  113. merged := []StatusCodeRange{ranges[0]}
  114. for _, r := range ranges[1:] {
  115. last := &merged[len(merged)-1]
  116. if r.Start <= last.End+1 {
  117. if r.End > last.End {
  118. last.End = r.End
  119. }
  120. continue
  121. }
  122. merged = append(merged, r)
  123. }
  124. return merged, nil
  125. }
  126. func parseHTTPStatusCodeToken(token string) (StatusCodeRange, error) {
  127. token = strings.TrimSpace(token)
  128. token = strings.ReplaceAll(token, " ", "")
  129. if token == "" {
  130. return StatusCodeRange{}, fmt.Errorf("empty token")
  131. }
  132. if strings.Contains(token, "-") {
  133. parts := strings.Split(token, "-")
  134. if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
  135. return StatusCodeRange{}, fmt.Errorf("invalid range token: %s", token)
  136. }
  137. start, err := strconv.Atoi(parts[0])
  138. if err != nil {
  139. return StatusCodeRange{}, fmt.Errorf("invalid range start: %s", token)
  140. }
  141. end, err := strconv.Atoi(parts[1])
  142. if err != nil {
  143. return StatusCodeRange{}, fmt.Errorf("invalid range end: %s", token)
  144. }
  145. if start > end {
  146. return StatusCodeRange{}, fmt.Errorf("range start > end: %s", token)
  147. }
  148. if start < 100 || end > 599 {
  149. return StatusCodeRange{}, fmt.Errorf("range out of bounds: %s", token)
  150. }
  151. return StatusCodeRange{Start: start, End: end}, nil
  152. }
  153. code, err := strconv.Atoi(token)
  154. if err != nil {
  155. return StatusCodeRange{}, fmt.Errorf("invalid status code: %s", token)
  156. }
  157. if code < 100 || code > 599 {
  158. return StatusCodeRange{}, fmt.Errorf("status code out of bounds: %s", token)
  159. }
  160. return StatusCodeRange{Start: code, End: code}, nil
  161. }