• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Jericho / ZoomNet / 900

19 Mar 2025 03:18PM UTC coverage: 22.365% (+2.0%) from 20.329%
900

push

appveyor

Jericho
Standardize how we validate `recordsPerPage`

730 of 3264 relevant lines covered (22.37%)

11.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

18.18
/Source/ZoomNet/Utilities/Http429RetryStrategy.cs
1
using Pathoschild.Http.Client.Retry;
2
using System;
3
using System.Net;
4
using System.Net.Http;
5
using System.Net.Http.Headers;
6

7
namespace ZoomNet.Utilities
8
{
9
        /// <summary>
10
        /// Implements IRetryConfig with back off based on a wait time derived from the
11
        /// "Retry-After" response header. The value in this header contains the date
12
        /// and time when the next attempt can take place.
13
        /// </summary>
14
        /// <seealso cref="IRetryConfig" />
15
        internal class Http429RetryStrategy : IRetryConfig
16
        {
17
                #region FIELDS
18

19
                private const int DEFAULT_MAX_RETRIES = 4;
20
                private const HttpStatusCode TOO_MANY_REQUESTS = (HttpStatusCode)429;
21
                private static readonly TimeSpan DEFAULT_DELAY = TimeSpan.FromSeconds(1);
×
22

23
                private readonly ISystemClock _systemClock;
24

25
                #endregion
26

27
                #region PROPERTIES
28

29
                /// <summary>Gets the maximum number of times to retry a request before failing.</summary>
30
                public int MaxRetries { get; }
31

32
                #endregion
33

34
                #region CTOR
35

36
                /// <summary>
37
                /// Initializes a new instance of the <see cref="Http429RetryStrategy" /> class.
38
                /// </summary>
39
                public Http429RetryStrategy()
40
                        : this(DEFAULT_MAX_RETRIES, null)
12✔
41
                {
42
                }
12✔
43

44
                /// <summary>
45
                /// Initializes a new instance of the <see cref="Http429RetryStrategy" /> class.
46
                /// </summary>
47
                /// <param name="maxAttempts">The maximum attempts.</param>
48
                public Http429RetryStrategy(int maxAttempts)
49
                        : this(maxAttempts, null)
×
50
                {
51
                }
×
52

53
                /// <summary>
54
                /// Initializes a new instance of the <see cref="Http429RetryStrategy" /> class.
55
                /// </summary>
56
                /// <param name="maxAttempts">The maximum attempts.</param>
57
                /// <param name="systemClock">The system clock. This is for unit testing only.</param>
58
                internal Http429RetryStrategy(int maxAttempts, ISystemClock systemClock)
59
                {
60
                        MaxRetries = maxAttempts;
61
                        _systemClock = systemClock ?? SystemClock.Instance;
12✔
62
                }
12✔
63

64
                #endregion
65

66
                #region PUBLIC METHODS
67

68
                /// <summary>
69
                /// Checks if we should retry an operation.
70
                /// </summary>
71
                /// <param name="response">The Http response of the previous request.</param>
72
                /// <returns>
73
                ///   <c>true</c> if another attempt should be made; otherwise, <c>false</c>.
74
                /// </returns>
75
                public bool ShouldRetry(HttpResponseMessage response)
76
                {
77
                        if (response == null) return false;
10✔
78
                        if (response.StatusCode != TOO_MANY_REQUESTS) return false;
20✔
79

80
                        var rateLimitInfo = GetRateLimitInformation(response?.Headers);
×
81

82
                        if (DateTime.TryParse(rateLimitInfo.RetryAfter, out DateTime nextRetryDateTime))
×
83
                        {
84
                                // There's no need to retry when the reset time is too far in the future.
85
                                // I arbitrarily decided that 15 seconds is the cutoff.
86
                                return nextRetryDateTime.Subtract(_systemClock.UtcNow).TotalSeconds < 15;
×
87
                        }
88
                        else if ((rateLimitInfo.Type ?? string.Empty).Equals("QPS", StringComparison.OrdinalIgnoreCase))
×
89
                        {
90
                                return true;
×
91
                        }
92
                        else
93
                        {
94
                                return false;
×
95
                        }
96
                }
97

98
                /// <summary>
99
                /// Gets a TimeSpan value which defines how long to wait before trying again after an unsuccessful attempt.
100
                /// </summary>
101
                /// <param name="attempt">The number of attempts carried out so far. That is, after the first attempt (for
102
                /// the first retry), attempt will be set to 1, after the second attempt it is set to 2, and so on.</param>
103
                /// <param name="response">The Http response of the previous request.</param>
104
                /// <returns>
105
                /// A TimeSpan value which defines how long to wait before the next attempt.
106
                /// </returns>
107
                public TimeSpan GetDelay(int attempt, HttpResponseMessage response)
108
                {
109
                        // Default value in case the HTTP headers don't provide enough information
110
                        var waitTime = DEFAULT_DELAY;
×
111

112
                        // Figure out how long to wait based on the presence of Zoom specific headers as discussed here:
113
                        // https://marketplace.zoom.us/docs/api-reference/rate-limits#best-practices-for-handling-errors
114
                        var rateLimitInfo = GetRateLimitInformation(response?.Headers);
×
115

116
                        if (DateTime.TryParse(rateLimitInfo.RetryAfter, out DateTime nextRetryDateTime))
×
117
                        {
118
                                waitTime = nextRetryDateTime.Subtract(_systemClock.UtcNow);
×
119
                        }
120
                        else if ((rateLimitInfo.Type ?? string.Empty).Equals("QPS", StringComparison.OrdinalIgnoreCase))
×
121
                        {
122
                                // QPS stands for "Query Per Second".
123
                                // It means that we have exceeded the number of API calls per second.
124
                                // Therefore we must wait one second before retrying.
125
                                var waitMilliseconds = 1000;
×
126

127
                                // Edit January 2021: I introduced randomness in the wait time to avoid retrying too quickly
128
                                // and to avoid requests issued in a tight loop to all be retried at the same time.
129
                                // Up to one extra second: 250 milliseconds * 4.
130
                                for (int i = 0; i < 4; i++)
×
131
                                {
132
                                        waitMilliseconds += RandomGenerator.RollDice(250);
×
133
                                }
134

135
                                waitTime = TimeSpan.FromMilliseconds(waitMilliseconds);
×
136
                        }
137

138
                        // Make sure the wait time is valid
139
                        if (waitTime.TotalMilliseconds < 0) waitTime = DEFAULT_DELAY;
×
140

141
                        // Totally arbitrary. Make sure we don't wait more than a 'reasonable' amount of time
142
                        if (waitTime.TotalSeconds > 5) waitTime = TimeSpan.FromSeconds(5);
×
143

144
                        return waitTime;
×
145
                }
146

147
                #endregion
148

149
                #region PRIVATE METHODS
150

151
                private static (string Category, string Type, string Limit, string Remaining, string RetryAfter) GetRateLimitInformation(HttpResponseHeaders headers)
152
                {
153
                        var category = headers?.GetValue("X-RateLimit-Category");
×
154
                        var type = headers?.GetValue("X-RateLimit-Type");
×
155
                        var limit = headers?.GetValue("X-RateLimit-Limit");
×
156
                        var remaining = headers?.GetValue("X-RateLimit-Remaining");
×
157
                        var retryAfter = headers?.GetValue("Retry-After");
×
158

159
                        return (category, type, limit, remaining, retryAfter);
×
160
                }
161

162
                #endregion
163
        }
164
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc