rm go-retryablehttp

This commit is contained in:
perennial
2024-10-21 14:30:21 +11:00
parent 785e38c88e
commit 7095acd437
7 changed files with 92 additions and 254 deletions
-11
View File
@@ -52,11 +52,6 @@ type ServerConfig struct {
TokenBaseTimeout time.Duration `env:"PIXIVFE_TOKEN_BASE_TIMEOUT,overwrite"`
TokenMaxBackoffTime time.Duration `env:"PIXIVFE_TOKEN_MAX_BACKOFF_TIME,overwrite"`
// API request level backoff settings
APIMaxRetries int `env:"PIXIVFE_API_MAX_RETRIES,overwrite"`
APIBaseTimeout time.Duration `env:"PIXIVFE_API_BASE_TIMEOUT,overwrite"`
APIMaxBackoffTime time.Duration `env:"PIXIVFE_API_MAX_BACKOFF_TIME,overwrite"`
AcceptLanguage string `env:"PIXIVFE_ACCEPTLANGUAGE,overwrite"`
RequestLimit uint64 `env:"PIXIVFE_REQUESTLIMIT"` // if 0, request limit is disabled
@@ -135,10 +130,6 @@ func (s *ServerConfig) LoadConfig() error {
s.TokenBaseTimeout = 1000 * time.Millisecond
s.TokenMaxBackoffTime = 32000 * time.Millisecond
s.APIMaxRetries = 3
s.APIBaseTimeout = 500 * time.Millisecond
s.APIMaxBackoffTime = 8000 * time.Millisecond
s.CacheEnabled = false
s.CacheSize = 100
s.CacheTTL = 60 * time.Minute
@@ -216,8 +207,6 @@ func (s *ServerConfig) LoadConfig() error {
log.Printf("Token manager settings: Max retries: %d, Base timeout: %v, Max backoff time: %v\n", s.TokenMaxRetries, s.TokenBaseTimeout, s.TokenMaxBackoffTime)
log.Printf("Token load balancing method: %s\n", s.TokenLoadBalancing)
log.Printf("API request backoff settings: Max retries: %d, Base timeout: %v, Max backoff time: %v\n", s.APIMaxRetries, s.APIBaseTimeout, s.APIMaxBackoffTime)
// Print cache configuration
if s.CacheEnabled {
log.Println("API response cache is enabled")
+86 -131
View File
@@ -1,5 +1,5 @@
// This file implements the core functionality for handling API requests,
// including response caching, retry mechanisms, and request proxying.
// including response caching and and request proxying.
package core
import (
@@ -24,7 +24,6 @@ import (
"codeberg.org/vnpower/pixivfe/v2/server/token_manager"
"codeberg.org/vnpower/pixivfe/v2/server/utils"
"github.com/hashicorp/go-retryablehttp"
lru "github.com/hashicorp/golang-lru"
"github.com/tidwall/gjson"
"github.com/zeebo/xxh3"
@@ -56,9 +55,8 @@ type CachingResult struct {
}
var (
retryClient *retryablehttp.Client
cacheSeed uint64
cache *lru.Cache
cacheSeed uint64
cache *lru.Cache
// shortTTLPaths lists API endpoints that require a shorter TTL for their cached responses.
shortTTLPaths = []string{
@@ -108,16 +106,6 @@ func InitCache() {
cacheSeed = binary.LittleEndian.Uint64(seedBytes[:])
}
// init sets up a configured retryablehttp client.
func init() {
retryClient = retryablehttp.NewClient()
retryClient.RetryMax = config.GlobalConfig.APIMaxRetries
retryClient.RetryWaitMin = config.GlobalConfig.APIBaseTimeout
retryClient.RetryWaitMax = config.GlobalConfig.APIMaxBackoffTime
retryClient.HTTPClient = utils.HttpClient
retryClient.Logger = nil // Disables the default logger in go-retryablehttp
}
// generateCacheKey creates a unique identifier for caching purposes by hashing
// the combined request URL and the user's token using seeded xxhash.
//
@@ -131,6 +119,7 @@ func generateCacheKey(url, userToken string) string {
}
// determineCachePolicy determines the caching policy for a given request.
//
// It returns a CachePolicy struct indicating whether to fetch from cache,
// whether to store the response in cache, and the cached response if available.
func determineCachePolicy(rawURL, userToken string, headers http.Header) CachePolicy {
@@ -152,8 +141,7 @@ func determineCachePolicy(rawURL, userToken string, headers http.Header) CachePo
}
}
// Retrieve the Cache-Control header from the downstream request
// and check for "no-cache"
// Retrieve the Cache-Control header from the downstream request and check for "no-cache"
cacheControl := headers.Get("Cache-Control")
lowerCacheControl := strings.ToLower(cacheControl)
if strings.Contains(lowerCacheControl, "no-cache") {
@@ -191,8 +179,9 @@ func determineCachePolicy(rawURL, userToken string, headers http.Header) CachePo
}
}
// manageCaching manages the caching logic during and after a request.
// It either returns a cached response or allows the request to proceed and optionally caches the new response.
// manageCaching manages the caching logic after receiving a response.
//
// It either returns a cached response or allows the response to be stored in cache.
func manageCaching(rawURL, userToken string, headers http.Header, response *SimpleHTTPResponse) CachingResult {
policy := determineCachePolicy(rawURL, userToken, headers)
if policy.ShouldUseCached && policy.CachedResponse != nil {
@@ -232,14 +221,80 @@ func manageCaching(rawURL, userToken string, headers http.Header, response *Simp
}
}
// makeRequest is a helper function that encapsulates the common logic for making HTTP requests.
// handleRequest handles HTTP requests.
func handleRequest(
ctx context.Context,
auditor *audit.Auditor,
reqFunc func(context.Context, string) (*http.Request, error),
userToken string,
isPost bool,
url string,
incomingHeaders http.Header,
) (*SimpleHTTPResponse, error) {
// Retrieve the token
tokenManager := config.GlobalConfig.TokenManager
token, err := retrieveToken(tokenManager, userToken)
if err != nil {
return nil, err
}
// For GET requests, determine if caching should be used
var cachingResult CachingResult
if !isPost {
policy := determineCachePolicy(url, userToken, incomingHeaders)
if policy.ShouldUseCached && policy.CachedResponse != nil {
return policy.CachedResponse, nil
}
}
// Make the HTTP request using the provided request function
resp, err := makeRequest(ctx, auditor, reqFunc, token, url)
if err != nil {
// NOTE: don't mark the token provided by tokenManager as timed out if the making the request itself failed
// tokenManager.MarkTokenStatus(token, token_manager.TimedOut)
return nil, err
}
// Handle the response based on the status code
if resp.StatusCode == http.StatusOK {
// Mark the token as good if the response is OK
tokenManager.MarkTokenStatus(token, token_manager.Good)
if !isPost {
// Handle caching logic after receiving an HTTP 200 response
cachingResult = manageCaching(url, userToken, incomingHeaders, resp)
if cachingResult.FromCache {
return cachingResult.Response, nil
}
}
return resp, nil
}
// Handle non-OK status codes
err = i18n.Errorf("HTTP status code: %d", resp.StatusCode)
// Mark the token provided by tokenManager as timed out if the
// request succeeded, but returned a non-OK response
tokenManager.MarkTokenStatus(token, token_manager.TimedOut)
// Check if the context has been canceled or timed out
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
return nil, err
}
}
// makeRequest is a helper function that encapsulates the common logic for making HTTP requests using utils.HttpClient.Do.
//
// It constructs the request using the provided request function, executes it using the retryable client,
// reads the response, logs the API round trip details for auditing, and returns a simplified response.
// It constructs the request using the provided request function, executes it using HttpClient,
// reads the response, logs the API round trip details, and returns a SimpleHTTPResponse.
func makeRequest(
ctx context.Context,
auditor *audit.Auditor,
reqFunc func(context.Context, string) (*retryablehttp.Request, error),
reqFunc func(context.Context, string) (*http.Request, error),
token *token_manager.Token,
url string,
) (*SimpleHTTPResponse, error) {
@@ -250,7 +305,7 @@ func makeRequest(
}
start := time.Now()
resp, err := retryClient.Do(req)
resp, err := utils.HttpClient.Do(req)
end := time.Now()
if err != nil {
@@ -282,18 +337,6 @@ func makeRequest(
}, nil
}
// executePost handles POST requests without retries.
func executePost(
ctx context.Context,
auditor *audit.Auditor,
reqFunc func(context.Context, string) (*retryablehttp.Request, error),
userToken string,
url string,
) (*SimpleHTTPResponse, error) {
token := &token_manager.Token{Value: userToken}
return makeRequest(ctx, auditor, reqFunc, token, url)
}
// retrieveToken obtains a valid token, preferring the userToken if provided by the caller.
func retrieveToken(tokenManager *token_manager.TokenManager, userToken string) (*token_manager.Token, error) {
if userToken != "" {
@@ -306,7 +349,7 @@ func retrieveToken(tokenManager *token_manager.TokenManager, userToken string) (
return nil,
i18n.Errorf(
`All tokens (%d) are timed out, resetting all tokens to their initial good state.
Consider providing additional tokens in PIXIVFE_TOKEN or reviewing API request level backoff configuration.
Consider providing additional tokens in PIXIVFE_TOKEN or reviewing token management configuration.
Please refer the following documentation for additional information:
- https://pixivfe-docs.pages.dev/hosting/obtaining-pixivfe-token/
- https://pixivfe-docs.pages.dev/hosting/environment-variables/#exponential-backoff-configuration`,
@@ -316,82 +359,6 @@ Please refer the following documentation for additional information:
return token, nil
}
// executeGet performs GET requests with retry logic and caching.
func executeGet(
ctx context.Context,
auditor *audit.Auditor,
reqFunc func(context.Context, string) (*retryablehttp.Request, error),
userToken, url string,
incomingHeaders http.Header,
) (*SimpleHTTPResponse, error) {
tokenManager := config.GlobalConfig.TokenManager
// Retrieve the token
token, err := retrieveToken(tokenManager, userToken)
if err != nil {
return nil, err
}
// Make the HTTP request
resp, err := makeRequest(ctx, auditor, reqFunc, token, url)
if err != nil {
// Mark the token as timed out if the request failed
tokenManager.MarkTokenStatus(token, token_manager.TimedOut)
return nil, err
}
// Handle the response based on the status code
if resp.StatusCode == http.StatusOK {
// Mark the token as good if the response is OK
tokenManager.MarkTokenStatus(token, token_manager.Good)
// Handle caching logic after receiving an HTTP 200 response
cachingResult := manageCaching(url, userToken, incomingHeaders, resp)
if cachingResult.FromCache {
return cachingResult.Response, nil
}
return cachingResult.Response, nil
}
// Handle non-OK status codes
err = i18n.Errorf("HTTP status code: %d", resp.StatusCode)
// Mark the token as timed out for non-OK responses
tokenManager.MarkTokenStatus(token, token_manager.TimedOut)
// Check if the context has been canceled or timed out
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
// Return the error without retrying
return nil, err
}
}
// retryRequest handles the logic for executing HTTP requests with automatic retries and token management.
func retryRequest(
ctx context.Context,
auditor *audit.Auditor,
reqFunc func(context.Context, string) (*retryablehttp.Request, error),
userToken string,
isPost bool,
url string,
incomingHeaders http.Header,
) (*SimpleHTTPResponse, error) {
if isPost {
return executePost(ctx, auditor, reqFunc, userToken, url)
}
// Determine caching policy before we send a request
policy := determineCachePolicy(url, userToken, incomingHeaders)
if policy.ShouldUseCached && policy.CachedResponse != nil {
return policy.CachedResponse, nil
}
return executeGet(ctx, auditor, reqFunc, userToken, url, incomingHeaders)
}
// API_GET performs a GET request to the Pixiv API with automatic retries and caching.
func API_GET(
ctx context.Context,
@@ -400,13 +367,12 @@ func API_GET(
userToken string,
incomingHeaders http.Header,
) (*SimpleHTTPResponse, error) {
return retryRequest(ctx, auditor, func(ctx context.Context, token string) (*retryablehttp.Request, error) {
return handleRequest(ctx, auditor, func(ctx context.Context, token string) (*http.Request, error) {
// Create a new GET request for the specified URL.
req, err := retryablehttp.NewRequest("GET", url, nil)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
req.Header.Add("User-Agent", config.GetRandomUserAgent())
req.Header.Add("Accept-Language", config.GlobalConfig.AcceptLanguage)
@@ -488,22 +454,22 @@ func API_POST(
return nil, i18n.Error("userToken is required for POST requests")
}
resp, err := retryRequest(ctx, auditor, func(ctx context.Context, token string) (*retryablehttp.Request, error) {
var req *retryablehttp.Request
return handleRequest(ctx, auditor, func(ctx context.Context, token string) (*http.Request, error) {
var req *http.Request
var err error
// Determine the type of payload and construct the request accordingly.
switch v := payload.(type) {
case string:
// If the payload is a string, send it as a raw byte buffer.
req, err = retryablehttp.NewRequest("POST", url, bytes.NewBuffer([]byte(v)))
req, err = http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer([]byte(v)))
case map[string]string:
// If the payload is a map, construct multipart form data.
body, formContentType, err := createMultipartFormData(v)
if err != nil {
return nil, err
}
req, err = retryablehttp.NewRequest("POST", url, body)
req, err = http.NewRequestWithContext(ctx, "POST", url, body)
if err == nil {
// Update the content type to reflect multipart form data.
contentType = formContentType
@@ -517,8 +483,6 @@ func API_POST(
return nil, err
}
req = req.WithContext(ctx)
req.Header.Add("User-Agent", config.GetRandomUserAgent())
req.Header.Add("Accept", "application/json")
req.Header.Add("x-csrf-token", csrf)
@@ -531,15 +495,6 @@ func API_POST(
return req, nil
}, userToken, true, url, incomingHeaders)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API request failed with status code: %d, body: %s", resp.StatusCode, resp.Body)
}
return resp, nil
}
// ProxyRequest forwards an incoming HTTP request to a target server and writes the response back to the original client.
-61
View File
@@ -1,61 +0,0 @@
# Exponential backoff
PixivFE implements exponential backoff as a strategy for handling API request failures and managing token usage. This document outlines the implementation details of this feature across two key components of the application.
## Overview
Exponential backoff is a technique used to gradually increase the wait time between retries of a failed operation. In PixivFE, this technique is applied at two levels:
1. API request level
2. Token management level
### Configuration
The `ServerConfig` struct in [`config/config.go`](https://codeberg.org/VnPower/PixivFE/src/branch/v2/config/config.go) includes fields for both API request level and token management level backoff settings.
The `LoadConfig` method sets default values for these settings if they are not provided through environment variables.
## API request level backoff
**Location: `core/requests.go`**
PixivFE uses the `retryablehttp` package to implement exponential backoff for API requests. The implementation is as follows:
1. A `retryablehttp.Client` is initialized in the `init` function:
```go
func init() {
retryClient = retryablehttp.NewClient()
retryClient.RetryMax = config.GlobalConfig.APIMaxRetries
retryClient.RetryWaitMin = config.GlobalConfig.APIBaseTimeout
retryClient.RetryWaitMax = config.GlobalConfig.APIMaxBackoffTime
retryClient.HTTPClient = utils.HttpClient
}
```
2. The `retryRequest` function uses this client to perform requests with automatic retries:
```go
func retryRequest(ctx context.Context, reqFunc func(context.Context, string) (*retryablehttp.Request, error)) (SimpleHTTPResponse, error) {
// ... (function implementation)
}
```
## Token management level backoff
**Location: `server/token_manager/token_manager.go`**
The `TokenManager` implements exponential backoff for individual tokens:
- In the `MarkTokenStatus` method, when a token is marked as `TimedOut`, it calculates a timeout duration:
```go
timeoutDuration := time.Duration(math.Min(
float64(tm.baseTimeout)*math.Pow(2, float64(token.FailureCount-1)),
float64(tm.maxBackoffTime),
))
```
- This calculation uses the `math.Pow` function to implement exponential growth based on the number of consecutive failures.
- The timeout duration is also capped at a maximum value (`tm.maxBackoffTime`).
- The token's `TimeoutUntil` is set to the current time plus this calculated duration.
This approach allows tokens that repeatedly fail increasingly longer "cool-down" periods before being used again, helping to manage rate limiting of individual tokens by the Pixiv API.
+6 -36
View File
@@ -137,43 +137,13 @@ You can disable periodic checks by setting the value to `0`. Then, proxies will
## Exponential backoff configuration
PixivFE implements exponential backoff for API requests and token management to handle failures gracefully and manage rate limiting. The following environment variables can be used to configure this behavior, fine-tuning the exponential backoff behavior for both API requests and token management. If not set, the default values will be used.
PixivFE implements exponential backoff for token management to manage rate limiting.
For more detailed information about the implementation of exponential backoff in PixivFE, please refer to the [Exponential Backoff documentation](../dev/features/exponential_backoff.md).
The following environment variables control how PixivFE manages token timeouts when a token encounters repeated failures. The backoff time for a token starts at the base timeout and doubles with each failure, up to the maximum backoff time.
### API request level backoff
If not set, the default values will be used.
These settings control how PixivFE handles retries for individual API requests. The backoff time starts at the base timeout and doubles with each retry, up to the maximum backoff time.
#### `PIXIVFE_API_MAX_RETRIES`
**Required**: No
**Default:** `3`
Maximum number of retries for API requests.
#### `PIXIVFE_API_BASE_TIMEOUT`
**Required**: No
**Default:** `500ms`
Base timeout duration for API requests.
#### `PIXIVFE_API_MAX_BACKOFF_TIME`
**Required**: No
**Default:** `8000ms`
Maximum backoff time for API requests.
### Token management level backoff
These settings control how PixivFE manages token timeouts when a token encounters repeated failures. The backoff time for a token starts at the base timeout and doubles with each failure, up to the maximum backoff time.
#### `PIXIVFE_TOKEN_MAX_RETRIES`
### `PIXIVFE_TOKEN_MAX_RETRIES`
**Required**: No
@@ -181,7 +151,7 @@ These settings control how PixivFE manages token timeouts when a token encounter
Maximum number of retries for token management.
#### `PIXIVFE_TOKEN_BASE_TIMEOUT`
### `PIXIVFE_TOKEN_BASE_TIMEOUT`
**Required**: No
@@ -189,7 +159,7 @@ Maximum number of retries for token management.
Base timeout duration for token management.
#### `PIXIVFE_TOKEN_MAX_BACKOFF_TIME`
### `PIXIVFE_TOKEN_MAX_BACKOFF_TIME`
**Required**: No
-2
View File
@@ -14,7 +14,6 @@ require (
github.com/go-faker/faker/v4 v4.5.0
github.com/goccy/go-json v0.10.3
github.com/gorilla/mux v1.8.1
github.com/hashicorp/go-retryablehttp v0.7.7
github.com/hashicorp/golang-lru v1.0.2
github.com/oklog/ulid/v2 v2.1.0
github.com/sethvargo/go-envconfig v1.1.0
@@ -30,7 +29,6 @@ require (
require (
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
-12
View File
@@ -8,8 +8,6 @@ github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsVi
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/go-faker/faker/v4 v4.5.0 h1:ARzAY2XoOL9tOUK+KSecUQzyXQsUaZHefjyF8x6YFHc=
github.com/go-faker/faker/v4 v4.5.0/go.mod h1:p3oq1GRjG2PZ7yqeFFfQI20Xm61DoBDlCA8RiSyZ48M=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
@@ -18,20 +16,10 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
-1
View File
@@ -22,7 +22,6 @@
"core/requests.go:9UmoeR3swA0": "failed to make HTTP request: %w",
"core/requests.go:XGjPq4l3s2o": "userToken is required for POST requests",
"core/requests.go:XdMN7Q7DY3k": "failed to proxy request: %w",
"core/requests.go:_IJGPWU-8jE": "max retries reached for GET request. Last error: %v",
"core/requests.go:f78uqyomFJk": "Unsupported payload type",
"core/requests.go:fEaOk30bc9I": "failed to read response body: %w",
"core/requests.go:lLy9SHFUtQQ": "failed to copy response body: %w",