Init with working version
Basic demo for Tally X AutoIxpert integration to showcase setup in Go due to impossibility of open-sourcing n8n setup.
This commit is contained in:
91
helper.go
Normal file
91
helper.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const maxRetries = 3
|
||||
|
||||
var client = &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
func Do(ctx context.Context, req *http.Request) (*http.Response, error) {
|
||||
return client.Do(req)
|
||||
}
|
||||
|
||||
func DoWithRateLimitReset(ctx context.Context, req *http.Request) (*http.Response, error) {
|
||||
var getBody func() (io.ReadCloser, error)
|
||||
if req.Body != nil {
|
||||
b, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = req.Body.Close()
|
||||
|
||||
getBody = func() (io.ReadCloser, error) {
|
||||
return io.NopCloser(bytes.NewReader(b)), nil
|
||||
}
|
||||
req.GetBody = getBody
|
||||
req.Body, _ = getBody()
|
||||
}
|
||||
|
||||
const safety = 250 * time.Millisecond // small buffer to avoid edge timing
|
||||
|
||||
for attempt := 0; attempt <= maxRetries; attempt++ {
|
||||
r := req.Clone(ctx)
|
||||
if getBody != nil {
|
||||
r.Body, _ = getBody()
|
||||
}
|
||||
|
||||
resp, err := client.Do(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusTooManyRequests {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
resetHeader := resp.Header.Get("X-RateLimit-Reset")
|
||||
_ = resp.Body.Close()
|
||||
|
||||
if attempt == maxRetries {
|
||||
return nil, fmt.Errorf("rate limited (429) after %d retries; last reset header=%q", maxRetries, resetHeader)
|
||||
}
|
||||
|
||||
wait, err := waitUntilUnixReset(resetHeader, safety)
|
||||
if err != nil {
|
||||
// If the header is missing/invalid, fall back to a short backoff
|
||||
wait = 2 * time.Second
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(wait):
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unreachable")
|
||||
}
|
||||
|
||||
func waitUntilUnixReset(h string, safety time.Duration) (time.Duration, error) {
|
||||
secs, err := strconv.ParseInt(h, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
resetAt := time.Unix(secs, 0)
|
||||
wait := time.Until(resetAt) + safety
|
||||
if wait < 0 {
|
||||
wait = 0
|
||||
}
|
||||
return wait, nil
|
||||
}
|
||||
Reference in New Issue
Block a user