client_http.go 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. package apiclient
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "net/http"
  10. "net/http/httputil"
  11. "net/url"
  12. "strings"
  13. log "github.com/sirupsen/logrus"
  14. )
  15. func (c *ApiClient) NewRequest(method, url string, body interface{}) (*http.Request, error) {
  16. if !strings.HasSuffix(c.BaseURL.Path, "/") {
  17. return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
  18. }
  19. u, err := c.BaseURL.Parse(url)
  20. if err != nil {
  21. return nil, err
  22. }
  23. var buf io.ReadWriter
  24. if body != nil {
  25. buf = &bytes.Buffer{}
  26. enc := json.NewEncoder(buf)
  27. enc.SetEscapeHTML(false)
  28. err := enc.Encode(body)
  29. if err != nil {
  30. return nil, err
  31. }
  32. }
  33. req, err := http.NewRequest(method, u.String(), buf)
  34. if err != nil {
  35. return nil, err
  36. }
  37. if body != nil {
  38. req.Header.Set("Content-Type", "application/json")
  39. }
  40. return req, nil
  41. }
  42. func (c *ApiClient) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
  43. if ctx == nil {
  44. return nil, errors.New("context must be non-nil")
  45. }
  46. req = req.WithContext(ctx)
  47. // Check rate limit
  48. if c.UserAgent != "" {
  49. req.Header.Add("User-Agent", c.UserAgent)
  50. }
  51. resp, err := c.client.Do(req)
  52. if resp != nil && resp.Body != nil {
  53. defer resp.Body.Close()
  54. }
  55. if err != nil {
  56. // If we got an error, and the context has been canceled,
  57. // the context's error is probably more useful.
  58. select {
  59. case <-ctx.Done():
  60. return nil, ctx.Err()
  61. default:
  62. }
  63. // If the error type is *url.Error, sanitize its URL before returning.
  64. if e, ok := err.(*url.Error); ok {
  65. if url, err := url.Parse(e.URL); err == nil {
  66. e.URL = url.String()
  67. return newResponse(resp), e
  68. } else {
  69. return newResponse(resp), err
  70. }
  71. }
  72. return newResponse(resp), err
  73. }
  74. for k, v := range resp.Header {
  75. log.Debugf("[headers] %s : %s", k, v)
  76. }
  77. dump, err := httputil.DumpResponse(resp, true)
  78. if err == nil {
  79. log.Debugf("Response: %s", string(dump))
  80. }
  81. response := newResponse(resp)
  82. err = CheckResponse(resp)
  83. if err != nil {
  84. return response, err
  85. }
  86. if v != nil {
  87. if w, ok := v.(io.Writer); ok {
  88. io.Copy(w, resp.Body)
  89. } else {
  90. decErr := json.NewDecoder(resp.Body).Decode(v)
  91. if decErr == io.EOF {
  92. decErr = nil // ignore EOF errors caused by empty response body
  93. }
  94. if decErr != nil {
  95. err = decErr
  96. }
  97. }
  98. }
  99. return response, err
  100. }