"In networks, chaos is the default. Let aoni be your ice-cold anchor."
When integrating with unstable APIs, scraping, or working with complex proxy networks in Go, the standard net/http client often requires significant boilerplate to handle real-world challenges like proxy rotation, rate limits, legacy charsets, or TLS fingerprinting.
aoni bridges this gap. It models HTTP requests as pipeline flows processed by declarative RequestModifiers and standard Go Middlewares, leveraging generics for type-safe response decoding. It remains unwavering under network load, just like the blue oni.
go get github.com/lemon4ksan/aoniaoni is not designed to replace net/http or lightweight wrappers like resty for standard, internal corporate microservices where raw, flat throughput over direct, reliable cloud connections is the only concern.
- Choose
net/http/restyfor: Internal microservices, direct cloud API integrations (AWS/S3, Stripe, Twilio), and standard high-throughput REST APIs where you fully control the destination server and the network environment. - Choose
aonifor: Deep-packet inspection (DPI) evasion, scraping/crawling targets behind aggressive firewalls (Cloudflare, Akamai, Imperva), rotating unstable proxy networks with sticky sessions, and real-time WebSockets over HTTP/2. It is your tactical off-road armor for uncooperative and chaotic network environments.
In aoni, a request is not a static object—it is a fluid stream processed in four distinct, highly optimized phases:
flowchart LR
%% Styling
classDef ice fill:#f0f8ff,stroke:#00a3e0,stroke-width:1.5px,color:#003366;
linkStyle default stroke:#00a3e0,stroke-width:2px;
p1["<b>[ RequestModifiers ]</b><br><i>Decorate req</i><br>━━━━━━━━━━━━━━━━━━<br>• Headers<br>• URL Variables<br>• Request Body"]:::ice
p2["<b>[ HTTP Middlewares ]</b><br><i>Intercept & Wrap</i><br>━━━━━━━━━━━━━━━━━━<br>• Rate Limiter<br>• CircuitBreaker<br>• RetryEngine"]:::ice
p3["<b>[ Transport Layer ]</b><br><i>Execute</i><br>━━━━━━━━━━━━━━━━━━<br>• HappyEyeballs<br>• ProxyRotator<br>• LoadBalancer"]:::ice
p4["<b>[ Generic Decoders ]</b><br><i>Extract output</i><br>━━━━━━━━━━━━━━━━━━<br>• Auto-UTF8<br>• JSON/XML/YAML<br>• Error Models"]:::ice
p1 --> p2 --> p3 --> p4
To make a JSON request through a resilient proxy pool with retries and custom error parsing, standard Go requires manual loop management, type casting, and verbose transport setup.
Here is how the two approaches compare:
Standard net/http (Manual Setup) |
Using aoni (Declarative & Resilient) |
|---|---|
// 🛑 Verbose, unsafe state handling
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
}
client := &http.Client{Transport: transport}
var lastErr error
for i := 0; i < 3; i++ {
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, err := client.Do(req)
if err != nil {
lastErr = err
time.Sleep(backoff)
continue
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
// Must manually decode error schema...
}
// Must manually decode JSON...
err = json.NewDecoder(resp.Body).Decode(&user)
break
} |
// ❄️ Clean, immutable, pipeline-driven flow
client := aoni.NewClient(transportChain)
user, err := aoni.GetJSON[User](ctx, client, "/users/{id}",
aoni.WithVar("id", 123),
aoni.WithErrorModel(&apiErr),
) |
This matrix shows where aoni focuses its design compared to Go's default capabilities and generic wrappers:
| Feature / Capability | Go net/http |
Standard Wrapper (e.g., Resty) | aoni |
|---|---|---|---|
| Generics-first Decoding | ✗ (Manual) | ✗ (Interface-based) | ✓ (Type-safe [T]) |
| Parallel "Happy Eyeballs" Dialing | ✗ | ✓ (RFC 8305) | |
| Active Circuit Breaking | ✗ | ✗ | ✓ (Native Middleware) |
Polite Retry-After Parsing |
✗ | ✗ | ✓ (Delta-sec & RFC1123) |
| Non-UTF8 Charset Translation | ✗ | ✗ | ✓ (Automatic) |
| TLS Evasion (JA3/JA4) | ✗ | ✗ | ✓ (via uTLS & Handshake) |
| JA4+ Fingerprinting | ✗ | ✗ | ✓ (TLS & HTTP, pure Go) |
| Sub-millisecond Tracing | ✗ | ✓ (Single-modifier) | |
| Structured Response Unwrapping | ✗ | ✗ | ✓ (WithBaseResponse) |
| Socket.IO / Engine.IO v4 Client | ✗ | ✗ | ✓ (Complete v5 Spec) |
Instead of dry features, here is how you solve common, frustrating networking challenges with aoni.
- The Problem: You need to rotate proxies to distribute load, but specific user requests must land on the exact same proxy address to preserve their active session state.
- The Ice-Cold Solution:
p1, _ := aoni.NewProxyClient(aoni.ProxyConfig{ProxyURL: "http://proxy1.local"})
p2, _ := aoni.NewProxyClient(aoni.ProxyConfig{ProxyURL: "http://proxy2.local"})
rotator, _ := aoni.NewProxyRotator(aoni.ProxyRotatorConfig{
MaxFails: 3,
RetryAfter: 30 * time.Second,
}, p1, p2)
// Lock proxy selection dynamically based on the request's session cookie
stickyRotator := rotator.WithStickySessions(func(req *http.Request) string {
if c, err := req.Cookie("sessionid"); err == nil {
return c.Value
}
return ""
})
client := aoni.NewClient(aoni.Chain(stickyRotator, rateLimiter))- The Problem: Unstable proxies or overloaded servers occasionally freeze, delaying your entire execution queue.
- The Ice-Cold Solution: If the primary request stalls and doesn't return headers in 150ms, a backup request is dispatched in parallel, returning whichever finishes first.
data, err := aoni.GetJSON[Data](ctx, aoni.NewClient(hedgedClient), "/data", WithHedging(10*time.Millisecond))- The Problem: Legacy regional APIs or crawled websites return text encoded in old charsets (e.g., Cyrillic or Asian legacy encodings), resulting in garbled characters during JSON unmarshaling.
- The Ice-Cold Solution:
aonidetects the encoding on-the-fly from the headers and transparently translates the stream to standard UTF-8 before passing it to any decoder.
manifest, err := aoni.GetJSON[Manifest](ctx, client, "/legacy-manifest",
aoni.WithDownloadProgress(func(current, total int64) {
fmt.Printf("Downloaded %d of %d bytes\n", current, total)
}),
)- The Problem: Modern Web Application Firewalls (WAFs like Cloudflare or Akamai) block automated requests based on TLS ClientHello fingerprints (JA3/JA4) and HTTP header ordering (JA4H).
- The Ice-Cold Solution:
aoninatively emulates modern browser TLS handshakes usinguTLSand automatically aligns headers to generate a clean, completely browser-compliant fingerprint. The built-inja4subpackage provides pure-Go JA4/JA4H computation.
info := &aoni.TraceInfo{}
client := aoni.NewClient(nil).
WithTLSFingerprint(aoni.BrowserChrome). // Spoofs TLS ClientHello
WithJA4Callback(func(r ja4.JA4Report) {
fmt.Println("Active TLS Handshake JA4:", r.JA4)
})
user, err := aoni.GetJSON[User](ctx, client, "/profile",
aoni.Trace(info),
aoni.TraceJA4(info), // Traces both TLS (JA4) and HTTP (JA4H) fingerprints
)
fmt.Println("Handshake TLS JA4:", info.JA4.JA4) // "t13d1516h2_8daaf6152771_e5627efa2ab1"
fmt.Println("Request HTTP JA4H:", info.JA4.JA4H) // "ge11nn03enus_9ed1ff1f7b03_cd8dafe26982"- The Problem: Real-time web sockets on protected servers get blocked during handshake due to standard Go TLS fingerprints, or silent TCP disconnects go unnoticed.
- The Ice-Cold Solution:
aoniestablishes fully authenticated, JA4-spoofed, proxy-routed Socket.IO v5 sessions over standard WebSockets or stealthy HTTP/2 Extended CONNECT tunnels. It includes automatic, jittered backoff reconnection and ping-timeout heartbeats natively.
cfg := aoni.SocketIOConfig{
Reconnection: true,
Namespace: "/realtime-prices",
Auth: map[string]string{"token": "my-secure-token"},
}
// Automatically inherits proxy rotators, DoT, JA4, and SSRF guards from the client!
sio, err := aoni.DialSocketIO(ctx, client, "wss://api.pricedb.io", cfg)
if err != nil {
log.Fatal(err)
}
sio.On("price_update", func(args []json.RawMessage) {
var price Price
_ = json.Unmarshal(args[0], &price)
fmt.Printf("Live Price: %s -> %.2f\n", price.SKU, price.Value)
})- The Problem: Tracking network bottlenecks across proxies is difficult, and recreating failing requests in terminal for manual verification takes time.
- The Ice-Cold Solution:
var trace aoni.TraceInfo
aoni.GetJSON[User](ctx, client, "/debug",
aoni.Trace(&trace), // Detailed DNS, TCP, and TLS metrics
aoni.AsCurl(), // Prints equivalent executable curl command to stderr
)
fmt.Printf("DNS: %s | TCP Connect: %s | TLS Handshake: %s | TTFB: %s\n",
trace.DNSLookup, trace.TCPConn, trace.TLSHandshake, trace.ServerProcessing)- The Problem: Many APIs wrap successful data in an envelope object like
{"success":true,"data":{...}}or{"status":"ok","result":{...}}. Manually unwrapping these envelopes, checking success flags, and extracting errors is repetitive boilerplate. - The Ice-Cold Solution: Implement the
BaseResponseinterface (IsSuccess,Error,SetData,UnmarshalJSON) and configure the client withWithBaseResponse. The decoder automatically decodes into your wrapper, checks the success flag, extracts errors, and unwraps the inner payload — all in one pass.
// 1. Define your envelope wrapper
type apiResponse struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
target any
}
func (r *apiResponse) IsSuccess() bool { return r.Success }
func (r *apiResponse) Error() error { return errors.New(r.Message) }
func (r *apiResponse) SetData(data any) { r.target = data }
func (r *apiResponse) UnmarshalJSON(data []byte) error {
type Alias apiResponse
var aux Alias
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
r.Success, r.Message = aux.Success, aux.Message
if r.target != nil {
return json.Unmarshal(data, r.target)
}
return nil
}
// 2. Configure the client — every JSON request unwraps through this envelope
client := aoni.NewClient(nil).
WithBaseURL("https://api.example.com").
WithBaseResponse(func() aoni.BaseResponse { return &apiResponse{} })
// 3. Use it — the decoder handles envelope unwrapping automatically
user, err := aoni.GetJSON[User](ctx, client, "/users/1")
// If API returns {"success":false,"message":"not found"}, err is non-nil
// If API returns {"success":true,"data":{"name":"Alice"}}, user.Name == "Alice"While standard clients focus only on raw speed, aoni is engineered to protect your host application's resources when scaling to thousands of concurrent worker loops:
- Static Heap Footprint: Maintains an ultra-lean runtime profile, consuming roughly ~1.2 MB of live heap memory in idle states.
- Sync.Pool Recycled Buffers: Utilizes pooled memory slices for body streaming, JSON parsing, and multipart encoding to keep GC overhead and "GC pauses" to a minimum.
- Leak Defense (Finalizers): Leverages
runtime.SetFinalizeron critical network responses to automatically release unclosed connections and warn you about resource leaks before file descriptors are exhausted. - Response Bomb Protection: Enforces strict payload reading limits (e.g. 10MB) via
io.LimitReaderon incoming responses to prevent out-of-memory (OOM) crashes from malicious or unexpectedly massive responses.
This project is licensed under the BSD 3-Clause License. See LICENSE for full details.