1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
namespace Cepis;
/// <summary>
/// How a <see cref="CepisClient"/> behaves. Every setting has a default; the
/// one that usually changes between environments is <see cref="BaseUrl"/>.
/// </summary>
/// <example>
/// <code>
/// using var client = new CepisClient(new CepisClientOptions
/// {
/// BaseUrl = "http://localhost:8080",
/// TotalTimeout = TimeSpan.FromSeconds(15),
/// });
/// </code>
/// </example>
public sealed record CepisClientOptions
{
/// <summary>
/// Where the API lives. Defaults to <see cref="CepisClient.DefaultUrl"/>.
/// </summary>
/// <remarks>
/// Point it at a local instance or at a test server. A trailing slash is
/// stripped when the client is built, so it cannot produce doubled
/// separators that some proxies normalise and others answer 404 to.
/// </remarks>
public string BaseUrl { get; init; } = CepisClient.DefaultUrl;
/// <summary>
/// How long a single attempt may take, connection included. Defaults to ten
/// seconds.
/// </summary>
/// <remarks>
/// It is not a cap on the whole call: with three attempts and the waits
/// between them, the worst case is roughly three times this plus the
/// backoffs. <see cref="TotalTimeout"/> is the cap on the whole call.
/// </remarks>
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(10);
/// <summary>
/// How many attempts in total, counting the first. Defaults to three.
/// </summary>
/// <remarks>
/// <c>1</c> disables retrying. The default of <c>3</c> covers the common
/// case, one transient failure, without turning a real outage into a long
/// wait.
/// </remarks>
public int MaxAttempts { get; init; } = 3;
/// <summary>
/// The base of the exponential wait between attempts. Defaults to 500
/// milliseconds.
/// </summary>
/// <remarks>
/// The actual wait is drawn from <c>[0, base * 2^(n-1)]</c> — full jitter.
/// The randomness is not decoration: without it, N clients that fail
/// together come back together and reproduce the spike that took the
/// service down.
/// </remarks>
public TimeSpan BackoffBase { get; init; } = TimeSpan.FromMilliseconds(500);
/// <summary>
/// The cap on each individual wait. Defaults to eight seconds.
/// </summary>
/// <remarks>
/// It applies to a server-sent <c>Retry-After</c> too, or a
/// <c>Retry-After: 3600</c> from a misconfigured proxy would park the
/// caller for an hour.
/// </remarks>
public TimeSpan BackoffMax { get; init; } = TimeSpan.FromSeconds(8);
/// <summary>
/// The cap on the whole call, attempts and waits included. Unset by
/// default.
/// </summary>
/// <remarks>
/// <see langword="null"/> leaves <see cref="MaxAttempts"/> as the only
/// bound. Set it in code serving a web request: without it the worst case
/// is <see cref="MaxAttempts"/> times <see cref="Timeout"/> plus the sum of
/// the backoffs.
/// </remarks>
public TimeSpan? TotalTimeout { get; init; }
/// <summary>
/// Overrides the <c>User-Agent</c>. <see langword="null"/> uses the
/// default.
/// </summary>
/// <remarks>
/// The default identifies the library and its version, which is what lets
/// the server diagnose an old client misbehaving.
/// </remarks>
public string? UserAgent { get; init; }
}
|