httputil.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. package httputil
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "github.com/kubecost/cost-model/pkg/util/mapper"
  11. )
  12. //--------------------------------------------------------------------------
  13. // QueryParams
  14. //--------------------------------------------------------------------------
  15. type QueryParams = mapper.PrimitiveMap
  16. // queryParamsMap is mapper.Map adapter for url.Values
  17. type queryParamsMap struct {
  18. values url.Values
  19. }
  20. // mapper.Getter implementation
  21. func (qpm *queryParamsMap) Get(key string) string {
  22. return qpm.values.Get(key)
  23. }
  24. // mapper.Setter implementation
  25. func (qpm *queryParamsMap) Set(key, value string) error {
  26. qpm.values.Set(key, value)
  27. return nil
  28. }
  29. // NewQueryParams creates a primitive map using the request query parameters
  30. func NewQueryParams(values url.Values) QueryParams {
  31. return mapper.NewMapper(&queryParamsMap{values})
  32. }
  33. //--------------------------------------------------------------------------
  34. // HTTP Context Utilities
  35. //--------------------------------------------------------------------------
  36. const (
  37. ContextWarning string = "Warning"
  38. ContextName string = "Name"
  39. ContextQuery string = "Query"
  40. )
  41. // GetWarning Extracts a warning message from the request context if it exists
  42. func GetWarning(r *http.Request) (warning string, ok bool) {
  43. warning, ok = r.Context().Value(ContextWarning).(string)
  44. return
  45. }
  46. // SetWarning Sets the warning context on the provided request and returns a new instance of the request
  47. // with the new context.
  48. func SetWarning(r *http.Request, warning string) *http.Request {
  49. ctx := context.WithValue(r.Context(), ContextWarning, warning)
  50. return r.WithContext(ctx)
  51. }
  52. // GetName Extracts a name value from the request context if it exists
  53. func GetName(r *http.Request) (name string, ok bool) {
  54. name, ok = r.Context().Value(ContextName).(string)
  55. return
  56. }
  57. // SetName Sets the name value on the provided request and returns a new instance of the request
  58. // with the new context.
  59. func SetName(r *http.Request, name string) *http.Request {
  60. ctx := context.WithValue(r.Context(), ContextName, name)
  61. return r.WithContext(ctx)
  62. }
  63. // GetQuery Extracts a query value from the request context if it exists
  64. func GetQuery(r *http.Request) (name string, ok bool) {
  65. name, ok = r.Context().Value(ContextQuery).(string)
  66. return
  67. }
  68. // SetQuery Sets the query value on the provided request and returns a new instance of the request
  69. // with the new context.
  70. func SetQuery(r *http.Request, query string) *http.Request {
  71. ctx := context.WithValue(r.Context(), ContextQuery, query)
  72. return r.WithContext(ctx)
  73. }
  74. //--------------------------------------------------------------------------
  75. // Package Funcs
  76. //--------------------------------------------------------------------------
  77. // IsRateLimited accepts a response and body to determine if either indicate
  78. // a rate limited return
  79. func IsRateLimited(resp *http.Response, body []byte) bool {
  80. return IsRateLimitedResponse(resp) || IsRateLimitedBody(resp, body)
  81. }
  82. // RateLimitedRetryFor returns the parsed Retry-After header relative to the
  83. // current time. If the Retry-After header does not exist, the defaultWait parameter
  84. // is returned.
  85. func RateLimitedRetryFor(resp *http.Response, defaultWait time.Duration) time.Duration {
  86. if resp.Header == nil {
  87. return defaultWait
  88. }
  89. // Retry-After is either the number of seconds to wait or a target datetime (RFC1123)
  90. value := resp.Header.Get("Retry-After")
  91. if value == "" {
  92. return defaultWait
  93. }
  94. seconds, err := strconv.ParseInt(value, 10, 64)
  95. if err == nil {
  96. return time.Duration(seconds) * time.Second
  97. }
  98. // failed to parse an integer, try datetime RFC1123
  99. t, err := time.Parse(time.RFC1123, value)
  100. if err == nil {
  101. // return 0 if the datetime has already elapsed
  102. result := t.Sub(time.Now())
  103. if result < 0 {
  104. return 0
  105. }
  106. return result
  107. }
  108. // failed to parse datetime, return default
  109. return defaultWait
  110. }
  111. // IsRateLimitedResponse returns true if the status code is a 429 (TooManyRequests)
  112. func IsRateLimitedResponse(resp *http.Response) bool {
  113. return resp.StatusCode == http.StatusTooManyRequests
  114. }
  115. // IsRateLimitedBody attempts to determine if a response body indicates throttling
  116. // has occurred. This function is a result of some API providers (AWS) returning
  117. // a 400 status code instead of 429 for rate limit exceptions.
  118. func IsRateLimitedBody(resp *http.Response, body []byte) bool {
  119. // ignore non-400 status
  120. if resp.StatusCode < http.StatusBadRequest || resp.StatusCode >= http.StatusInternalServerError {
  121. return false
  122. }
  123. return strings.Contains(string(body), "ThrottlingException")
  124. }
  125. // HeaderString writes the request/response http.Header to a string.
  126. func HeaderString(h http.Header) string {
  127. var sb strings.Builder
  128. var first bool = true
  129. sb.WriteString("{ ")
  130. for k, vs := range h {
  131. if first {
  132. first = false
  133. } else {
  134. sb.WriteString(", ")
  135. }
  136. fmt.Fprintf(&sb, "%s: [ ", k)
  137. for idx, v := range vs {
  138. sb.WriteString(v)
  139. if idx != len(vs)-1 {
  140. sb.WriteString(", ")
  141. }
  142. }
  143. sb.WriteString(" ]")
  144. }
  145. sb.WriteString(" }")
  146. return sb.String()
  147. }