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 }