Skip to content

Commit afbf580

Browse files
authored
Postman increase http client timeout (#4086)
The Postman source can encounter issues with reading response bodies due to context cancellation or HTTP client timeout while trying to read the body. This PR attempts to remedy this issue by: 1. Deferring the response bodies closing. 2. In the process of 1, I consolidated the read and close steps into the function that makes Postman API calls. 3. Assigning a timeout value to the client returned by RetryableHTTPClientTimeout. An odd bug was found in the process of exploring RetryableHTTPClientTimeout. The client returned by RetryableHTTPClientTimeout with return httpClient.StandardClient() did not keep the timeout attribute assigned to the receiver httpClient earlier in the function. The client returned by the call to RetryableHTTPClientTimeout would have a Timeout value of 0, which then begs the question of how a http client could ever time out with a Timeout value of 0 in the first place.
1 parent c3e668f commit afbf580

File tree

4 files changed

+54
-40
lines changed

4 files changed

+54
-40
lines changed

pkg/common/http.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4747
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
4848
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
4949
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
50-
-----END CERTIFICATE-----
50+
-----END CERTIFICATE-----
5151
`,
5252
// CN = ISRG Root X2
5353
// TODO: Expires September 17, 2040 at 9:00:00 AM Pacific Daylight Time
@@ -189,6 +189,7 @@ func RetryableHTTPClient(opts ...ClientOption) *http.Client {
189189
return httpClient.StandardClient()
190190
}
191191

192+
// RetryableHTTPClientTimeout returns a new http client with a specified timeout and RoundTripper transport
192193
func RetryableHTTPClientTimeout(timeOutSeconds int64, opts ...ClientOption) *http.Client {
193194
httpClient := retryablehttp.NewClient()
194195
httpClient.RetryMax = 3
@@ -199,7 +200,10 @@ func RetryableHTTPClientTimeout(timeOutSeconds int64, opts ...ClientOption) *htt
199200
for _, opt := range opts {
200201
opt(httpClient)
201202
}
202-
return httpClient.StandardClient()
203+
204+
standardClient := httpClient.StandardClient()
205+
standardClient.Timeout = httpClient.HTTPClient.Timeout
206+
return standardClient
203207
}
204208

205209
const DefaultResponseTimeout = 5 * time.Second

pkg/common/http_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,32 @@ func TestRetryableHTTPClientBackoff(t *testing.T) {
240240
})
241241
}
242242
}
243+
244+
func TestRetryableHTTPClientTimeout(t *testing.T) {
245+
testCases := []struct {
246+
name string
247+
timeoutSeconds int64
248+
expectedTimeout time.Duration
249+
}{
250+
{
251+
name: "5 second timeout",
252+
timeoutSeconds: 5,
253+
expectedTimeout: 5 * time.Second,
254+
},
255+
}
256+
257+
for _, tc := range testCases {
258+
t.Run(tc.name, func(t *testing.T) {
259+
260+
// Call the function with the test timeout value
261+
client := RetryableHTTPClientTimeout(tc.timeoutSeconds)
262+
263+
// Verify that the timeout is set correctly
264+
assert.Equal(t, tc.expectedTimeout, client.Timeout, "HTTP client timeout does not match expected value")
265+
266+
// Verify that the transport is a custom transport
267+
_, isRoundTripperTransport := client.Transport.(*retryablehttp.RoundTripper)
268+
assert.True(t, isRoundTripperTransport, "HTTP client transport is not a retryablehttp.RoundTripper")
269+
})
270+
}
271+
}

pkg/sources/postman/postman.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func (s *Source) Init(ctx context.Context, name string, jobId sources.JobID, sou
116116
return errors.New("Postman token is empty")
117117
}
118118
s.client = NewClient(conn.GetToken())
119-
s.client.HTTPClient = common.RetryableHTTPClientTimeout(3)
119+
s.client.HTTPClient = common.RetryableHTTPClientTimeout(10)
120120
log.RedactGlobally(conn.GetToken())
121121
case *sourcespb.Postman_Unauthenticated:
122122
s.client = nil
@@ -752,8 +752,8 @@ func unpackWorkspace(workspacePath string) (Workspace, error) {
752752
if err != nil {
753753
return workspace, err
754754
}
755+
defer rc.Close()
755756
contents, err := io.ReadAll(rc)
756-
rc.Close()
757757
if err != nil {
758758
return workspace, err
759759
}

pkg/sources/postman/postman_client.go

Lines changed: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,8 @@ func checkResponseStatus(r *http.Response) error {
246246
return fmt.Errorf("postman Request failed with status code: %d", r.StatusCode)
247247
}
248248

249-
func (c *Client) getPostmanReq(ctx context.Context, url string, headers map[string]string) (*http.Response, error) {
249+
// getPostmanResponseBodyBytes makes a request to the Postman API and returns the response body as bytes.
250+
func (c *Client) getPostmanResponseBodyBytes(ctx context.Context, url string, headers map[string]string) ([]byte, error) {
250251
req, err := c.NewRequest(url, headers)
251252
if err != nil {
252253
return nil, err
@@ -256,13 +257,19 @@ func (c *Client) getPostmanReq(ctx context.Context, url string, headers map[stri
256257
if err != nil {
257258
return nil, err
258259
}
260+
defer resp.Body.Close()
261+
262+
body, err := io.ReadAll(resp.Body)
263+
if err != nil {
264+
return nil, fmt.Errorf("could not read postman response body: %w", err)
265+
}
259266

260267
ctx.Logger().V(4).Info("postman api response headers are available", "response_header", resp.Header)
261268

262269
if err := checkResponseStatus(resp); err != nil {
263270
return nil, err
264271
}
265-
return resp, nil
272+
return body, nil
266273
}
267274

268275
// EnumerateWorkspaces returns the workspaces for a given user (both private, public, team and personal).
@@ -276,17 +283,10 @@ func (c *Client) EnumerateWorkspaces(ctx context.Context) ([]Workspace, error) {
276283
if err := c.WorkspaceAndCollectionRateLimiter.Wait(ctx); err != nil {
277284
return nil, fmt.Errorf("could not wait for rate limiter during workspaces enumeration getting: %w", err)
278285
}
279-
r, err := c.getPostmanReq(ctx, "https://api.getpostman.com/workspaces", nil)
286+
body, err := c.getPostmanResponseBodyBytes(ctx, "https://api.getpostman.com/workspaces", nil)
280287
if err != nil {
281-
return nil, fmt.Errorf("could not get workspaces during enumeration: %w", err)
288+
return nil, fmt.Errorf("could not get postman workspace response bytes during enumeration: %w", err)
282289
}
283-
284-
body, err := io.ReadAll(r.Body)
285-
if err != nil {
286-
return nil, fmt.Errorf("could not read response body for workspaces during enumeration: %w", err)
287-
}
288-
r.Body.Close()
289-
290290
if err := json.Unmarshal([]byte(body), &workspacesObj); err != nil {
291291
return nil, fmt.Errorf("could not unmarshal workspaces JSON during enumeration: %w", err)
292292
}
@@ -318,17 +318,10 @@ func (c *Client) GetWorkspace(ctx context.Context, workspaceUUID string) (Worksp
318318
if err := c.WorkspaceAndCollectionRateLimiter.Wait(ctx); err != nil {
319319
return Workspace{}, fmt.Errorf("could not wait for rate limiter during workspace getting: %w", err)
320320
}
321-
r, err := c.getPostmanReq(ctx, url, nil)
321+
body, err := c.getPostmanResponseBodyBytes(ctx, url, nil)
322322
if err != nil {
323-
return Workspace{}, fmt.Errorf("could not get workspace (%s): %w", workspaceUUID, err)
323+
return Workspace{}, fmt.Errorf("could not get postman workspace (%s) response bytes: %w", workspaceUUID, err)
324324
}
325-
326-
body, err := io.ReadAll(r.Body)
327-
if err != nil {
328-
return Workspace{}, fmt.Errorf("could not read response body for workspace (%s): %w", workspaceUUID, err)
329-
}
330-
r.Body.Close()
331-
332325
if err := json.Unmarshal([]byte(body), &obj); err != nil {
333326
return Workspace{}, fmt.Errorf("could not unmarshal workspace JSON for workspace (%s): %w", workspaceUUID, err)
334327
}
@@ -346,16 +339,10 @@ func (c *Client) GetEnvironmentVariables(ctx context.Context, environment_uuid s
346339
if err := c.GeneralRateLimiter.Wait(ctx); err != nil {
347340
return VariableData{}, fmt.Errorf("could not wait for rate limiter during environment variable getting: %w", err)
348341
}
349-
r, err := c.getPostmanReq(ctx, url, nil)
350-
if err != nil {
351-
return VariableData{}, fmt.Errorf("could not get env variables for environment (%s): %w", environment_uuid, err)
352-
}
353-
354-
body, err := io.ReadAll(r.Body)
342+
body, err := c.getPostmanResponseBodyBytes(ctx, url, nil)
355343
if err != nil {
356-
return VariableData{}, fmt.Errorf("could not read env var response body for environment (%s): %w", environment_uuid, err)
344+
return VariableData{}, fmt.Errorf("could not get postman environment (%s) response bytes: %w", environment_uuid, err)
357345
}
358-
r.Body.Close()
359346
if err := json.Unmarshal([]byte(body), &obj); err != nil {
360347
return VariableData{}, fmt.Errorf("could not unmarshal env variables JSON for environment (%s): %w", environment_uuid, err)
361348
}
@@ -373,16 +360,10 @@ func (c *Client) GetCollection(ctx context.Context, collection_uuid string) (Col
373360
if err := c.WorkspaceAndCollectionRateLimiter.Wait(ctx); err != nil {
374361
return Collection{}, fmt.Errorf("could not wait for rate limiter during collection getting: %w", err)
375362
}
376-
r, err := c.getPostmanReq(ctx, url, nil)
377-
if err != nil {
378-
return Collection{}, fmt.Errorf("could not get collection (%s): %w", collection_uuid, err)
379-
}
380-
381-
body, err := io.ReadAll(r.Body)
363+
body, err := c.getPostmanResponseBodyBytes(ctx, url, nil)
382364
if err != nil {
383-
return Collection{}, fmt.Errorf("could not read response body for collection (%s): %w", collection_uuid, err)
365+
return Collection{}, fmt.Errorf("could not get postman collection (%s) response bytes: %w", collection_uuid, err)
384366
}
385-
r.Body.Close()
386367
if err := json.Unmarshal([]byte(body), &obj); err != nil {
387368
return Collection{}, fmt.Errorf("could not unmarshal JSON for collection (%s): %w", collection_uuid, err)
388369
}

0 commit comments

Comments
 (0)