warning.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package headers
  2. import (
  3. "errors"
  4. "net/http"
  5. "strconv"
  6. "strings"
  7. "time"
  8. "github.com/ydb-platform/ydb/library/go/core/xerrors"
  9. )
  10. const (
  11. WarningKey = "Warning"
  12. WarningResponseIsStale = 110 // RFC 7234, 5.5.1
  13. WarningRevalidationFailed = 111 // RFC 7234, 5.5.2
  14. WarningDisconnectedOperation = 112 // RFC 7234, 5.5.3
  15. WarningHeuristicExpiration = 113 // RFC 7234, 5.5.4
  16. WarningMiscellaneousWarning = 199 // RFC 7234, 5.5.5
  17. WarningTransformationApplied = 214 // RFC 7234, 5.5.6
  18. WarningMiscellaneousPersistentWarning = 299 // RFC 7234, 5.5.7
  19. )
  20. var warningStatusText = map[int]string{
  21. WarningResponseIsStale: "Response is Stale",
  22. WarningRevalidationFailed: "Revalidation Failed",
  23. WarningDisconnectedOperation: "Disconnected Operation",
  24. WarningHeuristicExpiration: "Heuristic Expiration",
  25. WarningMiscellaneousWarning: "Miscellaneous Warning",
  26. WarningTransformationApplied: "Transformation Applied",
  27. WarningMiscellaneousPersistentWarning: "Miscellaneous Persistent Warning",
  28. }
  29. // WarningText returns a text for the warning header code. It returns the empty
  30. // string if the code is unknown.
  31. func WarningText(warn int) string {
  32. return warningStatusText[warn]
  33. }
  34. // AddWarning adds Warning to http.Header with proper formatting
  35. // see: https://tools.ietf.org/html/rfc7234#section-5.5
  36. func AddWarning(h http.Header, warn int, agent, reason string, date time.Time) {
  37. values := make([]string, 0, 4)
  38. values = append(values, strconv.Itoa(warn))
  39. if agent != "" {
  40. values = append(values, agent)
  41. } else {
  42. values = append(values, "-")
  43. }
  44. if reason != "" {
  45. values = append(values, strconv.Quote(reason))
  46. }
  47. if !date.IsZero() {
  48. values = append(values, strconv.Quote(date.Format(time.RFC1123)))
  49. }
  50. h.Add(WarningKey, strings.Join(values, " "))
  51. }
  52. type WarningHeader struct {
  53. Code int
  54. Agent string
  55. Reason string
  56. Date time.Time
  57. }
  58. // ParseWarnings reads and parses Warning headers from http.Header
  59. func ParseWarnings(h http.Header) ([]WarningHeader, error) {
  60. warnings, ok := h[WarningKey]
  61. if !ok {
  62. return nil, nil
  63. }
  64. res := make([]WarningHeader, 0, len(warnings))
  65. for _, warn := range warnings {
  66. wh, err := parseWarning(warn)
  67. if err != nil {
  68. return nil, xerrors.Errorf("cannot parse '%s' header: %w", warn, err)
  69. }
  70. res = append(res, wh)
  71. }
  72. return res, nil
  73. }
  74. func parseWarning(warn string) (WarningHeader, error) {
  75. var res WarningHeader
  76. // parse code
  77. {
  78. codeSP := strings.Index(warn, " ")
  79. // fast path - code only warning
  80. if codeSP == -1 {
  81. code, err := strconv.Atoi(warn)
  82. res.Code = code
  83. return res, err
  84. }
  85. code, err := strconv.Atoi(warn[:codeSP])
  86. if err != nil {
  87. return WarningHeader{}, err
  88. }
  89. res.Code = code
  90. warn = strings.TrimSpace(warn[codeSP+1:])
  91. }
  92. // parse agent
  93. {
  94. agentSP := strings.Index(warn, " ")
  95. // fast path - no data after agent
  96. if agentSP == -1 {
  97. res.Agent = warn
  98. return res, nil
  99. }
  100. res.Agent = warn[:agentSP]
  101. warn = strings.TrimSpace(warn[agentSP+1:])
  102. }
  103. // parse reason
  104. {
  105. if len(warn) == 0 {
  106. return res, nil
  107. }
  108. // reason must by quoted, so we search for second quote
  109. reasonSP := strings.Index(warn[1:], `"`)
  110. // fast path - bad reason
  111. if reasonSP == -1 {
  112. return WarningHeader{}, errors.New("bad reason formatting")
  113. }
  114. res.Reason = warn[1 : reasonSP+1]
  115. warn = strings.TrimSpace(warn[reasonSP+2:])
  116. }
  117. // parse date
  118. {
  119. if len(warn) == 0 {
  120. return res, nil
  121. }
  122. // optional date must by quoted, so we search for second quote
  123. dateSP := strings.Index(warn[1:], `"`)
  124. // fast path - bad date
  125. if dateSP == -1 {
  126. return WarningHeader{}, errors.New("bad date formatting")
  127. }
  128. dt, err := time.Parse(time.RFC1123, warn[1:dateSP+1])
  129. if err != nil {
  130. return WarningHeader{}, err
  131. }
  132. res.Date = dt
  133. }
  134. return res, nil
  135. }