service.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // Copyright 2020-2021 InfluxData, Inc. All rights reserved.
  2. // Use of this source code is governed by MIT
  3. // license that can be found in the LICENSE file.
  4. // Package http provides HTTP servicing related code.
  5. //
  6. // Important type is Service which handles HTTP operations. It is internally used by library and it is not necessary to use it directly for common operations.
  7. // It can be useful when creating custom InfluxDB2 server API calls using generated code from the domain package, that are not yet exposed by API of this library.
  8. //
  9. // Service can be obtained from client using HTTPService() method.
  10. // It can be also created directly. To instantiate a Service use NewService(). Remember, the authorization param is in form "Token your-auth-token". e.g. "Token DXnd7annkGteV5Wqx9G3YjO9Ezkw87nHk8OabcyHCxF5451kdBV0Ag2cG7OmZZgCUTHroagUPdxbuoyen6TSPw==".
  11. // srv := http.NewService("http://localhost:8086", "Token my-token", http.DefaultOptions())
  12. package http
  13. import (
  14. "context"
  15. "encoding/json"
  16. "io"
  17. "io/ioutil"
  18. "mime"
  19. "net/http"
  20. "net/url"
  21. "strconv"
  22. http2 "github.com/influxdata/influxdb-client-go/v2/internal/http"
  23. "github.com/influxdata/influxdb-client-go/v2/internal/log"
  24. )
  25. // RequestCallback defines function called after a request is created before any call
  26. type RequestCallback func(req *http.Request)
  27. // ResponseCallback defines function called after a successful response was received
  28. type ResponseCallback func(resp *http.Response) error
  29. // Service handles HTTP operations with taking care of mandatory request headers and known errors
  30. type Service interface {
  31. // DoPostRequest sends HTTP POST request to the given url with body
  32. DoPostRequest(ctx context.Context, url string, body io.Reader, requestCallback RequestCallback, responseCallback ResponseCallback) *Error
  33. // DoHTTPRequest sends given HTTP request and handles response
  34. DoHTTPRequest(req *http.Request, requestCallback RequestCallback, responseCallback ResponseCallback) *Error
  35. // DoHTTPRequestWithResponse sends given HTTP request and returns response
  36. DoHTTPRequestWithResponse(req *http.Request, requestCallback RequestCallback) (*http.Response, error)
  37. // SetAuthorization sets the authorization header value
  38. SetAuthorization(authorization string)
  39. // Authorization returns current authorization header value
  40. Authorization() string
  41. // ServerAPIURL returns URL to InfluxDB2 server API space
  42. ServerAPIURL() string
  43. // ServerURL returns URL to InfluxDB2 server
  44. ServerURL() string
  45. }
  46. // service implements Service interface
  47. type service struct {
  48. serverAPIURL string
  49. serverURL string
  50. authorization string
  51. client Doer
  52. userAgent string
  53. }
  54. // NewService creates instance of http Service with given parameters
  55. func NewService(serverURL, authorization string, httpOptions *Options) Service {
  56. apiURL, err := url.Parse(serverURL)
  57. serverAPIURL := serverURL
  58. if err == nil {
  59. apiURL, err = apiURL.Parse("api/v2/")
  60. if err == nil {
  61. serverAPIURL = apiURL.String()
  62. }
  63. }
  64. return &service{
  65. serverAPIURL: serverAPIURL,
  66. serverURL: serverURL,
  67. authorization: authorization,
  68. client: httpOptions.HTTPDoer(),
  69. userAgent: http2.FormatUserAgent(httpOptions.ApplicationName()),
  70. }
  71. }
  72. func (s *service) ServerAPIURL() string {
  73. return s.serverAPIURL
  74. }
  75. func (s *service) ServerURL() string {
  76. return s.serverURL
  77. }
  78. func (s *service) SetAuthorization(authorization string) {
  79. s.authorization = authorization
  80. }
  81. func (s *service) Authorization() string {
  82. return s.authorization
  83. }
  84. func (s *service) DoPostRequest(ctx context.Context, url string, body io.Reader, requestCallback RequestCallback, responseCallback ResponseCallback) *Error {
  85. return s.doHTTPRequestWithURL(ctx, http.MethodPost, url, body, requestCallback, responseCallback)
  86. }
  87. func (s *service) doHTTPRequestWithURL(ctx context.Context, method, url string, body io.Reader, requestCallback RequestCallback, responseCallback ResponseCallback) *Error {
  88. req, err := http.NewRequestWithContext(ctx, method, url, body)
  89. if err != nil {
  90. return NewError(err)
  91. }
  92. return s.DoHTTPRequest(req, requestCallback, responseCallback)
  93. }
  94. func (s *service) DoHTTPRequest(req *http.Request, requestCallback RequestCallback, responseCallback ResponseCallback) *Error {
  95. resp, err := s.DoHTTPRequestWithResponse(req, requestCallback)
  96. if err != nil {
  97. return NewError(err)
  98. }
  99. if resp.StatusCode < 200 || resp.StatusCode >= 300 {
  100. return s.parseHTTPError(resp)
  101. }
  102. if responseCallback != nil {
  103. err := responseCallback(resp)
  104. if err != nil {
  105. return NewError(err)
  106. }
  107. }
  108. return nil
  109. }
  110. func (s *service) DoHTTPRequestWithResponse(req *http.Request, requestCallback RequestCallback) (*http.Response, error) {
  111. log.Infof("HTTP %s req to %s", req.Method, req.URL.String())
  112. if len(s.authorization) > 0 {
  113. req.Header.Set("Authorization", s.authorization)
  114. }
  115. if req.Header.Get("User-Agent") == "" {
  116. req.Header.Set("User-Agent", s.userAgent)
  117. }
  118. if requestCallback != nil {
  119. requestCallback(req)
  120. }
  121. return s.client.Do(req)
  122. }
  123. func (s *service) parseHTTPError(r *http.Response) *Error {
  124. // successful status code range
  125. if r.StatusCode >= 200 && r.StatusCode < 300 {
  126. return nil
  127. }
  128. defer func() {
  129. // discard body so connection can be reused
  130. _, _ = io.Copy(ioutil.Discard, r.Body)
  131. _ = r.Body.Close()
  132. }()
  133. perror := NewError(nil)
  134. perror.StatusCode = r.StatusCode
  135. if v := r.Header.Get("Retry-After"); v != "" {
  136. r, err := strconv.ParseUint(v, 10, 32)
  137. if err == nil {
  138. perror.RetryAfter = uint(r)
  139. }
  140. }
  141. // json encoded error
  142. ctype, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
  143. if ctype == "application/json" {
  144. perror.Err = json.NewDecoder(r.Body).Decode(perror)
  145. } else {
  146. body, err := ioutil.ReadAll(r.Body)
  147. if err != nil {
  148. perror.Err = err
  149. return perror
  150. }
  151. perror.Code = r.Status
  152. perror.Message = string(body)
  153. }
  154. if perror.Code == "" && perror.Message == "" {
  155. switch r.StatusCode {
  156. case http.StatusTooManyRequests:
  157. perror.Code = "too many requests"
  158. perror.Message = "exceeded rate limit"
  159. case http.StatusServiceUnavailable:
  160. perror.Code = "unavailable"
  161. perror.Message = "service temporarily unavailable"
  162. default:
  163. perror.Code = r.Status
  164. perror.Message = r.Header.Get("X-Influxdb-Error")
  165. }
  166. }
  167. return perror
  168. }