• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

UiPath / uipathcli / 13765865558

10 Mar 2025 01:27PM UTC coverage: 90.218% (+0.1%) from 90.121%
13765865558

push

github

thschmitt
Add network package with common http client

Moved common code for handling HTTP requests in a new shared
network package to avoid duplication and ensure that each plugin
handles network requests the same way.

The common HTTP client code takes care of a couple of cross-cutting
concerns needed by all plugins and the main HTTP executor:

- Logs request and response when debug flag is set. Added a new
  resettableReader which preserves the request and response bodies while
  they are being read and forwards them to the debug logger.

- Added retries for all HTTP requests. This also leverages the
  resettableReader to ensure the request body can be replayed.

- The `CommandBuilder` generates now an operation id which is set on every
  request the uipathcli performs. The same operation id is kept for the
  duration of the whole command execution which makes it easier to
  correlate multiple requests performed by a single command.

- The `HttpClient` also sets transport-related settings like certificate
  validation and response header timeout.

- Using the built-in context instead of a custom requestError channel.

369 of 384 new or added lines in 22 files covered. (96.09%)

7 existing lines in 4 files now uncovered.

5340 of 5919 relevant lines covered (90.22%)

1.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.72
/utils/network/http_client.go
1
package network
2

3
import (
4
        "bytes"
5
        "context"
6
        "crypto/tls"
7
        "fmt"
8
        "io"
9
        "net/http"
10

11
        "github.com/UiPath/uipathcli/log"
12
        "github.com/UiPath/uipathcli/utils/resiliency"
13
)
14

15
type HttpClient struct {
16
        logger   log.Logger
17
        settings HttpClientSettings
18
}
19

20
func (c HttpClient) Send(request *http.Request) (*http.Response, error) {
1✔
21
        return c.sendWithRetries(request, context.Background())
1✔
22
}
1✔
23

24
func (c HttpClient) SendWithContext(request *http.Request, ctx context.Context) (*http.Response, error) {
1✔
25
        return c.sendWithRetries(request, ctx)
1✔
26
}
1✔
27

28
func (c HttpClient) sendWithRetries(request *http.Request, ctx context.Context) (*http.Response, error) {
1✔
29
        var response *http.Response
1✔
30
        var err error
1✔
31

1✔
32
        request.Header.Set("x-request-id", c.settings.OperationId)
1✔
33
        request.Body = newResettableReader(request.Body, func(body []byte) { c.logRequest(request, body) })
2✔
34

35
        err = resiliency.RetryN(c.settings.MaxAttempts, func() error {
2✔
36
                response, err = c.send(request, ctx)
1✔
37
                if err != nil {
2✔
38
                        return resiliency.Retryable(err)
1✔
39
                }
1✔
40

41
                response.Body = newResettableReader(response.Body, func(body []byte) { c.logResponse(response, body) })
2✔
42

43
                if response.StatusCode == 0 || response.StatusCode >= 500 {
2✔
44
                        defer response.Body.Close()
1✔
45
                        body, err := io.ReadAll(response.Body)
1✔
46
                        if err != nil {
1✔
NEW
47
                                return resiliency.Retryable(fmt.Errorf("Error reading response: %w", err))
×
NEW
48
                        }
×
49
                        return resiliency.Retryable(fmt.Errorf("Service returned status code '%v' and body '%v'", response.StatusCode, string(body)))
1✔
50
                }
51
                return nil
1✔
52
        })
53
        return response, err
1✔
54
}
55

56
func (c HttpClient) send(request *http.Request, ctx context.Context) (*http.Response, error) {
1✔
57
        transport := &http.Transport{
1✔
58
                TLSClientConfig:       &tls.Config{InsecureSkipVerify: c.settings.Insecure}, //nolint // This is user configurable and disabled by default
1✔
59
                ResponseHeaderTimeout: c.settings.Timeout,
1✔
60
        }
1✔
61
        client := &http.Client{Transport: transport}
1✔
62

1✔
63
        responseChan := make(chan *http.Response)
1✔
64
        ctx, cancel := context.WithCancelCause(ctx)
1✔
65
        go func(client *http.Client, request *http.Request) {
2✔
66
                response, err := client.Do(request)
1✔
67
                if err != nil {
2✔
68
                        cancel(fmt.Errorf("Error sending request: %w", err))
1✔
69
                        return
1✔
70
                }
1✔
71
                responseChan <- response
1✔
72
        }(client, request)
73

74
        select {
1✔
75
        case <-ctx.Done():
1✔
76
                return nil, fmt.Errorf("Error sending request: %w", context.Cause(ctx))
1✔
77
        case response := <-responseChan:
1✔
78
                return response, nil
1✔
79
        }
80
}
81

82
func (c HttpClient) logRequest(request *http.Request, body []byte) {
1✔
83
        if c.settings.Debug {
2✔
84
                requestInfo := log.NewRequestInfo(request.Method, request.URL.String(), request.Proto, request.Header, bytes.NewReader(body))
1✔
85
                c.logger.LogRequest(*requestInfo)
1✔
86
        }
1✔
87
}
88

89
func (c HttpClient) logResponse(response *http.Response, body []byte) {
1✔
90
        if c.settings.Debug {
2✔
91
                responseInfo := log.NewResponseInfo(response.StatusCode, response.Status, response.Proto, response.Header, bytes.NewReader(body))
1✔
92
                c.logger.LogResponse(*responseInfo)
1✔
93
        }
1✔
94
}
95

96
func NewHttpClient(logger log.Logger, settings HttpClientSettings) *HttpClient {
1✔
97
        return &HttpClient{logger, settings}
1✔
98
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc