curl –retry in C#
To retry a request in C# the way curl does, add --retry N to the command you pass to Curl.ExecuteAsync. CurlDotNet then repeats the transfer up to N more times when it fails with a transient error, waiting between attempts like curl: one second first, doubling each time, capped at ten minutes, and honouring a Retry-After header from the server.
The curl command
using CurlDotNet;
// Up to 3 retries for timeouts, DNS failures and HTTP 408, 429, 500, 502, 503 and 504
var result = await Curl.ExecuteAsync("curl --retry 3 -m 10 https://httpbin.org/status/200");
Console.WriteLine(result.StatusCode); // 200
-m / --max-time limits each attempt, not the whole sequence. Use --retry-max-time to cap the total time spent retrying.
What is retried
| Failure | Retried with --retry |
Also needs |
|---|---|---|
Timeout (-m expired or --speed-limit tripped, exit 28) |
yes | |
| Host or proxy name did not resolve (exit 6, 5) | yes | |
| HTTP 408, 429, 500, 502, 503, 504 | yes | |
| Connection failed (exit 7) | no | --retry-connrefused (CurlDotNet retries any connect failure with it; curl only a refused connection) |
Any other error (other 4xx/5xx with -f, TLS, …) |
no | --retry-all-errors |
Transient HTTP statuses are retried with or without -f. When the last attempt still gets, say, a 503, the result depends on -f: without it you get a normal CurlResult with StatusCode 503; with it CurlHttpReturnedErrorException (exit 22) is thrown.
using CurlDotNet;
using CurlDotNet.Exceptions;
try
{
var result = await Curl.ExecuteAsync(
"curl -f --retry 4 --retry-connrefused --retry-max-time 60 https://httpbin.org/status/503");
Console.WriteLine(result.Body);
}
catch (CurlHttpReturnedErrorException ex)
{
Console.WriteLine($"Still failing after retries: HTTP {ex.StatusCode}");
}
catch (CurlCouldntConnectException ex)
{
Console.WriteLine($"Server unreachable: {ex.Message}");
}
catch (CurlTimeoutException)
{
Console.WriteLine("Gave up: the last attempt timed out");
}
The retry options
| Option | Meaning |
|---|---|
--retry N |
Retry up to N times after the first attempt. |
--retry-delay SECONDS |
Wait this long between attempts instead of the doubling back-off. Decimals work: --retry-delay 0.5 (a CurlDotNet extension; the curl tool takes whole seconds). |
--retry-max-time SECONDS |
Stop retrying once this many seconds have passed since the first attempt, or when a server’s Retry-After wait would go past that point. |
--retry-all-errors |
Retry every failure, not only the transient ones. |
--retry-connrefused |
Also retry when the connection cannot be made. |
The waits without --retry-delay are 1, 2, 4, 8 … seconds, up to 600. Like curl, the wait before each retry is the larger of that delay (or your --retry-delay) and the server’s Retry-After: <seconds> header, capped at 600 seconds; Retry-After: 0 is treated as no hint. Only the seconds form of Retry-After is read, not an HTTP date.
When --retry-max-time runs out after a failed attempt, the last attempt’s own result or exception stands, exactly as in curl. -w output is written once, for the final attempt, and %{num_retries} reports how many retries happened.
Retries without a command string
CurlOptions has the same settings as properties, so you can build the request without any quoting:
using CurlDotNet;
using CurlDotNet.Core;
var options = new CurlOptions
{
Url = "https://httpbin.org/get",
Retry = 3,
RetryDelay = 2, // seconds; 0 means curl's doubling back-off
RetryMaxTime = 30,
RetryConnRefused = true,
MaxTime = 10
};
var result = await Curl.ExecuteAsync(options);
Console.WriteLine(result.StatusCode);
CurlSettings.WithRetries(count, delayMs) is the .NET-style shortcut. It retries any failure (it turns on --retry-all-errors) with a fixed delay:
using CurlDotNet;
using CurlDotNet.Core;
var settings = new CurlSettings().WithRetries(3, 500); // 3 retries, 500 ms apart
var result = await Curl.ExecuteAsync("curl https://httpbin.org/get", settings);
Console.WriteLine(result.StatusCode);
Downloads and output files
With -o, a failed attempt that already wrote body bytes is truncated before the next attempt, as in curl, so the file ends up holding one complete copy. An attempt that wrote only headers (for example -i -f on an error status) is left in the file.
To continue a large download that stopped in an earlier run instead of starting over, run the command again with -C -, which resumes from the size of the existing file:
using CurlDotNet;
var result = await Curl.ExecuteAsync("curl --retry 5 -C - -o big.iso https://example.com/big.iso");
Console.WriteLine(result.OutputFiles[0]);
See curl download file in C# for -C - and the other output options.
Gotchas
- Cancellation is not retried. A cancelled
CancellationTokenstops the retry loop, including a wait between attempts, and throwsOperationCanceledException. -w/--write-outtext is written once, after the final attempt, as the curl tool does, and%{num_retries}holds the number of retries. When an attempt returns a transient status without-f(a 503, say) and is retried, its body stays inBodyahead of the final attempt’s output, the way curl prints every attempt to stdout; use-oor readresult.StatusCodeif you only want the last response.- With several URLs in one command, each transfer gets its own retries.
- Retries apply to HTTP and HTTPS transfers.