A high-throughput, massively-parallel file downloader for .NET that doubles as a Cloudflare CDN analyzer — it measures real download performance while surfacing per-request cache status, edge PoP, and time-to-first-byte pulled straight from Cloudflare's response headers.
Point it at a list of URLs and it will saturate your connection across hundreds of concurrent transfers, then break down — file by file and in aggregate — how fast each one went, whether it was served from cache, and which Cloudflare edge location answered.
Platform: Windows (WinForms UI), .NET 8. The download engine itself has no UI dependencies and is a plain reusable class.
Generic download managers tell you how fast. When the origin sits behind Cloudflare, the more interesting questions are why a transfer was fast or slow: was it a cache HIT or a MISS back to origin? Which PoP served it? How much of the latency was time-to-first-byte versus transfer? CloudflareBench answers those while pushing enough parallelism to actually find the ceiling.
- Unbounded parallelism, under control. Downloads run concurrently through a
SemaphoreSlimgate (default: unlimited, configurable). A single tunedSocketsHttpHandleris shared across all transfers with HTTP/2 multiplexing, 100 connections per server, and a warm connection pool. - Live per-file telemetry: status, progress, size, instantaneous speed (sampled every 100 ms), TTFB, HTTP status, and errors — updated on a throttled 50 ms cadence so the UI stays responsive under load.
- Aggregate dashboard: current / average / peak throughput, total bytes, ETA, and active-transfer count, recomputed every 200 ms.
- Cloudflare intelligence parsed from each response:
CF-Cache-Statusbucketed into HIT / MISS / EXPIRED / DYNAMIC counters- Edge PoP extracted from
CF-RAY, with a live per-PoP distribution cf-polished,Age,Server,ETag,CF-Request-IDsurfaced per file
- Robust file handling: streams directly to disk with a 1 MB async buffer (
FileOptions.Asynchronous | SequentialScan), sanitizes filenames from URLs, and auto-deduplicates collisions (file.bin→file_1.bin). - Cancellation everywhere: a global stop plus per-item cancellation, all wired through linked
CancellationTokenSources.
MainForm (WinForms)
│ events: progress / completed / failed / stats
▼
DownloadEngine ──shared──▶ SocketsHttpHandler (HTTP/2, pooled, 100 conn/server)
│
├── SemaphoreSlim(maxParallel) gate
├── per-item Task: stream → 1 MB buffer → FileStream
├── per-item stats: TTFB, speed, ETA (throttled UI events)
└── background loop: aggregate speed/peak/ETA (every 200 ms)
The engine (Engine/DownloadEngine.cs) is deliberately UI-agnostic: it exposes events and a small imperative API (AddUrls, StartAllDownloadsAsync, StopAllDownloads, CancelDownload) and knows nothing about WinForms. The form is a thin presentation layer over it.
Process-level tuning in Program.cs lifts the framework's default throttles for this workload: DefaultConnectionLimit = 1000, Nagle and Expect: 100-continue disabled, and a pre-warmed thread pool (SetMinThreads(100, 100)).
Requires the .NET 8 SDK on Windows.
git clone https://github.com/astralmaster/CloudflareBench.git
cd CloudflareBench
dotnet build -c Release
dotnet run -c Release- Paste one URL per line into the URLs box.
- Pick an output directory, and optionally cap Parallel Downloads and Buffer Size.
- Start. Watch the per-file grid and the Statistics / Cloudflare Stats panels fill in live.
- Stop cancels everything cleanly; Clear resets the queue.
Only download from servers you own or are authorized to test — at high parallelism this generates substantial load.
- One handler, many requests. Creating an
HttpClientper download is the classic socket-exhaustion bug; here a singleSocketsHttpHandlerwith a bounded, long-lived connection pool backs every transfer. - Timeouts are per-request, not global. The client's own timeout is disabled and each transfer manages its own linked token, so one slow file can be cancelled without touching the rest.
- UI updates are throttled at the source. Progress and stats events are rate-limited inside the engine (50 ms / 200 ms), so a thousand active transfers don't flood the UI thread.
MIT — see LICENSE.