requests.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. package httpc
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. nurl "net/url"
  10. "strings"
  11. "github.com/wuntsong-org/go-zero-plus/core/lang"
  12. "github.com/wuntsong-org/go-zero-plus/core/mapping"
  13. "github.com/wuntsong-org/go-zero-plus/core/trace"
  14. "github.com/wuntsong-org/go-zero-plus/rest/httpc/internal"
  15. "github.com/wuntsong-org/go-zero-plus/rest/internal/header"
  16. "go.opentelemetry.io/otel"
  17. "go.opentelemetry.io/otel/codes"
  18. "go.opentelemetry.io/otel/propagation"
  19. semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
  20. oteltrace "go.opentelemetry.io/otel/trace"
  21. )
  22. var interceptors = []internal.Interceptor{
  23. internal.LogInterceptor,
  24. }
  25. // Do sends an HTTP request with the given arguments and returns an HTTP response.
  26. // data is automatically marshal into a *httpRequest, typically it's defined in an API file.
  27. func Do(ctx context.Context, method, url string, data any) (*http.Response, error) {
  28. req, err := buildRequest(ctx, method, url, data)
  29. if err != nil {
  30. return nil, err
  31. }
  32. return DoRequest(req)
  33. }
  34. // DoRequest sends an HTTP request and returns an HTTP response.
  35. func DoRequest(r *http.Request) (*http.Response, error) {
  36. return request(r, defaultClient{})
  37. }
  38. type (
  39. client interface {
  40. do(r *http.Request) (*http.Response, error)
  41. }
  42. defaultClient struct{}
  43. )
  44. func (c defaultClient) do(r *http.Request) (*http.Response, error) {
  45. return http.DefaultClient.Do(r)
  46. }
  47. func buildFormQuery(u *nurl.URL, val map[string]any) string {
  48. query := u.Query()
  49. for k, v := range val {
  50. query.Add(k, fmt.Sprint(v))
  51. }
  52. return query.Encode()
  53. }
  54. func buildRequest(ctx context.Context, method, url string, data any) (*http.Request, error) {
  55. u, err := nurl.Parse(url)
  56. if err != nil {
  57. return nil, err
  58. }
  59. var val map[string]map[string]any
  60. if data != nil {
  61. val, err = mapping.Marshal(data)
  62. if err != nil {
  63. return nil, err
  64. }
  65. }
  66. if err := fillPath(u, val[pathKey]); err != nil {
  67. return nil, err
  68. }
  69. var reader io.Reader
  70. jsonVars, hasJsonBody := val[jsonKey]
  71. if hasJsonBody {
  72. if method == http.MethodGet {
  73. return nil, ErrGetWithBody
  74. }
  75. var buf bytes.Buffer
  76. enc := json.NewEncoder(&buf)
  77. if err := enc.Encode(jsonVars); err != nil {
  78. return nil, err
  79. }
  80. reader = &buf
  81. }
  82. req, err := http.NewRequestWithContext(ctx, method, u.String(), reader)
  83. if err != nil {
  84. return nil, err
  85. }
  86. req.URL.RawQuery = buildFormQuery(u, val[formKey])
  87. fillHeader(req, val[headerKey])
  88. if hasJsonBody {
  89. req.Header.Set(header.ContentType, header.JsonContentType)
  90. }
  91. return req, nil
  92. }
  93. func fillHeader(r *http.Request, val map[string]any) {
  94. for k, v := range val {
  95. r.Header.Add(k, fmt.Sprint(v))
  96. }
  97. }
  98. func fillPath(u *nurl.URL, val map[string]any) error {
  99. used := make(map[string]lang.PlaceholderType)
  100. fields := strings.Split(u.Path, slash)
  101. for i := range fields {
  102. field := fields[i]
  103. if len(field) > 0 && field[0] == colon {
  104. name := field[1:]
  105. ival, ok := val[name]
  106. if !ok {
  107. return fmt.Errorf("missing path variable %q", name)
  108. }
  109. value := fmt.Sprint(ival)
  110. if len(value) == 0 {
  111. return fmt.Errorf("empty path variable %q", name)
  112. }
  113. fields[i] = value
  114. used[name] = lang.Placeholder
  115. }
  116. }
  117. if len(val) != len(used) {
  118. for key := range used {
  119. delete(val, key)
  120. }
  121. var unused []string
  122. for key := range val {
  123. unused = append(unused, key)
  124. }
  125. return fmt.Errorf("more path variables are provided: %q", strings.Join(unused, ", "))
  126. }
  127. u.Path = strings.Join(fields, slash)
  128. return nil
  129. }
  130. func request(r *http.Request, cli client) (*http.Response, error) {
  131. ctx := r.Context()
  132. tracer := trace.TracerFromContext(ctx)
  133. propagator := otel.GetTextMapPropagator()
  134. spanName := r.URL.Path
  135. ctx, span := tracer.Start(
  136. ctx,
  137. spanName,
  138. oteltrace.WithSpanKind(oteltrace.SpanKindClient),
  139. oteltrace.WithAttributes(semconv.HTTPClientAttributesFromHTTPRequest(r)...),
  140. )
  141. defer span.End()
  142. respHandlers := make([]internal.ResponseHandler, len(interceptors))
  143. for i, interceptor := range interceptors {
  144. var h internal.ResponseHandler
  145. r, h = interceptor(r)
  146. respHandlers[i] = h
  147. }
  148. r = r.WithContext(ctx)
  149. propagator.Inject(ctx, propagation.HeaderCarrier(r.Header))
  150. resp, err := cli.do(r)
  151. for i := len(respHandlers) - 1; i >= 0; i-- {
  152. respHandlers[i](resp, err)
  153. }
  154. if err != nil {
  155. span.RecordError(err)
  156. span.SetStatus(codes.Error, err.Error())
  157. return resp, err
  158. }
  159. span.SetAttributes(semconv.HTTPAttributesFromHTTPStatusCode(resp.StatusCode)...)
  160. span.SetStatus(semconv.SpanStatusFromHTTPStatusCodeAndSpanKind(resp.StatusCode, oteltrace.SpanKindClient))
  161. return resp, err
  162. }