|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- package invoker
-
- import (
- "bytes"
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "reflect"
- "strings"
- "sync"
- "time"
-
- "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state"
- "github.com/seata/seata-go/pkg/util/log"
- )
-
- const errHttpCode = 400
-
- type HTTPInvoker struct {
- clientsMapLock sync.Mutex
- clients map[string]HTTPClient
- }
-
- func NewHTTPInvoker() *HTTPInvoker {
- return &HTTPInvoker{
- clients: make(map[string]HTTPClient),
- }
- }
-
- func (h *HTTPInvoker) RegisterClient(serviceName string, client HTTPClient) {
- h.clientsMapLock.Lock()
- defer h.clientsMapLock.Unlock()
- h.clients[serviceName] = client
- }
-
- func (h *HTTPInvoker) GetClient(serviceName string) HTTPClient {
- h.clientsMapLock.Lock()
- defer h.clientsMapLock.Unlock()
- return h.clients[serviceName]
- }
-
- func (h *HTTPInvoker) Invoke(ctx context.Context, input []any, service state.ServiceTaskState) (output []reflect.Value, err error) {
- serviceTaskStateImpl := service.(*state.ServiceTaskStateImpl)
- client := h.GetClient(serviceTaskStateImpl.ServiceName())
- if client == nil {
- return nil, fmt.Errorf("no http client %s for service task state", serviceTaskStateImpl.ServiceName())
- }
-
- if serviceTaskStateImpl.IsAsync() {
- go func() {
- _, err := client.Call(ctx, serviceTaskStateImpl, input)
- if err != nil {
- log.Errorf("invoke Service[%s].%s failed, err is %s", serviceTaskStateImpl.ServiceName(),
- serviceTaskStateImpl.ServiceMethod(), err)
- }
- }()
- return nil, nil
- }
-
- return client.Call(ctx, serviceTaskStateImpl, input)
- }
-
- func (h *HTTPInvoker) Close(ctx context.Context) error {
- return nil
- }
-
- type HTTPClient interface {
- Call(ctx context.Context, serviceTaskStateImpl *state.ServiceTaskStateImpl, input []any) ([]reflect.Value, error)
- }
-
- type HTTPClientImpl struct {
- serviceName string
- baseURL string
- client *http.Client
- }
-
- func NewHTTPClient(serviceName string, baseURL string, client *http.Client) *HTTPClientImpl {
- if client == nil {
- client = &http.Client{
- Timeout: time.Second * 30,
- }
- }
- return &HTTPClientImpl{
- serviceName: serviceName,
- baseURL: baseURL,
- client: client,
- }
- }
-
- func (h *HTTPClientImpl) Call(ctx context.Context, serviceTaskStateImpl *state.ServiceTaskStateImpl, input []any) ([]reflect.Value, error) {
- retryCountMap := make(map[state.Retry]int)
- for {
- res, err, shouldRetry := func() (res []reflect.Value, resErr error, shouldRetry bool) {
- defer func() {
- if r := recover(); r != nil {
- errStr := fmt.Sprintf("%v", r)
- retry := h.matchRetry(serviceTaskStateImpl, errStr)
- resErr = errors.New(errStr)
- if retry != nil {
- shouldRetry = h.needRetry(serviceTaskStateImpl, retryCountMap, retry, resErr)
- }
- }
- }()
-
- reqBody, err := json.Marshal(input)
- if err != nil {
- return nil, err, false
- }
-
- req, err := http.NewRequestWithContext(ctx,
- serviceTaskStateImpl.ServiceMethod(),
- h.baseURL+serviceTaskStateImpl.Name(),
- bytes.NewBuffer(reqBody))
- if err != nil {
- return nil, err, false
- }
-
- req.Header.Set("Content-Type", "application/json")
-
- resp, err := h.client.Do(req)
- if err != nil {
- retry := h.matchRetry(serviceTaskStateImpl, err.Error())
- if retry != nil {
- return nil, err, h.needRetry(serviceTaskStateImpl, retryCountMap, retry, err)
- }
- return nil, err, false
- }
- defer resp.Body.Close()
-
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, err, false
- }
-
- if resp.StatusCode >= errHttpCode {
- errStr := fmt.Sprintf("HTTP error: %d - %s", resp.StatusCode, string(body))
- retry := h.matchRetry(serviceTaskStateImpl, errStr)
- if retry != nil {
- return nil, errors.New(errStr), h.needRetry(serviceTaskStateImpl, retryCountMap, retry, err)
- }
- return nil, errors.New(errStr), false
- }
-
- return []reflect.Value{
- reflect.ValueOf(string(body)),
- reflect.Zero(reflect.TypeOf((*error)(nil)).Elem()),
- }, nil, false
- }()
-
- if !shouldRetry {
- if err != nil {
- return nil, fmt.Errorf("invoke Service[%s] failed, not satisfy retry config, the last err is %s",
- serviceTaskStateImpl.ServiceName(), err)
- }
- return res, nil
- }
- }
- }
-
- func (h *HTTPClientImpl) matchRetry(impl *state.ServiceTaskStateImpl, str string) state.Retry {
- if impl.Retry() != nil {
- for _, retry := range impl.Retry() {
- if retry.Exceptions() != nil {
- for _, exception := range retry.Exceptions() {
- if strings.Contains(str, exception) {
- return retry
- }
- }
- }
- }
- }
- return nil
- }
-
- func (h *HTTPClientImpl) needRetry(impl *state.ServiceTaskStateImpl, countMap map[state.Retry]int, retry state.Retry, err error) bool {
- attempt, exist := countMap[retry]
- if !exist {
- countMap[retry] = 0
- }
-
- if attempt >= retry.MaxAttempt() {
- return false
- }
-
- intervalSecond := retry.IntervalSecond()
- backoffRate := retry.BackoffRate()
- var currentInterval int64
- if attempt == 0 {
- currentInterval = int64(intervalSecond * 1000)
- } else {
- currentInterval = int64(intervalSecond * backoffRate * float64(attempt) * 1000)
- }
-
- log.Warnf("invoke service[%s.%s] failed, will retry after %s millis, current retry count: %s, current err: %s",
- impl.ServiceName(), impl.ServiceMethod(), currentInterval, attempt, err)
-
- time.Sleep(time.Duration(currentInterval) * time.Millisecond)
- countMap[retry] = attempt + 1
- return true
- }
|