You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

http_invoker.go 6.2 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package invoker
  18. import (
  19. "bytes"
  20. "context"
  21. "encoding/json"
  22. "errors"
  23. "fmt"
  24. "io"
  25. "net/http"
  26. "reflect"
  27. "strings"
  28. "sync"
  29. "time"
  30. "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state"
  31. "github.com/seata/seata-go/pkg/util/log"
  32. )
  33. const errHttpCode = 400
  34. type HTTPInvoker struct {
  35. clientsMapLock sync.Mutex
  36. clients map[string]HTTPClient
  37. }
  38. func NewHTTPInvoker() *HTTPInvoker {
  39. return &HTTPInvoker{
  40. clients: make(map[string]HTTPClient),
  41. }
  42. }
  43. func (h *HTTPInvoker) RegisterClient(serviceName string, client HTTPClient) {
  44. h.clientsMapLock.Lock()
  45. defer h.clientsMapLock.Unlock()
  46. h.clients[serviceName] = client
  47. }
  48. func (h *HTTPInvoker) GetClient(serviceName string) HTTPClient {
  49. h.clientsMapLock.Lock()
  50. defer h.clientsMapLock.Unlock()
  51. return h.clients[serviceName]
  52. }
  53. func (h *HTTPInvoker) Invoke(ctx context.Context, input []any, service state.ServiceTaskState) (output []reflect.Value, err error) {
  54. serviceTaskStateImpl := service.(*state.ServiceTaskStateImpl)
  55. client := h.GetClient(serviceTaskStateImpl.ServiceName())
  56. if client == nil {
  57. return nil, fmt.Errorf("no http client %s for service task state", serviceTaskStateImpl.ServiceName())
  58. }
  59. if serviceTaskStateImpl.IsAsync() {
  60. go func() {
  61. _, err := client.Call(ctx, serviceTaskStateImpl, input)
  62. if err != nil {
  63. log.Errorf("invoke Service[%s].%s failed, err is %s", serviceTaskStateImpl.ServiceName(),
  64. serviceTaskStateImpl.ServiceMethod(), err)
  65. }
  66. }()
  67. return nil, nil
  68. }
  69. return client.Call(ctx, serviceTaskStateImpl, input)
  70. }
  71. func (h *HTTPInvoker) Close(ctx context.Context) error {
  72. return nil
  73. }
  74. type HTTPClient interface {
  75. Call(ctx context.Context, serviceTaskStateImpl *state.ServiceTaskStateImpl, input []any) ([]reflect.Value, error)
  76. }
  77. type HTTPClientImpl struct {
  78. serviceName string
  79. baseURL string
  80. client *http.Client
  81. }
  82. func NewHTTPClient(serviceName string, baseURL string, client *http.Client) *HTTPClientImpl {
  83. if client == nil {
  84. client = &http.Client{
  85. Timeout: time.Second * 30,
  86. }
  87. }
  88. return &HTTPClientImpl{
  89. serviceName: serviceName,
  90. baseURL: baseURL,
  91. client: client,
  92. }
  93. }
  94. func (h *HTTPClientImpl) Call(ctx context.Context, serviceTaskStateImpl *state.ServiceTaskStateImpl, input []any) ([]reflect.Value, error) {
  95. retryCountMap := make(map[state.Retry]int)
  96. for {
  97. res, err, shouldRetry := func() (res []reflect.Value, resErr error, shouldRetry bool) {
  98. defer func() {
  99. if r := recover(); r != nil {
  100. errStr := fmt.Sprintf("%v", r)
  101. retry := h.matchRetry(serviceTaskStateImpl, errStr)
  102. resErr = errors.New(errStr)
  103. if retry != nil {
  104. shouldRetry = h.needRetry(serviceTaskStateImpl, retryCountMap, retry, resErr)
  105. }
  106. }
  107. }()
  108. reqBody, err := json.Marshal(input)
  109. if err != nil {
  110. return nil, err, false
  111. }
  112. req, err := http.NewRequestWithContext(ctx,
  113. serviceTaskStateImpl.ServiceMethod(),
  114. h.baseURL+serviceTaskStateImpl.Name(),
  115. bytes.NewBuffer(reqBody))
  116. if err != nil {
  117. return nil, err, false
  118. }
  119. req.Header.Set("Content-Type", "application/json")
  120. resp, err := h.client.Do(req)
  121. if err != nil {
  122. retry := h.matchRetry(serviceTaskStateImpl, err.Error())
  123. if retry != nil {
  124. return nil, err, h.needRetry(serviceTaskStateImpl, retryCountMap, retry, err)
  125. }
  126. return nil, err, false
  127. }
  128. defer resp.Body.Close()
  129. body, err := io.ReadAll(resp.Body)
  130. if err != nil {
  131. return nil, err, false
  132. }
  133. if resp.StatusCode >= errHttpCode {
  134. errStr := fmt.Sprintf("HTTP error: %d - %s", resp.StatusCode, string(body))
  135. retry := h.matchRetry(serviceTaskStateImpl, errStr)
  136. if retry != nil {
  137. return nil, errors.New(errStr), h.needRetry(serviceTaskStateImpl, retryCountMap, retry, err)
  138. }
  139. return nil, errors.New(errStr), false
  140. }
  141. return []reflect.Value{
  142. reflect.ValueOf(string(body)),
  143. reflect.Zero(reflect.TypeOf((*error)(nil)).Elem()),
  144. }, nil, false
  145. }()
  146. if !shouldRetry {
  147. if err != nil {
  148. return nil, fmt.Errorf("invoke Service[%s] failed, not satisfy retry config, the last err is %s",
  149. serviceTaskStateImpl.ServiceName(), err)
  150. }
  151. return res, nil
  152. }
  153. }
  154. }
  155. func (h *HTTPClientImpl) matchRetry(impl *state.ServiceTaskStateImpl, str string) state.Retry {
  156. if impl.Retry() != nil {
  157. for _, retry := range impl.Retry() {
  158. if retry.Exceptions() != nil {
  159. for _, exception := range retry.Exceptions() {
  160. if strings.Contains(str, exception) {
  161. return retry
  162. }
  163. }
  164. }
  165. }
  166. }
  167. return nil
  168. }
  169. func (h *HTTPClientImpl) needRetry(impl *state.ServiceTaskStateImpl, countMap map[state.Retry]int, retry state.Retry, err error) bool {
  170. attempt, exist := countMap[retry]
  171. if !exist {
  172. countMap[retry] = 0
  173. }
  174. if attempt >= retry.MaxAttempt() {
  175. return false
  176. }
  177. intervalSecond := retry.IntervalSecond()
  178. backoffRate := retry.BackoffRate()
  179. var currentInterval int64
  180. if attempt == 0 {
  181. currentInterval = int64(intervalSecond * 1000)
  182. } else {
  183. currentInterval = int64(intervalSecond * backoffRate * float64(attempt) * 1000)
  184. }
  185. log.Warnf("invoke service[%s.%s] failed, will retry after %s millis, current retry count: %s, current err: %s",
  186. impl.ServiceName(), impl.ServiceMethod(), currentInterval, attempt, err)
  187. time.Sleep(time.Duration(currentInterval) * time.Millisecond)
  188. countMap[retry] = attempt + 1
  189. return true
  190. }