Client Configuration¶
The apnic-skills client is highly configurable via functional options. This page documents all available options, their defaults, and common configuration patterns for anti-scraping, chunked downloads, caching, and service endpoints.
Default Client¶
Calling NewClient() with no options provides sensible defaults for interactive use:
| setting | default |
|---|---|
| HTTP client timeout | 15 seconds |
| Whois server | whois.apnic.net:43 |
| Whois timeout | 10 seconds |
| RDAP base URL | https://rdap.apnic.net |
| Stats base URL | https://ftp.apnic.net/apnic/stats/apnic/ |
| Cache TTL | 30 minutes |
| User-Agent (non-stealth) | APNIC-Go-SDK/1.0 (security) |
| Stealth | true (browser headers + jitter) |
| Browser UA | mainstream Chrome |
| Jitter range | 200–800 ms per request |
| Rate limit | none (unlimited) |
| Chunked download | enabled (4 workers, 2 MiB chunks, 512 KiB minimum) |
| RRDP base URL | https://rrdp.apnic.net |
| Thyme base URL | https://thyme.apnic.net |
| Thyme default source | current (global view) |
| FTP base URL | https://ftp.apnic.net/ |
| REx base URL | https://api.rex.apnic.net |
All defaults are safe for normal use. Override them only when you have specific requirements (throttled networks, custom endpoints, integration tests, or unusual throughput patterns).
All Options (Alphabetical)¶
| Option | Description |
|---|---|
WithBrowserUserAgent(ua) | User-Agent string used when stealth is enabled |
WithCacheTTL(d) | Cache time-to-live for Get* methods (stats, extended, assigned, legacy, transfers, changes, IRR, telemetry) |
WithChunkSize(bytes) | Target chunk size for chunked download (0 = split evenly across workers) |
WithDownloadTimeout(d) | Per-chunk timeout for chunked downloads |
WithFTPBaseURL(url) | APNIC FTP root URL (IRR, transfers-all, zones, telemetry) |
WithHTTPClient(hc) | Custom *http.Client (overrides default 15s timeout) |
WithJitter(min, max) | Random delay range per HTTP request when stealth is on |
WithMaxConcurrentDownloads(n) | Parallel Range requests for large files (≤1 disables chunking) |
WithRateLimit(perSecond) | Token-bucket rate limit (requests/second, 0 = unlimited) |
WithRDAPBaseURL(url) | Base URL for RDAP queries |
WithRDAPDate(t) | Default point-in-time for all RDAP lookups (APNIC history_version_0) |
WithRExBaseURL(url) | Base URL for REx cross-RIR resource registry |
WithRRDPBaseURL(url) | Base URL for RPKI RRDP |
WithStatsBaseURL(url) | Base URL for delegated/extended/assigned/legacy stats files |
WithStealth(enable) | Enable browser mimicry headers and request jitter (default true) |
WithThymeBaseURL(url) | Base URL for thyme BGP analysis |
WithThymeSource(src) | Default thyme data source: current, au, or hk |
WithUserAgent(ua) | User-Agent when stealth is disabled |
WithWhoisServer(addr) | Whois server address (host:port) |
WithWhoisTimeout(d) | Whois connection timeout |
Configuration Layers¶
The Client is built in layers. Functional options mutate these fields directly. The diagram below shows how HTTP, anti-scraping, caching, and download layers relate.
flowchart TB
subgraph Options["Functional Options"]
O1["WithHTTPClient"]
O2["WithCacheTTL"]
O3["WithUserAgent"]
O4["WithStealth"]
O5["WithBrowserUserAgent"]
O6["WithJitter"]
O7["WithRateLimit"]
O8["WithMaxConcurrentDownloads"]
O9["WithChunkSize"]
O10["WithDownloadTimeout"]
O11["WithRDAPBaseURL"]
O12["WithWhoisServer/Timeout"]
O13["With*BaseURL"]
O14["WithThymeSource"]
O15["WithRDAPDate"]
end
subgraph Client["Client struct"]
HTTP["httpClient<br/>*http.Client"]
Cache["cache<br/>*cache (TTL)"]
Stealth["stealth<br/>browserUA<br/>jitterMin/Max<br/>rateLimiter"]
Download["downloadCfg<br/>maxConcurrent<br/>minSize<br/>targetChunk<br/>timeout"]
URLs["rdapBaseURL<br/>statsBaseURL<br/>rrdpBaseURL<br/>thymeBaseURL<br/>ftpBaseURL<br/>rexBaseURL"]
Whois["whoisServer<br/>whoisTimeout"]
RDAPDate["rdapDate<br/>point-in-time default"]
end
subgraph Layers["Execution Layers"]
L1["HTTP Layer<br/>doHTTPRequest()"]
L2["Anti-Scraping Layer<br/>applyBrowserHeaders()<br/>waitRateLimit()<br/>jitter()"]
L3["Cache Layer<br/>get()/set()"]
L4["Download Layer<br/>probeRange()<br/>downloadChunked()"]
end
O1 --> HTTP
O2 --> Cache
O3 --> Client
O4 --> Stealth
O5 --> Stealth
O6 --> Stealth
O7 --> Stealth
O8 --> Download
O9 --> Download
O10 --> Download
O11 --> URLs
O12 --> Whois
O13 --> URLs
O14 --> Client
O15 --> RDAPDate
HTTP --> L1
Stealth --> L2
Cache --> L3
Download --> L4
L1 --> L2 --> L3 --> L4 Option Pattern in Action¶
Each option is a function that mutates the *Client. They are applied in order, so later options override earlier ones. This pattern enables clean, declarative configuration.
client := apnic.NewClient(
apnic.WithCacheTTL(10*time.Minute),
apnic.WithUserAgent("my-scanner/2.0"),
apnic.WithRDAPBaseURL("https://rdap.apnic.net"),
apnic.WithWhoisServer("whois.apnic.net:43"),
apnic.WithWhoisTimeout(15*time.Second),
apnic.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
)
Internally, NewClient constructs a Client with defaults, then applies each Option in sequence:
func NewClient(opts ...Option) *Client {
c := &Client{
httpClient: &http.Client{Timeout: 15 * time.Second},
whoisServer: "whois.apnic.net:43",
whoisTimeout: 10 * time.Second,
// ... other defaults
}
for _, opt := range opts {
opt(c) // mutate c in place
}
return c
}
flowchart LR
subgraph Defaults["Default Client"]
D1["httpClient.Timeout = 15s"]
D2["cache.ttl = 30m"]
D3["stealth = true"]
D4["browserUA = Chrome"]
D5["jitter = 200–800ms"]
D6["maxConcurrent = 4"]
D7["chunkSize = 2MiB"]
end
subgraph Chain["Option Chain"]
O1["WithCacheTTL(10m)"]
O2["WithJitter(0, 0)"]
O3["WithRateLimit(5)"]
end
subgraph Final["Final Client"]
F1["httpClient.Timeout = 15s"]
F2["cache.ttl = 10m"]
F3["stealth = true"]
F4["jitter = 0 (disabled)"]
F5["rateLimiter = 5 req/s"]
end
Defaults --> Chain --> Final Anti-Scraping Configuration¶
APNIC services are public but rate-limited. The SDK's anti-scraping stack (browser mimicry, jitter, token bucket) is enabled by default and tuned for courtesy. Adjust these knobs if you hit throttling or need higher throughput.
What Stealth Mode Does¶
When WithStealth(true) (the default), every HTTP request from the SDK includes a full set of mainstream Chrome headers:
| header | value |
|---|---|
User-Agent | Chrome UA (customizable via WithBrowserUserAgent) |
Accept | text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 |
Accept-Language | en-US,en;q=0.9 |
Accept-Encoding | gzip (SDK handles decompression) |
Sec-Fetch-* | Sec-Fetch-Dest: document, Sec-Fetch-Mode: navigate, Sec-Fetch-Site: none, Sec-Fetch-User: ?1 |
Sec-Ch-Ua-* | Sec-Ch-Ua: "Chromium";v="130", Sec-Ch-Ua-Mobile: ?0, Sec-Ch-Ua-Platform: "Linux" |
Upgrade-Insecure-Requests | 1 |
The whois port-43 connection adds a random sleep between 200–800 ms (default jitter range) before each query.
Disabling Stealth¶
If your use case cannot tolerate extra headers (e.g., a minimal scanner, or integration with a gateway that rejects unknown headers), disable stealth:
client := apnic.NewClient(
apnic.WithStealth(false), // send only UA + Accept
apnic.WithUserAgent("my-bot/1.0"),
)
When stealth is off, the SDK sends a minimal User-Agent and Accept header only — the pre-stealth behavior. The jitter is also skipped.
Jitter and Rate Limiting¶
Jitter randomizes the interval between HTTP requests. Rate limiting enforces a global ceiling. Both are orthogonal; you can have one without the other.
// Tighter jitter for higher throughput (riskier)
client := apnic.NewClient(
apnic.WithJitter(50*time.Millisecond, 150*time.Millisecond),
)
// No jitter (for benchmarks or trusted networks)
client := apnic.NewClient(
apnic.WithJitter(0, 0), // zero min disables jitter entirely
)
// Rate limit to 2 requests per second, burst = 1
client := apnic.NewClient(
apnic.WithRateLimit(2.0),
)
// Combine both: max 5 req/s, with 100–300ms random jitter on top
client := apnic.NewClient(
apnic.WithRateLimit(5.0),
apnic.WithJitter(100*time.Millisecond, 300*time.Millisecond),
)
The token bucket has a burst capacity of 1. A rate limit of 2.0 means the bucket refills at 2 tokens per second; a request costs 1 token. If the bucket is empty, the SDK blocks until a token is available.
Testing Without Jitter¶
For unit or integration tests, jitter adds non-determinism. Disable it via environment variable rather than code:
This skips the jitter() call entirely, making request timing deterministic. The SDK still applies rate limiting and browser headers.
Chunked Download Configuration¶
Large APNIC FTP files (delegated ~4.3 MB, extended, IRR dumps up to 50+ MB) are throttled per-connection to ~8–22 KB/s. A single TCP connection for a 50 MB IRR dump can take over 40 minutes — far beyond typical timeouts.
The SDK's chunked download layer probes Range support, then splits the file into ~2 MiB chunks and downloads them with 4 parallel Range requests. Each connection gets its own throttle bucket, yielding 3–4× throughput. The chunks are reassembled in order via io.Pipe and decompressed on the fly.
Turning Chunking On/Off¶
// Disable chunking (single connection, legacy behavior)
client := apnic.NewClient(
apnic.WithMaxConcurrentDownloads(0), // or 1
)
// Increase parallelism (8 workers)
client := apnic.NewClient(
apnic.WithMaxConcurrentDownloads(8),
)
// Smaller chunks (helpful on very slow networks)
client := apnic.NewClient(
apnic.WithChunkSize(1 * 1024 * 1024), // 1 MiB
)
// Longer per-chunk timeout
client := apnic.NewClient(
apnic.WithDownloadTimeout(10 * time.Minute),
)
How Chunked Download Works¶
flowchart TB
subgraph Probe["Range Probe"]
Start["fetchText(url)"]
Probe["GET Range: bytes=0-0"]
Check{"Content-Length<br/>Accept-Ranges: bytes?"}
end
subgraph Strategy["Strategy Selection"]
Chunked["Chunked Download<br/>Split by chunkSize<br/>Round-robin N workers"]
Single["Single Connection<br/>io.ReadAll"]
end
subgraph Workers["Parallel Workers (N=4 default)"]
W1["Worker 0<br/>Range 0–2MiB"]
W2["Worker 1<br/>Range 2MiB–4MiB"]
W3["Worker 2<br/>Range 4MiB–6MiB"]
W4["Worker 3<br/>Range 6MiB–8MiB"]
WN["... rotate through<br/>remaining chunks"]
end
subgraph Merge["Merge & Decompress"]
Pipe["io.Pipe<br/>ordered by chunk index"]
Gunzip["gzip.NewReader<br/>if .gz extension"]
Parse["Parser<br/>line-by-line"]
end
Start --> Probe --> Check
Check -->|yes| Chunked --> W1 & W2 & W3 & W4 --> WN --> Pipe --> Gunzip --> Parse
Check -->|no| Single --> Gunzip --> Parse Key behaviors:
- Probe first: A
Range: bytes=0-0request checks whether the server supports Range requests and revealsContent-Length. If Range is unsupported or the response is transport-layer gzipped (APNIC does not do this, but some CDNs might), the SDK falls back to a single connection. - Minimum size: Files smaller than
minSize(default 512 KiB) skip chunking entirely — the overhead of Range probing outweighs the benefit. - Timeouts: Each chunk request uses
WithDownloadTimeout(default inherits HTTP client timeout). If a chunk stalls past its deadline, the SDK splits it into two sub-chunks and retries with new workers, avoiding a dead TCP connection from killing the entire download. - Decompression: If the URL ends in
.gz, the SDK transparently wraps the merged stream withgzip.NewReader.
Troubleshooting Chunked Downloads¶
| symptom | likely cause | fix |
|---|---|---|
context deadline exceeded on large IRR dump | per-chunk timeout too short | WithDownloadTimeout(5 * time.Minute) |
| still timing out | network is very slow | WithChunkSize(512 * 1024) + WithDownloadTimeout(10 * time.Minute) |
403 Forbidden | Range probing rejected | server does not allow Range; SDK falls back to single connection automatically |
| multiple parallel connections still slow | APNIC FTP per-IP throttle | reduce WithMaxConcurrentDownloads and retry later; or distribute requests across IPs |
Cache Configuration¶
The SDK caches large, static datasets (delegated, extended, assigned, legacy, transfers, changes, IRR dumps, telemetry) in memory for the duration of the cache TTL. The Get* methods (GetDelegatedEntries, GetExtendedEntries, etc.) return cached data if available; Fetch* methods always refetch.
Default Cache Behavior¶
client := apnic.NewClient() // TTL = 30 minutes
// First call: fetches from APNIC FTP, parses, caches
entries, _ := client.GetDelegatedEntries(ctx)
// Second call within 30 min: returns cached result, no network I/O
cached, _ := client.GetDelegatedEntries(ctx)
Adjusting TTL¶
// Shorter TTL for fresher data
client := apnic.NewClient(
apnic.WithCacheTTL(5 * time.Minute),
)
// Longer TTL for batch jobs that re-use the same dataset
client := apnic.NewClient(
apnic.WithCacheTTL(2 * time.Hour),
)
// Effectively no caching (TTL = 1 nanosecond)
client := apnic.NewClient(
apnic.WithCacheTTL(1),
)
The cache is an in-memory map with a timestamp per entry. It is not persisted to disk. Restarting your process clears the cache.
Cache vs Fetch Methods¶
| method | cache behavior |
|---|---|
GetDelegatedEntries(ctx) | returns cached if within TTL, otherwise fetches |
FetchDelegatedEntries(ctx) | always fetches from network, updates cache |
GetExtendedEntries(ctx) | returns cached if within TTL |
FetchExtendedEntries(ctx) | always fetches |
GetIRRDatabase(ctx, objType) | returns cached if within TTL |
FetchIRRDatabase(ctx, objType) | always fetches |
Use Fetch* when you know the upstream data has changed and want the latest. Use Get* for most read-only workloads.
Service Endpoint Configuration¶
Each APNIC service has its own base URL. Override these for testing against a mirror, a local cache, or an alternate endpoint.
client := apnic.NewClient(
apnic.WithRDAPBaseURL("https://rdap-example.labs.apnic.net"),
apnic.WithStatsBaseURL("https://mirror.example.org/apnic/stats/apnic/"),
apnic.WithWhoisServer("whois.example.org:43"),
apnic.WithRRDPBaseURL("https://rrdp-example.labs.apnic.net"),
apnic.WithThymeBaseURL("https://thyme-example.labs.apnic.net"),
apnic.WithFTPBaseURL("https://ftp.example.org/"),
apnic.WithRExBaseURL("https://rex-example.labs.apnic.net"),
)
RDAP Point-in-Time (Historical Queries)¶
APNIC's RDAP service supports history_version_0 for point-in-time lookups. Set a global default:
t, _ := time.Parse(time.RFC3339, "2022-06-01T00:00:00Z")
client := apnic.NewClient(
apnic.WithRDAPDate(t),
)
// All RDAP calls now query the resource state as of 2022-06-01
net, _ := client.RDAPLookupIP(ctx, "1.1.1.1")
Or override per-call with the *At variants:
netNow, _ := client.RDAPLookupIP(ctx, "1.1.1.1") // live
netThen, _ := client.RDAPLookupIPAt(ctx, "1.1.1.1", "2020-01-01T00:00:00Z") // historical
thyme BGP Default Source¶
The thyme service publishes data from three vantage points. Set a default source so you don't have to pass it per-call:
// Default to Brisbane
client := apnic.NewClient(
apnic.WithThymeSource("au"),
)
// All FetchBGPSummary, FetchBGPBadPrefixes, etc. use "au" unless overridden
// Per-call override still works
hkBadPfx, _ := client.FetchBGPBadPrefixes(ctx, "hk")
Custom HTTP Client¶
For fine-grained control (proxy, TLS config, connection pooling), inject a custom *http.Client:
import (
"net/http"
"net/url"
"time"
)
proxyURL, _ := url.Parse("http://proxy.example.org:8080")
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
}
client := apnic.NewClient(
apnic.WithHTTPClient(&http.Client{
Transport: transport,
Timeout: 60 * time.Second,
}),
)
When you provide a custom HTTP client, its timeout supersedes the default 15s. However, chunked download timeouts are controlled separately by WithDownloadTimeout, which defaults to inheriting the HTTP client timeout.
Summary¶
| concern | options |
|---|---|
| anti-scraping | WithStealth, WithBrowserUserAgent, WithJitter, WithRateLimit |
| caching | WithCacheTTL |
| chunked download | WithMaxConcurrentDownloads, WithChunkSize, WithDownloadTimeout |
| endpoints | WithRDAPBaseURL, WithStatsBaseURL, WithWhoisServer, WithRRDPBaseURL, WithThymeBaseURL, WithFTPBaseURL, WithRExBaseURL |
| thyme source | WithThymeSource |
| RDAP historical | WithRDAPDate |
| whois | WithWhoisServer, WithWhoisTimeout |
| HTTP layer | WithHTTPClient, WithUserAgent |
For most users, the defaults are optimal. Override only when you hit a specific constraint (slow network, custom proxy, alternate endpoint, test environment). See Chunked Download for an in-depth treatment of the Range-probe / multi-worker / merge pipeline.