github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/requests/client.go (about)

     1  /*
     2   * Copyright (C) 2017 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package requests
    19  
    20  import (
    21  	"encoding/json"
    22  	"fmt"
    23  	"io"
    24  	"net/http"
    25  	"net/url"
    26  	"sync"
    27  	"time"
    28  
    29  	"github.com/mysteriumnetwork/go-rest/apierror"
    30  	"github.com/mysteriumnetwork/node/logconfig/httptrace"
    31  	"github.com/mysteriumnetwork/node/metadata"
    32  )
    33  
    34  const (
    35  	// DefaultTimeout is a default HTTP client timeout.
    36  	DefaultTimeout = 20 * time.Second
    37  )
    38  
    39  // NewHTTPClientWithTransport creates a new HTTP client with custom transport.
    40  func NewHTTPClientWithTransport(transport *http.Transport, timeout time.Duration) *HTTPClient {
    41  	c := &HTTPClient{
    42  		clientFactory: func(proxyPort int) *http.Client {
    43  			t := transport.Clone()
    44  
    45  			if proxyPort > 0 {
    46  				url, _ := url.Parse(fmt.Sprintf("http://localhost:%d", proxyPort))
    47  				t.Proxy = http.ProxyURL(url)
    48  			}
    49  
    50  			return &http.Client{
    51  				Timeout:   timeout,
    52  				Transport: setUserAgent(t, getUserAgent()),
    53  			}
    54  		},
    55  	}
    56  	// Create initial clean before any HTTP request is made.
    57  	c.client = c.clientFactory(0)
    58  
    59  	return c
    60  }
    61  
    62  func setUserAgent(transport http.RoundTripper, userAgent string) http.RoundTripper {
    63  	return &userAgenter{
    64  		transport: transport,
    65  		Agent:     userAgent,
    66  	}
    67  }
    68  
    69  func getUserAgent() string {
    70  	return fmt.Sprintf("Mysterium node(%v; https://mysterium.network)", metadata.VersionAsString())
    71  }
    72  
    73  type userAgenter struct {
    74  	transport http.RoundTripper
    75  	Agent     string
    76  }
    77  
    78  func (ua *userAgenter) RoundTrip(r *http.Request) (*http.Response, error) {
    79  	r.Header.Set("User-Agent", ua.Agent)
    80  	return ua.transport.RoundTrip(r)
    81  }
    82  
    83  // NewHTTPClient creates a new HTTP client.
    84  func NewHTTPClient(srcIP string, timeout time.Duration) *HTTPClient {
    85  	return NewHTTPClientWithTransport(NewTransport(NewDialer(srcIP).DialContext), timeout)
    86  }
    87  
    88  // HTTPClient describes a client for performing HTTP requests.
    89  type HTTPClient struct {
    90  	client        *http.Client
    91  	clientMu      sync.Mutex
    92  	clientFactory func(proxyPort int) *http.Client
    93  }
    94  
    95  // Do send an HTTP request and returns an HTTP response.
    96  func (c *HTTPClient) Do(req *http.Request) (*http.Response, error) {
    97  	return c.resolveClient().Do(req)
    98  }
    99  
   100  // DoViaProxy send an HTTP request via proxy and returns an HTTP response.
   101  func (c *HTTPClient) DoViaProxy(req *http.Request, proxyPort int) (*http.Response, error) {
   102  	return c.clientFactory(proxyPort).Do(req)
   103  }
   104  
   105  // DoRequest performs HTTP requests and parses error without returning response.
   106  func (c *HTTPClient) DoRequest(req *http.Request) error {
   107  	response, err := c.Do(req)
   108  	if err != nil {
   109  		return err
   110  	}
   111  	defer response.Body.Close()
   112  
   113  	return ParseResponseError(response)
   114  }
   115  
   116  // DoRequestViaProxy performs HTTP requests via proxy and parses error without returning response.
   117  func (c *HTTPClient) DoRequestViaProxy(req *http.Request, proxyPort int) error {
   118  	response, err := c.DoViaProxy(req, proxyPort)
   119  	if err != nil {
   120  		return err
   121  	}
   122  	defer response.Body.Close()
   123  
   124  	return ParseResponseError(response)
   125  }
   126  
   127  // DoRequestAndParseResponse performs HTTP requests and response from JSON.
   128  func (c *HTTPClient) DoRequestAndParseResponse(req *http.Request, resp interface{}) error {
   129  	response, err := c.Do(req)
   130  	if err != nil {
   131  		return err
   132  	}
   133  	defer response.Body.Close()
   134  
   135  	httptrace.TraceRequestResponse(req, response)
   136  
   137  	err = ParseResponseError(response)
   138  	if err != nil {
   139  		return err
   140  	}
   141  
   142  	return ParseResponseJSON(response, &resp)
   143  }
   144  
   145  // DoRequestViaProxyAndParseResponse performs HTTP requests and response from JSON.
   146  func (c *HTTPClient) DoRequestViaProxyAndParseResponse(req *http.Request, resp interface{}, proxyPort int) error {
   147  	response, err := c.DoViaProxy(req, proxyPort)
   148  	if err != nil {
   149  		return err
   150  	}
   151  	defer response.Body.Close()
   152  
   153  	httptrace.TraceRequestResponse(req, response)
   154  
   155  	err = ParseResponseError(response)
   156  	if err != nil {
   157  		return err
   158  	}
   159  
   160  	return ParseResponseJSON(response, &resp)
   161  }
   162  
   163  func (c *HTTPClient) resolveClient() *http.Client {
   164  	c.clientMu.Lock()
   165  	defer c.clientMu.Unlock()
   166  	if c.client != nil {
   167  		return c.client
   168  	}
   169  	c.client = c.clientFactory(0)
   170  	return c.client
   171  }
   172  
   173  // ParseResponseJSON parses http.Response into given struct.
   174  func ParseResponseJSON(response *http.Response, dto interface{}) error {
   175  	err := json.NewDecoder(response.Body).Decode(dto)
   176  	if err == io.EOF {
   177  		return nil
   178  	}
   179  	return err
   180  }
   181  
   182  // ParseResponseError parses http.Response error.
   183  func ParseResponseError(response *http.Response) error {
   184  	if response.StatusCode < 200 || response.StatusCode >= 300 {
   185  		return apierror.TryParse(response)
   186  	}
   187  	return nil
   188  }