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

UiPath / uipathcli / 13786392533

11 Mar 2025 10:59AM UTC coverage: 90.225% (+0.1%) from 90.121%
13786392533

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.

399 of 419 new or added lines in 22 files covered. (95.23%)

5 existing lines in 3 files now uncovered.

5372 of 5954 relevant lines covered (90.23%)

1.01 hits per line

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

91.36
/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
const bufferLimit = 10 * 1024 * 1024
21
const loggingLimit = 1 * 1024 * 1024
22

23
func (c HttpClient) Send(request *http.Request) (*http.Response, error) {
1✔
24
        return c.sendWithRetries(request, context.Background())
1✔
25
}
1✔
26

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

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

1✔
35
        request.Header.Set("x-request-id", c.settings.OperationId)
1✔
36

1✔
37
        if c.settings.Debug {
2✔
38
                request.Body = newResettableReader(request.Body, bufferLimit, func(body []byte) { c.logRequest(request, body) })
2✔
39
        } else if c.settings.MaxAttempts > 1 {
2✔
40
                request.Body = newResettableReader(request.Body, bufferLimit, func(body []byte) {})
2✔
41
        }
42

43
        err = resiliency.RetryN(c.settings.MaxAttempts, func() error {
2✔
44
                resetErr := c.resetReader(request.Body)
1✔
45
                if resetErr != nil {
1✔
NEW
46
                        return err
×
NEW
47
                }
×
48

49
                response, err = c.send(request, ctx)
1✔
50
                if err != nil {
2✔
51
                        return resiliency.Retryable(err)
1✔
52
                }
1✔
53

54
                if c.settings.Debug {
2✔
55
                        response.Body = newResettableReader(response.Body, bufferLimit, func(body []byte) { c.logResponse(response, body) })
2✔
56
                }
57

58
                if response.StatusCode == 0 || response.StatusCode >= 500 {
2✔
59
                        defer response.Body.Close()
1✔
60
                        body, err := io.ReadAll(response.Body)
1✔
61
                        if err != nil {
1✔
NEW
62
                                return resiliency.Retryable(fmt.Errorf("Error reading response: %w", err))
×
NEW
63
                        }
×
64
                        return resiliency.Retryable(fmt.Errorf("Service returned status code '%v' and body '%v'", response.StatusCode, string(body)))
1✔
65
                }
66
                return nil
1✔
67
        })
68
        return response, err
1✔
69
}
70

71
func (c HttpClient) resetReader(reader io.Reader) error {
1✔
72
        resettableReader, ok := reader.(*resettableReader)
1✔
73
        if ok {
2✔
74
                return resettableReader.Reset()
1✔
75
        }
1✔
NEW
76
        return fmt.Errorf("Reader is not resettable.")
×
77
}
78

79
func (c HttpClient) send(request *http.Request, ctx context.Context) (*http.Response, error) {
1✔
80
        transport := &http.Transport{
1✔
81
                TLSClientConfig:       &tls.Config{InsecureSkipVerify: c.settings.Insecure}, //nolint // This is user configurable and disabled by default
1✔
82
                ResponseHeaderTimeout: c.settings.Timeout,
1✔
83
        }
1✔
84
        client := &http.Client{Transport: transport}
1✔
85

1✔
86
        responseChan := make(chan *http.Response)
1✔
87
        ctx, cancel := context.WithCancelCause(ctx)
1✔
88
        go func(client *http.Client, request *http.Request) {
2✔
89
                response, err := client.Do(request)
1✔
90
                if err != nil {
2✔
91
                        cancel(fmt.Errorf("Error sending request: %w", err))
1✔
92
                        return
1✔
93
                }
1✔
94
                responseChan <- response
1✔
95
        }(client, request)
96

97
        select {
1✔
98
        case <-ctx.Done():
1✔
99
                return nil, fmt.Errorf("Error sending request: %w", context.Cause(ctx))
1✔
100
        case response := <-responseChan:
1✔
101
                return response, nil
1✔
102
        }
103
}
104

105
func (c HttpClient) logRequest(request *http.Request, body []byte) {
1✔
106
        reader := bytes.NewReader(c.truncate(body, loggingLimit))
1✔
107
        requestInfo := log.NewRequestInfo(request.Method, request.URL.String(), request.Proto, request.Header, reader)
1✔
108
        c.logger.LogRequest(*requestInfo)
1✔
109
}
1✔
110

111
func (c HttpClient) logResponse(response *http.Response, body []byte) {
1✔
112
        reader := bytes.NewReader(c.truncate(body, loggingLimit))
1✔
113
        responseInfo := log.NewResponseInfo(response.StatusCode, response.Status, response.Proto, response.Header, reader)
1✔
114
        c.logger.LogResponse(*responseInfo)
1✔
115
}
1✔
116

117
func (c HttpClient) truncate(data []byte, size int) []byte {
1✔
118
        if len(data) > size {
1✔
NEW
119
                return data[:size]
×
NEW
120
        }
×
121
        return data
1✔
122
}
123

124
func NewHttpClient(logger log.Logger, settings HttpClientSettings) *HttpClient {
1✔
125
        return &HttpClient{logger, settings}
1✔
126
}
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