Bound Weather API response diagnostics
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -30,8 +31,11 @@ const (
|
||||
defaultWarmupDelay = time.Second
|
||||
defaultFetchAttempts = 2
|
||||
defaultFetchRetryDelay = time.Second
|
||||
maxResponseBodyBytes = 10 << 20
|
||||
)
|
||||
|
||||
var errResponseBodyTooLarge = errors.New("response exceeds 10 MiB limit")
|
||||
|
||||
type Client struct {
|
||||
baseURL *url.URL
|
||||
httpClient *http.Client
|
||||
@@ -497,16 +501,12 @@ func (c *Client) warmupOnce(ctx context.Context, endpoint string) error {
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, 10<<20))
|
||||
_, err = readResponseBody(resp.Body)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("read %s response: %w", endpoint, err)
|
||||
if ctx.Err() != nil {
|
||||
return err
|
||||
}
|
||||
return retryableRequestError{err: err}
|
||||
return responseReadError(ctx, endpoint, err)
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
err := fmt.Errorf("fetch %s: unexpected HTTP status %d: %s", endpoint, resp.StatusCode, strings.TrimSpace(string(body)))
|
||||
err := fmt.Errorf("fetch %s: unexpected HTTP status %d", endpoint, resp.StatusCode)
|
||||
if isRetryableHTTPStatus(resp.StatusCode) {
|
||||
return retryableRequestError{err: err}
|
||||
}
|
||||
@@ -559,16 +559,12 @@ func (c *Client) fetchHTTPOnce(ctx context.Context, endpoint string, opts queryO
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, 10<<20))
|
||||
body, err := readResponseBody(resp.Body)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("read %s response: %w", endpoint, err)
|
||||
if ctx.Err() != nil {
|
||||
return reqURL, nil, err
|
||||
}
|
||||
return reqURL, nil, retryableRequestError{err: err}
|
||||
return reqURL, nil, responseReadError(ctx, endpoint, err)
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
err := fmt.Errorf("fetch %s: unexpected HTTP status %d: %s", endpoint, resp.StatusCode, strings.TrimSpace(string(body)))
|
||||
err := fmt.Errorf("fetch %s: unexpected HTTP status %d", endpoint, resp.StatusCode)
|
||||
if isRetryableHTTPStatus(resp.StatusCode) {
|
||||
return reqURL, nil, retryableRequestError{err: err}
|
||||
}
|
||||
@@ -577,6 +573,25 @@ func (c *Client) fetchHTTPOnce(ctx context.Context, endpoint string, opts queryO
|
||||
return reqURL, body, nil
|
||||
}
|
||||
|
||||
func readResponseBody(body io.Reader) ([]byte, error) {
|
||||
data, err := io.ReadAll(io.LimitReader(body, maxResponseBodyBytes+1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if int64(len(data)) > maxResponseBodyBytes {
|
||||
return nil, errResponseBodyTooLarge
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func responseReadError(ctx context.Context, endpoint string, err error) error {
|
||||
err = fmt.Errorf("read %s response: %w", endpoint, err)
|
||||
if errors.Is(err, errResponseBodyTooLarge) || ctx.Err() != nil {
|
||||
return err
|
||||
}
|
||||
return retryableRequestError{err: err}
|
||||
}
|
||||
|
||||
type retryableRequestError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user