github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/httputil/httputil.go (about)

     1  // Copyright 2020 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package httputil
    15  
    16  import (
    17  	"context"
    18  	"io"
    19  	"net/http"
    20  	"net/url"
    21  	"strings"
    22  	"time"
    23  
    24  	"github.com/pingcap/errors"
    25  	"github.com/pingcap/tiflow/pkg/security"
    26  )
    27  
    28  // Client wraps an HTTP client and support TLS requests.
    29  type Client struct {
    30  	client http.Client
    31  }
    32  
    33  // NewClient creates an HTTP client with the given Credential.
    34  func NewClient(credential *security.Credential) (*Client, error) {
    35  	transport := http.DefaultTransport
    36  	if credential != nil {
    37  		tlsConf, err := credential.ToTLSConfigWithVerify()
    38  		if err != nil {
    39  			return nil, err
    40  		}
    41  		if tlsConf != nil {
    42  			httpTrans := http.DefaultTransport.(*http.Transport).Clone()
    43  			httpTrans.TLSClientConfig = tlsConf
    44  			transport = httpTrans
    45  		}
    46  	}
    47  	// TODO: specific timeout in http client
    48  	return &Client{
    49  		client: http.Client{Transport: transport},
    50  	}, nil
    51  }
    52  
    53  // Timeout returns the timeout of the client.
    54  func (c *Client) Timeout() time.Duration {
    55  	return c.client.Timeout
    56  }
    57  
    58  // SetTimeout specifies a time limit for requests made by this Client.
    59  // See http.Client.Timeout.
    60  func (c *Client) SetTimeout(timeout time.Duration) {
    61  	c.client.Timeout = timeout
    62  }
    63  
    64  // Get issues a GET to the specified URL with context.
    65  // See http.Client.Get.
    66  func (c *Client) Get(ctx context.Context, url string) (resp *http.Response, err error) {
    67  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    68  	if err != nil {
    69  		return nil, errors.Trace(err)
    70  	}
    71  	return c.client.Do(req)
    72  }
    73  
    74  // PostForm issues a POST to the specified URL,
    75  // with data's keys and values URL-encoded as the request body.
    76  // See http.Client.PostForm.
    77  func (c *Client) PostForm(
    78  	ctx context.Context, url string, data url.Values,
    79  ) (resp *http.Response, err error) {
    80  	req, err := http.NewRequestWithContext(
    81  		ctx, http.MethodPost, url, strings.NewReader(data.Encode()))
    82  	if err != nil {
    83  		return nil, errors.Trace(err)
    84  	}
    85  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    86  	return c.client.Do(req)
    87  }
    88  
    89  // Do sends an HTTP request and returns an HTTP response.
    90  // See http.Client.Do.
    91  func (c *Client) Do(req *http.Request) (*http.Response, error) {
    92  	return c.client.Do(req)
    93  }
    94  
    95  // DoRequest sends an request and returns an HTTP response content.
    96  func (c *Client) DoRequest(
    97  	ctx context.Context, url, method string, headers http.Header, body io.Reader,
    98  ) ([]byte, error) {
    99  	req, err := http.NewRequestWithContext(ctx, method, url, body)
   100  	if err != nil {
   101  		return nil, errors.Trace(err)
   102  	}
   103  
   104  	for key, values := range headers {
   105  		for _, v := range values {
   106  			req.Header.Add(key, v)
   107  		}
   108  	}
   109  
   110  	resp, err := c.Do(req)
   111  	if err != nil {
   112  		return nil, errors.Trace(err)
   113  	}
   114  	defer resp.Body.Close()
   115  
   116  	content, err := io.ReadAll(resp.Body)
   117  	if err != nil {
   118  		return nil, errors.Trace(err)
   119  	}
   120  	// treat http 2xx as valid
   121  	if resp.StatusCode/100 != 2 {
   122  		return nil, errors.Errorf("[%d] %s", resp.StatusCode, content)
   123  	}
   124  	return content, nil
   125  }
   126  
   127  // CloseIdleConnections closes any connections are now sitting idle.
   128  // See http.Client.CloseIdleConnections.
   129  func (c *Client) CloseIdleConnections() {
   130  	c.client.CloseIdleConnections()
   131  }