gitee.com/larksuite/oapi-sdk-go/v3@v3.0.3/core/httptransport.go (about)

     1  /*
     2   * MIT License
     3   *
     4   * Copyright (c) 2022 Lark Technologies Pte. Ltd.
     5   *
     6   * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
     7   *
     8   * The above copyright notice and this permission notice, shall be included in all copies or substantial portions of the Software.
     9   *
    10   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    11   */
    12  
    13  package larkcore
    14  
    15  import (
    16  	"context"
    17  	"encoding/json"
    18  	"errors"
    19  	"fmt"
    20  	"net"
    21  	"net/http"
    22  	"net/url"
    23  	"strings"
    24  )
    25  
    26  var reqTranslator ReqTranslator
    27  
    28  func NewHttpClient(config *Config) {
    29  	if config.HttpClient == nil {
    30  		if config.ReqTimeout == 0 {
    31  			config.HttpClient = http.DefaultClient
    32  		} else {
    33  			config.HttpClient = &http.Client{Timeout: config.ReqTimeout}
    34  		}
    35  	}
    36  }
    37  
    38  func validateTokenType(accessTokenTypes []AccessTokenType, option *RequestOption) error {
    39  	if option == nil || len(accessTokenTypes) == 0 || len(accessTokenTypes) > 1 {
    40  		return nil
    41  	}
    42  
    43  	accessTokenType := accessTokenTypes[0]
    44  	if accessTokenType == AccessTokenTypeTenant && option.UserAccessToken != "" {
    45  		return errors.New("tenant token type not match user access token")
    46  	}
    47  	if accessTokenType == AccessTokenTypeUser && option.TenantAccessToken != "" {
    48  		return errors.New("user token type not match tenant access token")
    49  	}
    50  	return nil
    51  }
    52  
    53  func determineTokenType(accessTokenTypes []AccessTokenType, option *RequestOption, enableTokenCache bool) AccessTokenType {
    54  	if !enableTokenCache {
    55  		if option.UserAccessToken != "" {
    56  			return AccessTokenTypeUser
    57  		}
    58  		if option.TenantAccessToken != "" {
    59  			return AccessTokenTypeTenant
    60  		}
    61  		if option.AppAccessToken != "" {
    62  			return AccessTokenTypeApp
    63  		}
    64  
    65  		return AccessTokenTypeNone
    66  	}
    67  	accessibleTokenTypeSet := make(map[AccessTokenType]struct{})
    68  	accessTokenType := accessTokenTypes[0]
    69  	for _, t := range accessTokenTypes {
    70  		if t == AccessTokenTypeTenant {
    71  			accessTokenType = t // default
    72  		}
    73  		accessibleTokenTypeSet[t] = struct{}{}
    74  	}
    75  	if option.TenantKey != "" {
    76  		if _, ok := accessibleTokenTypeSet[AccessTokenTypeTenant]; ok {
    77  			accessTokenType = AccessTokenTypeTenant
    78  		}
    79  	}
    80  	if option.UserAccessToken != "" {
    81  		if _, ok := accessibleTokenTypeSet[AccessTokenTypeUser]; ok {
    82  			accessTokenType = AccessTokenTypeUser
    83  		}
    84  	}
    85  
    86  	return accessTokenType
    87  }
    88  
    89  func validate(config *Config, option *RequestOption, accessTokenType AccessTokenType) error {
    90  	if config.AppId == "" {
    91  		return &IllegalParamError{msg: "AppId is empty"}
    92  	}
    93  
    94  	if config.AppSecret == "" {
    95  		return &IllegalParamError{msg: "AppSecret is empty"}
    96  	}
    97  
    98  	if !config.EnableTokenCache {
    99  		if accessTokenType == AccessTokenTypeNone {
   100  			return nil
   101  		}
   102  		if option.UserAccessToken == "" && option.TenantAccessToken == "" && option.AppAccessToken == "" {
   103  			return &IllegalParamError{msg: "accessToken is empty"}
   104  		}
   105  	}
   106  
   107  	if config.AppType == AppTypeMarketplace && accessTokenType == AccessTokenTypeTenant && option.TenantKey == "" {
   108  		return &IllegalParamError{msg: "tenant key is empty"}
   109  	}
   110  
   111  	if accessTokenType == AccessTokenTypeUser && option.UserAccessToken == "" {
   112  		return &IllegalParamError{msg: "user access token is empty"}
   113  	}
   114  
   115  	if option.Header != nil {
   116  		if option.Header.Get(HttpHeaderKeyRequestId) != "" {
   117  			return &IllegalParamError{msg: fmt.Sprintf("use %s as header key is not allowed", HttpHeaderKeyRequestId)}
   118  		}
   119  		if option.Header.Get(httpHeaderRequestId) != "" {
   120  			return &IllegalParamError{msg: fmt.Sprintf("use %s as header key is not allowed", httpHeaderRequestId)}
   121  		}
   122  		if option.Header.Get(HttpHeaderKeyLogId) != "" {
   123  			return &IllegalParamError{msg: fmt.Sprintf("use %s as header key is not allowed", HttpHeaderKeyLogId)}
   124  		}
   125  	}
   126  
   127  	return nil
   128  }
   129  
   130  func doSend(ctx context.Context, rawRequest *http.Request, httpClient HttpClient, logger Logger) (*ApiResp, error) {
   131  	if httpClient == nil {
   132  		httpClient = http.DefaultClient
   133  	}
   134  	resp, err := httpClient.Do(rawRequest)
   135  	if err != nil {
   136  		if er, ok := err.(*url.Error); ok {
   137  			if er.Timeout() {
   138  				return nil, &ClientTimeoutError{msg: er.Error()}
   139  			}
   140  
   141  			if e, ok := er.Err.(*net.OpError); ok && e.Op == "dial" {
   142  				return nil, &DialFailedError{msg: er.Error()}
   143  			}
   144  		}
   145  		return nil, err
   146  	}
   147  
   148  	if resp.StatusCode == http.StatusGatewayTimeout {
   149  		logID := resp.Header.Get(HttpHeaderKeyLogId)
   150  		if logID == "" {
   151  			logID = resp.Header.Get(HttpHeaderKeyRequestId)
   152  		}
   153  		logger.Info(ctx, fmt.Sprintf("req path:%s, server time out,requestId:%s",
   154  			rawRequest.URL.RequestURI(), logID))
   155  		return nil, &ServerTimeoutError{msg: "server time out error"}
   156  	}
   157  	body, err := readResponse(resp)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	return &ApiResp{
   163  		StatusCode: resp.StatusCode,
   164  		Header:     resp.Header,
   165  		RawBody:    body,
   166  	}, nil
   167  }
   168  
   169  func Request(ctx context.Context, req *ApiReq, config *Config, options ...RequestOptionFunc) (*ApiResp, error) {
   170  	option := &RequestOption{}
   171  	for _, optionFunc := range options {
   172  		optionFunc(option)
   173  	}
   174  
   175  	err := validateTokenType(req.SupportedAccessTokenTypes, option)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  	accessTokenType := determineTokenType(req.SupportedAccessTokenTypes, option, config.EnableTokenCache)
   180  	err = validate(config, option, accessTokenType)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	return doRequest(ctx, req, accessTokenType, config, option)
   186  
   187  }
   188  
   189  func doRequest(ctx context.Context, httpReq *ApiReq, accessTokenType AccessTokenType, config *Config, option *RequestOption) (*ApiResp, error) {
   190  	var rawResp *ApiResp
   191  	var errResult error
   192  	for i := 0; i < 2; i++ {
   193  		req, err := reqTranslator.translate(ctx, httpReq, accessTokenType, config, option)
   194  		if err != nil {
   195  			return nil, err
   196  		}
   197  
   198  		if config.LogReqAtDebug {
   199  			config.Logger.Debug(ctx, fmt.Sprintf("req:%v", req))
   200  		} else {
   201  			config.Logger.Debug(ctx, fmt.Sprintf("req:%s,%s", httpReq.HttpMethod, httpReq.ApiPath))
   202  		}
   203  		rawResp, err = doSend(ctx, req, config.HttpClient, config.Logger)
   204  		if config.LogReqAtDebug {
   205  			config.Logger.Debug(ctx, fmt.Sprintf("resp:%v", rawResp))
   206  		}
   207  		_, isDialError := err.(*DialFailedError)
   208  		if err != nil && !isDialError {
   209  			return nil, err
   210  		}
   211  		errResult = err
   212  		if isDialError {
   213  			continue
   214  		}
   215  
   216  		fileDownloadSuccess := option.FileDownload && rawResp.StatusCode == http.StatusOK
   217  		if fileDownloadSuccess || !strings.Contains(rawResp.Header.Get(contentTypeHeader), contentTypeJson) {
   218  			break
   219  		}
   220  
   221  		codeError := &CodeError{}
   222  		err = json.Unmarshal(rawResp.RawBody, codeError)
   223  		if err != nil {
   224  			return nil, err
   225  		}
   226  
   227  		code := codeError.Code
   228  		if code == errCodeAppTicketInvalid {
   229  			applyAppTicket(ctx, config)
   230  		}
   231  
   232  		if accessTokenType == AccessTokenTypeNone {
   233  			break
   234  		}
   235  
   236  		if !config.EnableTokenCache {
   237  			break
   238  		}
   239  
   240  		if code != errCodeAccessTokenInvalid && code != errCodeAppAccessTokenInvalid &&
   241  			code != errCodeTenantAccessTokenInvalid {
   242  			break
   243  		}
   244  	}
   245  
   246  	if errResult != nil {
   247  		return nil, errResult
   248  	}
   249  	return rawResp, nil
   250  }