Basic demo for Tally X AutoIxpert integration to showcase setup in Go due to impossibility of open-sourcing n8n setup.
92 lines
1.8 KiB
Go
92 lines
1.8 KiB
Go
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
|
|
}
|