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