github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/http/http.go (about)

     1  package http
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"github.com/isyscore/isc-gobase/config"
     8  	"github.com/isyscore/isc-gobase/logger"
     9  	gourl "net/url"
    10  
    11  	//"github.com/isyscore/isc-gobase/goid"
    12  	"io"
    13  	"log"
    14  	"net"
    15  	"net/http"
    16  	"strconv"
    17  	"strings"
    18  	"time"
    19  )
    20  
    21  var httpClient = createHTTPClient()
    22  
    23  const (
    24  	MaxIdleConns          int    = 100
    25  	MaxIdleConnsPerHost   int    = 100
    26  	IdleConnTimeout       int    = 90
    27  	ContentTypeJson       string = "application/json; charset=utf-8"
    28  	ContentTypeHtml       string = "text/html; charset=utf-8"
    29  	ContentTypeText       string = "text/plain; charset=utf-8"
    30  	ContentTypeCss        string = "text/css; charset=utf-8"
    31  	ContentTypeJavaScript string = "application/x-javascript; charset=utf-8"
    32  	ContentTypeJpeg       string = "image/jpeg"
    33  	ContentTypePng        string = "image/png"
    34  	ContentTypeGif        string = "image/gif"
    35  	ContentTypeAll        string = "*/*"
    36  	ContentPostForm       string = "application/x-www-form-urlencoded"
    37  )
    38  
    39  var NetHttpHooks []GobaseHttpHook
    40  
    41  func init() {
    42  	NetHttpHooks = []GobaseHttpHook{}
    43  }
    44  
    45  type GobaseHttpHook interface {
    46  	Before(ctx context.Context, req *http.Request) (context.Context, http.Header)
    47  	After(ctx context.Context, rsp *http.Response, rspCode int, rspData any, err error)
    48  }
    49  
    50  func AddHook(httpHook GobaseHttpHook) {
    51  	NetHttpHooks = append(NetHttpHooks, httpHook)
    52  }
    53  
    54  type NetError struct {
    55  	ErrMsg string
    56  }
    57  
    58  func (error *NetError) Error() string {
    59  	return error.ErrMsg
    60  }
    61  
    62  type DataResponse[T any] struct {
    63  	Code    int    `json:"code"`
    64  	Message string `json:"message"`
    65  	Data    T      `json:"data"`
    66  }
    67  
    68  // createHTTPClient for connection re-use
    69  func createHTTPClient() *http.Client {
    70  	config.LoadConfig()
    71  	client := &http.Client{}
    72  
    73  	// 从配置文件中载入配置
    74  	loadClientFromConfig(client)
    75  
    76  	return client
    77  }
    78  
    79  func loadClientFromConfig(client *http.Client) {
    80  	if config.GetValueString("base.http.timeout") != "" {
    81  		t, err := time.ParseDuration(config.GetValueString("base.http.timeout"))
    82  		if err != nil {
    83  			logger.Warn("读取配置【base.http.timeout】异常", err)
    84  		} else {
    85  			client.Timeout = t
    86  		}
    87  	}
    88  
    89  	transport := &http.Transport{}
    90  	if config.GetValueString("base.http.transport.tls-handshake-timeout") != "" {
    91  		t, err := time.ParseDuration(config.GetValueString("base.http.transport.tls-handshake-timeout"))
    92  		if err != nil {
    93  			logger.Warn("读取配置【base.http.transport.tls-handshake-timeout】异常", err)
    94  		} else {
    95  			transport.TLSHandshakeTimeout = t
    96  		}
    97  	}
    98  
    99  	if config.GetValueString("base.http.transport.disable-keep-alives") != "" {
   100  		transport.DisableKeepAlives = config.GetValueBool("base.http.transport.disable-keep-alives")
   101  	}
   102  
   103  	if config.GetValueString("base.http.transport.disable-compression") != "" {
   104  		transport.DisableCompression = config.GetValueBool("base.http.transport.disable-compression")
   105  	}
   106  
   107  	if config.GetValueString("base.http.transport.max-idle-conns") != "" {
   108  		transport.MaxIdleConns = config.GetValueInt("base.http.transport.max-idle-conns")
   109  	}
   110  
   111  	if config.GetValueString("base.http.transport.max-idle-conns-per-host") != "" {
   112  		transport.MaxIdleConnsPerHost = config.GetValueInt("base.http.transport.max-idle-conns-per-host")
   113  	}
   114  
   115  	if config.GetValueString("base.http.transport.max-conns-per-host") != "" {
   116  		transport.MaxConnsPerHost = config.GetValueInt("base.http.transport.max-conns-per-host")
   117  	}
   118  
   119  	if config.GetValueString("base.http.transport.idle-conn-timeout") != "" {
   120  		t, err := time.ParseDuration(config.GetValueString("base.http.transport.idle-conn-timeout"))
   121  		if err != nil {
   122  			logger.Warn("读取配置【base.http.transport.idle-conn-timeout】异常", err)
   123  		} else {
   124  			transport.IdleConnTimeout = t
   125  		}
   126  	}
   127  
   128  	if config.GetValueString("base.http.transport.response-header-timeout") != "" {
   129  		t, err := time.ParseDuration(config.GetValueString("base.http.transport.response-header-timeout"))
   130  		if err != nil {
   131  			logger.Warn("读取配置【base.http.transport.response-header-timeout】异常", err)
   132  		} else {
   133  			transport.ResponseHeaderTimeout = t
   134  		}
   135  	}
   136  
   137  	if config.GetValueString("base.http.transport.expect-continue-timeout") != "" {
   138  		t, err := time.ParseDuration(config.GetValueString("base.http.transport.expect-continue-timeout"))
   139  		if err != nil {
   140  			logger.Warn("读取配置【base.http.transport.expect-continue-timeout】异常", err)
   141  		} else {
   142  			transport.ExpectContinueTimeout = t
   143  		}
   144  	}
   145  
   146  	if config.GetValueString("base.http.transport.max-response-header-bytes") != "" {
   147  		transport.MaxResponseHeaderBytes = config.GetValueInt64("base.http.transport.max-response-header-bytes")
   148  	}
   149  
   150  	if config.GetValueString("base.http.transport.write-buffer-size") != "" {
   151  		transport.WriteBufferSize = config.GetValueInt("base.http.transport.write-buffer-size")
   152  	}
   153  
   154  	if config.GetValueString("base.http.transport.read-buffer-size") != "" {
   155  		transport.ReadBufferSize = config.GetValueInt("base.http.transport.read-buffer-size")
   156  	}
   157  
   158  	if config.GetValueString("base.http.transport.force-attempt-HTTP2") != "" {
   159  		transport.ForceAttemptHTTP2 = config.GetValueBool("base.http.transport.force-attempt-HTTP2")
   160  	}
   161  
   162  	transport.DialContext = loadConfigOfDialContext()
   163  	client.Transport = transport
   164  }
   165  
   166  func loadConfigOfDialContext() func(ctx context.Context, network, addr string) (net.Conn, error) {
   167  	dialer := &net.Dialer{}
   168  	if config.GetValueString("base.http.transport.dial-context.timeout") != "" {
   169  		t, err := time.ParseDuration(config.GetValueString("base.http.transport.dial-context.timeout"))
   170  		if err != nil {
   171  			logger.Warn("读取配置【base.http.transport.dial-context.timeout】异常", err)
   172  		} else {
   173  			dialer.Timeout = t
   174  		}
   175  	}
   176  
   177  	if config.GetValueString("base.http.transport.dial-context.keep-alive") != "" {
   178  		t, err := time.ParseDuration(config.GetValueString("base.http.transport.dial-context.keep-alive"))
   179  		if err != nil {
   180  			logger.Warn("读取配置【base.http.transport.dial-context.keep-alive】异常", err)
   181  		} else {
   182  			dialer.KeepAlive = t
   183  		}
   184  	}
   185  	return dialer.DialContext
   186  }
   187  
   188  func SetHttpClient(httpClientOuter *http.Client) {
   189  	httpClient = httpClientOuter
   190  }
   191  
   192  func GetClient() *http.Client {
   193  	return httpClient
   194  }
   195  
   196  func Do(httpRequest *http.Request) (int, http.Header, any, error) {
   197  	ctx := context.Background()
   198  	for _, hook := range NetHttpHooks {
   199  		_ctx, httpHeader := hook.Before(ctx, httpRequest)
   200  		httpRequest.Header = httpHeader
   201  		ctx = _ctx
   202  	}
   203  
   204  	resp, err := httpClient.Do(httpRequest)
   205  	rspCode, rspHead, rspData, err := doParseResponse(resp, err)
   206  	for _, hook := range NetHttpHooks {
   207  		hook.After(ctx, resp, rspCode, rspData, err)
   208  	}
   209  	return rspCode, rspHead, rspData, err
   210  }
   211  
   212  // ------------------ get ------------------
   213  
   214  func GetSimple(url string) (int, http.Header, any, error) {
   215  	return Get(url, nil, nil)
   216  }
   217  
   218  func GetSimpleOfStandard(url string) (int, http.Header, any, error) {
   219  	return GetOfStandard(url, nil, nil)
   220  }
   221  
   222  func Get(url string, header http.Header, parameterMap map[string]string) (int, http.Header, any, error) {
   223  	httpRequest, err := http.NewRequest("GET", UrlWithParameter(url, parameterMap), nil)
   224  	if err != nil {
   225  		log.Printf("NewRequest error(%v)\n", err)
   226  		return -1, nil, nil, err
   227  	}
   228  
   229  	if header != nil {
   230  		httpRequest.Header = header
   231  	}
   232  
   233  	return call(httpRequest, url)
   234  }
   235  
   236  func GetOfStandard(url string, header http.Header, parameterMap map[string]string) (int, http.Header, any, error) {
   237  	httpRequest, err := http.NewRequest("GET", UrlWithParameter(url, parameterMap), nil)
   238  	if err != nil {
   239  		log.Printf("NewRequest error(%v)\n", err)
   240  		return -1, nil, nil, err
   241  	}
   242  
   243  	if header != nil {
   244  		httpRequest.Header = header
   245  	}
   246  
   247  	return callToStandard(httpRequest, url)
   248  }
   249  
   250  // ------------------ head ------------------
   251  
   252  func HeadSimple(url string) error {
   253  	return Head(url, nil, nil)
   254  }
   255  
   256  func Head(url string, header http.Header, parameterMap map[string]string) error {
   257  	httpRequest, err := http.NewRequest("GET", UrlWithParameter(url, parameterMap), nil)
   258  	if err != nil {
   259  		log.Printf("NewRequest error(%v)\n", err)
   260  		return err
   261  	}
   262  
   263  	if header != nil {
   264  		httpRequest.Header = header
   265  	}
   266  
   267  	return callIgnoreReturn(httpRequest, url)
   268  }
   269  
   270  // ------------------ post ------------------
   271  
   272  func PostSimple(url string, body any) (int, http.Header, any, error) {
   273  	return Post(url, nil, nil, body)
   274  }
   275  
   276  func PostSimpleOfStandard(url string, body any) (int, http.Header, any, error) {
   277  	return PostOfStandard(url, nil, nil, body)
   278  }
   279  
   280  func Post(url string, header http.Header, parameterMap map[string]string, body any) (int, http.Header, any, error) {
   281  	bytes, _ := json.Marshal(body)
   282  	payload := strings.NewReader(string(bytes))
   283  	httpRequest, err := http.NewRequest("POST", UrlWithParameter(url, parameterMap), payload)
   284  	if err != nil {
   285  		log.Printf("NewRequest error(%v)\n", err)
   286  		return -1, nil, nil, err
   287  	}
   288  
   289  	if header != nil {
   290  		httpRequest.Header = header
   291  	}
   292  	httpRequest.Header.Add("Content-Type", ContentTypeJson)
   293  	return call(httpRequest, url)
   294  }
   295  
   296  func PostOfStandard(url string, header http.Header, parameterMap map[string]string, body any) (int, http.Header, any, error) {
   297  	bytes, _ := json.Marshal(body)
   298  	payload := strings.NewReader(string(bytes))
   299  	httpRequest, err := http.NewRequest("POST", UrlWithParameter(url, parameterMap), payload)
   300  	if err != nil {
   301  		log.Printf("NewRequest error(%v)\n", err)
   302  		return -1, nil, nil, err
   303  	}
   304  
   305  	if header != nil {
   306  		httpRequest.Header = header
   307  	}
   308  	httpRequest.Header.Add("Content-Type", ContentTypeJson)
   309  	return callToStandard(httpRequest, url)
   310  }
   311  
   312  func PostForm(url string, header http.Header, parameterMap map[string]any) (int, http.Header, any, error) {
   313  	// resolve parameterMap
   314  	var httpRequest http.Request
   315  	_ = httpRequest.ParseForm()
   316  	if parameterMap != nil {
   317  		for k, v := range parameterMap {
   318  			httpRequest.Form.Add(k, fmt.Sprintf("%v", v))
   319  		}
   320  	}
   321  	body := strings.NewReader(httpRequest.Form.Encode())
   322  	// 简单封装一下
   323  	httpReq, err := http.NewRequest("POST", url, body)
   324  	if err != nil {
   325  		return 0, nil, nil, err
   326  	}
   327  	// resolve header
   328  	if header != nil {
   329  		httpReq.Header = header
   330  	}
   331  	httpReq.Header.Set("Content-Type", ContentPostForm)
   332  
   333  	ctx := context.Background()
   334  	for _, hook := range NetHttpHooks {
   335  		_ctx, httpHeader := hook.Before(ctx, httpReq)
   336  		httpReq.Header = httpHeader
   337  		ctx = _ctx
   338  	}
   339  
   340  	resp, err := httpClient.Do(httpReq)
   341  	rspCode, rspHead, rspData, err := doParseResponse(resp, err)
   342  	for _, hook := range NetHttpHooks {
   343  		hook.After(ctx, resp, rspCode, rspData, err)
   344  	}
   345  
   346  	return rspCode, rspHead, rspData, err
   347  }
   348  
   349  // ------------------ put ------------------
   350  
   351  func PutSimple(url string, body any) (int, http.Header, any, error) {
   352  	return Put(url, nil, nil, body)
   353  }
   354  
   355  func PutSimpleOfStandard(url string, body any) (int, http.Header, any, error) {
   356  	return PutOfStandard(url, nil, nil, body)
   357  }
   358  
   359  func Put(url string, header http.Header, parameterMap map[string]string, body any) (int, http.Header, any, error) {
   360  	bytes, _ := json.Marshal(body)
   361  	payload := strings.NewReader(string(bytes))
   362  	httpRequest, err := http.NewRequest("PUT", UrlWithParameter(url, parameterMap), payload)
   363  	if err != nil {
   364  		log.Printf("NewRequest error(%v)\n", err)
   365  		return -1, nil, nil, err
   366  	}
   367  
   368  	if header != nil {
   369  		httpRequest.Header = header
   370  	}
   371  	httpRequest.Header.Add("Content-Type", ContentTypeJson)
   372  	return call(httpRequest, url)
   373  }
   374  
   375  func PutOfStandard(url string, header http.Header, parameterMap map[string]string, body any) (int, http.Header, any, error) {
   376  	bytes, _ := json.Marshal(body)
   377  	payload := strings.NewReader(string(bytes))
   378  	httpRequest, err := http.NewRequest("PUT", UrlWithParameter(url, parameterMap), payload)
   379  	if err != nil {
   380  		log.Printf("NewRequest error(%v)\n", err)
   381  		return -1, nil, nil, err
   382  	}
   383  
   384  	if header != nil {
   385  		httpRequest.Header = header
   386  	}
   387  	httpRequest.Header.Add("Content-Type", ContentTypeJson)
   388  	return callToStandard(httpRequest, url)
   389  }
   390  
   391  // ------------------ delete ------------------
   392  
   393  func DeleteSimple(url string) (int, http.Header, any, error) {
   394  	return Get(url, nil, nil)
   395  }
   396  
   397  func DeleteSimpleOfStandard(url string) (int, http.Header, any, error) {
   398  	return GetOfStandard(url, nil, nil)
   399  }
   400  
   401  func Delete(url string, header http.Header, parameterMap map[string]string) (int, http.Header, any, error) {
   402  	httpRequest, err := http.NewRequest("DELETE", UrlWithParameter(url, parameterMap), nil)
   403  	if err != nil {
   404  		log.Printf("NewRequest error(%v)\n", err)
   405  		return -1, nil, nil, err
   406  	}
   407  
   408  	if header != nil {
   409  		httpRequest.Header = header
   410  	}
   411  
   412  	return call(httpRequest, url)
   413  }
   414  
   415  func DeleteOfStandard(url string, header http.Header, parameterMap map[string]string) (int, http.Header, any, error) {
   416  	httpRequest, err := http.NewRequest("DELETE", UrlWithParameter(url, parameterMap), nil)
   417  	if err != nil {
   418  		log.Printf("NewRequest error(%v)\n", err)
   419  		return -1, nil, nil, err
   420  	}
   421  
   422  	if header != nil {
   423  		httpRequest.Header = header
   424  	}
   425  
   426  	return callToStandard(httpRequest, url)
   427  }
   428  
   429  // ------------------ patch ------------------
   430  
   431  func PatchSimple(url string, body any) (int, http.Header, any, error) {
   432  	return Post(url, nil, nil, body)
   433  }
   434  
   435  func PatchSimpleOfStandard(url string, body any) (int, http.Header, any, error) {
   436  	return PostOfStandard(url, nil, nil, body)
   437  }
   438  
   439  func Patch(url string, header http.Header, parameterMap map[string]string, body any) (int, http.Header, any, error) {
   440  	bytes, _ := json.Marshal(body)
   441  	payload := strings.NewReader(string(bytes))
   442  	httpRequest, err := http.NewRequest("PATCH", UrlWithParameter(url, parameterMap), payload)
   443  	if err != nil {
   444  		log.Printf("NewRequest error(%v)\n", err)
   445  		return -1, nil, nil, err
   446  	}
   447  
   448  	if header != nil {
   449  		httpRequest.Header = header
   450  	}
   451  	httpRequest.Header.Add("Content-Type", ContentTypeJson)
   452  	return call(httpRequest, url)
   453  }
   454  
   455  func PatchOfStandard(url string, header http.Header, parameterMap map[string]string, body any) (int, http.Header, any, error) {
   456  	bytes, _ := json.Marshal(body)
   457  	payload := strings.NewReader(string(bytes))
   458  	httpRequest, err := http.NewRequest("PATCH", UrlWithParameter(url, parameterMap), payload)
   459  	if err != nil {
   460  		log.Printf("NewRequest error(%v)\n", err)
   461  		return -1, nil, nil, err
   462  	}
   463  
   464  	if header != nil {
   465  		httpRequest.Header = header
   466  	}
   467  	httpRequest.Header.Add("Content-Type", ContentTypeJson)
   468  	return callToStandard(httpRequest, url)
   469  }
   470  
   471  func call(httpRequest *http.Request, url string) (int, http.Header, any, error) {
   472  	ctx := context.Background()
   473  
   474  	for _, hook := range NetHttpHooks {
   475  		_ctx, httpHeader := hook.Before(ctx, httpRequest)
   476  		httpRequest.Header = httpHeader
   477  		ctx = _ctx
   478  	}
   479  
   480  	httpResponse, err := httpClient.Do(httpRequest)
   481  	rspCode, rspHead, rspData, err := doParseResponse(httpResponse, err)
   482  
   483  	for _, hook := range NetHttpHooks {
   484  		hook.After(ctx, httpResponse, rspCode, rspData, err)
   485  	}
   486  	return rspCode, rspHead, rspData, err
   487  }
   488  
   489  func doParseResponse(httpResponse *http.Response, err error) (int, http.Header, any, error) {
   490  	if err != nil && httpResponse == nil {
   491  		log.Printf("Error sending request to API endpoint. %+v", err)
   492  		return -1, nil, nil, &NetError{ErrMsg: "Error sending request, err" + err.Error()}
   493  	} else {
   494  		if httpResponse == nil {
   495  			log.Printf("httpResponse is nil\n")
   496  			return -1, nil, nil, nil
   497  		}
   498  		defer func(Body io.ReadCloser) {
   499  			err := Body.Close()
   500  			if err != nil {
   501  				log.Printf("Body close error(%v)", err)
   502  			}
   503  		}(httpResponse.Body)
   504  
   505  		code := httpResponse.StatusCode
   506  		headers := httpResponse.Header
   507  		if code != http.StatusOK {
   508  			body, _ := io.ReadAll(httpResponse.Body)
   509  			return code, headers, nil, &NetError{ErrMsg: "remote error, url: code " + strconv.Itoa(code) + ", message: " + string(body)}
   510  		}
   511  
   512  		// We have seen inconsistencies even when we get 200 OK response
   513  		body, err := io.ReadAll(httpResponse.Body)
   514  		if err != nil {
   515  			log.Printf("Couldn't parse response body(%v)", err)
   516  			return code, headers, nil, &NetError{ErrMsg: "Couldn't parse response body, err: " + err.Error()}
   517  		}
   518  
   519  		return code, headers, body, nil
   520  	}
   521  }
   522  
   523  // ------------------ trace ------------------
   524  // ------------------ options ------------------
   525  // 暂时先不处理
   526  
   527  func callIgnoreReturn(httpRequest *http.Request, url string) error {
   528  	ctx := context.Background()
   529  
   530  	for _, hook := range NetHttpHooks {
   531  		_ctx, httpHeader := hook.Before(ctx, httpRequest)
   532  		httpRequest.Header = httpHeader
   533  		ctx = _ctx
   534  	}
   535  
   536  	httpResponse, err := httpClient.Do(httpRequest)
   537  	rspCode, _, rspData, err := doParseResponse(httpResponse, err)
   538  
   539  	for _, hook := range NetHttpHooks {
   540  		hook.After(ctx, httpResponse, rspCode, rspData, err)
   541  	}
   542  	return err
   543  }
   544  
   545  func callToStandard(httpRequest *http.Request, url string) (int, http.Header, any, error) {
   546  	return parseStandard(call(httpRequest, url))
   547  }
   548  
   549  func parseStandard(statusCode int, headers http.Header, responseResult any, errs error) (int, http.Header, any, error) {
   550  	if errs != nil {
   551  		return statusCode, headers, nil, errs
   552  	}
   553  	var standRsp DataResponse[any]
   554  	err := json.Unmarshal(responseResult.([]byte), &standRsp)
   555  	if err != nil {
   556  		return statusCode, headers, nil, err
   557  	}
   558  
   559  	// 判断业务的失败信息
   560  	if standRsp.Code != 0 && standRsp.Code != 200 {
   561  		return statusCode, headers, nil, &NetError{ErrMsg: fmt.Sprintf("remote err, bizCode=%d, message=%s", standRsp.Code, standRsp.Message)}
   562  	}
   563  
   564  	return statusCode, headers, standRsp.Data, nil
   565  }
   566  
   567  func UrlWithParameter(url string, parameterMap map[string]string) string {
   568  	goUrl, err := gourl.Parse(url)
   569  	if err != nil {
   570  		//Here, in order to be compatible with the old logic, if parsing fails, it will call the old method.
   571  		return oldUrlWithParameter(url, parameterMap)
   572  	}
   573  	queryValue := goUrl.Query()
   574  	for key, value := range parameterMap {
   575  		queryValue.Set(key, value)
   576  	}
   577  	goUrl.RawQuery = queryValue.Encode()
   578  	return goUrl.String()
   579  }
   580  
   581  // Deprecated
   582  func oldUrlWithParameter(url string, parameterMap map[string]string) string {
   583  	if parameterMap == nil || len(parameterMap) == 0 {
   584  		return url
   585  	}
   586  
   587  	url += "?"
   588  
   589  	var parameters []string
   590  	for key, value := range parameterMap {
   591  		parameters = append(parameters, key+"="+value)
   592  	}
   593  
   594  	return url + strings.Join(parameters, "&")
   595  }