88 "io"
99 "maps"
1010 "net/http"
11+ "net/http/httptrace"
1112 "slices"
1213 "strings"
1314
@@ -169,9 +170,10 @@ func responseHeadersFromNetHeader(h http.Header) (map[string]string, map[string]
169170}
170171
171172type httpClient struct {
172- client * safeurl.WrappedClient
173- config HTTPClientConfig
174- lggr logger.Logger
173+ client * safeurl.WrappedClient
174+ config HTTPClientConfig
175+ lggr logger.Logger
176+ metrics * httpClientMetrics
175177}
176178
177179// NewHTTPClient creates a new NewHTTPClient
@@ -185,6 +187,12 @@ func NewHTTPClient(config HTTPClientConfig, lggr logger.Logger) (HTTPClient, err
185187 config .AllowedPorts = append (config .AllowedPorts , expanded ... )
186188 }
187189 config .ApplyDefaults ()
190+
191+ defaultTransport , ok := http .DefaultTransport .(* http.Transport )
192+ if ! ok {
193+ return nil , errors .New ("could not coerce http.DefaultTransport to *http.Transport" )
194+ }
195+
188196 safeConfig := safeurl .
189197 GetConfigBuilder ().
190198 SetAllowedIPs (config .AllowedIPs ... ).
@@ -194,12 +202,19 @@ func NewHTTPClient(config HTTPClientConfig, lggr logger.Logger) (HTTPClient, err
194202 SetBlockedIPs (config .BlockedIPs ... ).
195203 SetBlockedIPsCIDR (config .BlockedIPsCIDR ... ).
196204 SetCheckRedirect (disableRedirects ).
205+ SetTransport (defaultTransport ).
197206 Build ()
198207
208+ metrics , err := newHTTPClientMetrics ()
209+ if err != nil {
210+ return nil , err
211+ }
212+
199213 return & httpClient {
200- config : config ,
201- client : safeurl .Client (safeConfig ),
202- lggr : lggr ,
214+ config : config ,
215+ client : safeurl .Client (safeConfig ),
216+ lggr : lggr ,
217+ metrics : metrics ,
203218 }, nil
204219}
205220
@@ -297,8 +312,13 @@ func (c *httpClient) Send(ctx context.Context, req HTTPRequest) (*HTTPResponse,
297312 timeoutCtx , cancel := context .WithTimeout (ctx , to )
298313 defer cancel ()
299314
315+ requestStart := time .Now ()
316+ trace , traceState := newClientTrace (ctx , req .Method , requestStart , c .metrics )
317+ timeoutCtx = httptrace .WithClientTrace (timeoutCtx , trace )
318+
300319 r , err := http .NewRequestWithContext (timeoutCtx , req .Method , req .URL , bytes .NewBuffer (req .Body ))
301320 if err != nil {
321+ c .metrics .recordTotal (ctx , req .Method , 0 , false , false , time .Since (requestStart ))
302322 return nil , err
303323 }
304324 for k , values := range requestToNetHeader (req ) {
@@ -309,6 +329,7 @@ func (c *httpClient) Send(ctx context.Context, req HTTPRequest) (*HTTPResponse,
309329
310330 resp , err := c .client .Do (r )
311331 if err != nil {
332+ c .metrics .recordTotal (ctx , req .Method , 0 , false , traceState .connReused .Load (), time .Since (requestStart ))
312333 if isBlockedRequest (err ) {
313334 c .lggr .Warnw ("HTTP request blocked" , "err" , err )
314335 return nil , fmt .Errorf ("%w: %w" , ErrBlockedRequest , err )
@@ -324,10 +345,13 @@ func (c *httpClient) Send(ctx context.Context, req HTTPRequest) (*HTTPResponse,
324345 reader := http .MaxBytesReader (nil , resp .Body , int64 (n ))
325346 body , err := io .ReadAll (reader )
326347 if err != nil {
348+ c .metrics .recordTotal (ctx , req .Method , resp .StatusCode , false , traceState .connReused .Load (), time .Since (requestStart ))
327349 c .lggr .Errorw ("failed to read HTTP response body" , "err" , err )
328350 return nil , errors .Join (err , ErrHTTPRead )
329351 }
330352
353+ c .metrics .recordTotal (ctx , req .Method , resp .StatusCode , true , traceState .connReused .Load (), time .Since (requestStart ))
354+
331355 headers , multiHeaders := responseHeadersFromNetHeader (resp .Header )
332356 c .lggr .Debugw ("received HTTP response" , "statusCode" , resp .StatusCode )
333357 return & HTTPResponse {
0 commit comments