1- using System . Globalization ;
1+ using System ;
2+ using System . Globalization ;
23using System . Net . Http ;
34using System . Threading . Tasks ;
45using System . Web ;
@@ -9,6 +10,9 @@ namespace PG.StarWarsGame.Infrastructure.Services.Steam;
910internal class SteamWorkshopWebpageDownloader : ISteamWorkshopWebpageDownloader
1011{
1112 private const string SteamWorkshopsBaseUrl = "https://steamcommunity.com/sharedfiles/filedetails/?" ;
13+ private const int MaxRetries = 4 ;
14+ private static readonly TimeSpan InitialBackoff = TimeSpan . FromSeconds ( 1 ) ;
15+ private static readonly TimeSpan MaxBackoff = TimeSpan . FromSeconds ( 30 ) ;
1216
1317 public async Task < HtmlDocument > GetSteamWorkshopsPageHtmlAsync ( ulong workshopId , CultureInfo ? culture )
1418 {
@@ -20,9 +24,45 @@ public async Task<HtmlDocument> GetSteamWorkshopsPageHtmlAsync(ulong workshopId,
2024
2125 var address = $ "{ SteamWorkshopsBaseUrl } { queryString } ";
2226 using var client = new HttpClient ( ) ;
23- var reply = await client . GetStringAsync ( address ) ;
24- var htmlDocument = new HtmlDocument ( ) ;
25- htmlDocument . LoadHtml ( reply ) ;
26- return htmlDocument ;
27+
28+ var backoff = InitialBackoff ;
29+ for ( var attempt = 0 ; ; attempt ++ )
30+ {
31+ using var response = await client . GetAsync ( address ) . ConfigureAwait ( false ) ;
32+
33+ if ( ( int ) response . StatusCode != 429 )
34+ {
35+ response . EnsureSuccessStatusCode ( ) ;
36+ var reply = await response . Content . ReadAsStringAsync ( ) . ConfigureAwait ( false ) ;
37+ var htmlDocument = new HtmlDocument ( ) ;
38+ htmlDocument . LoadHtml ( reply ) ;
39+ return htmlDocument ;
40+ }
41+
42+ if ( attempt >= MaxRetries )
43+ response . EnsureSuccessStatusCode ( ) ;
44+
45+ var wait = GetRetryAfter ( response ) ?? backoff ;
46+ if ( wait > MaxBackoff )
47+ wait = MaxBackoff ;
48+ await Task . Delay ( wait ) . ConfigureAwait ( false ) ;
49+
50+ backoff = TimeSpan . FromTicks ( Math . Min ( backoff . Ticks * 2 , MaxBackoff . Ticks ) ) ;
51+ }
52+ }
53+
54+ private static TimeSpan ? GetRetryAfter ( HttpResponseMessage response )
55+ {
56+ var header = response . Headers . RetryAfter ;
57+ if ( header is null )
58+ return null ;
59+ if ( header . Delta is { } delta && delta > TimeSpan . Zero )
60+ return delta ;
61+ if ( header . Date is { } when )
62+ {
63+ var delay = when - DateTimeOffset . UtcNow ;
64+ return delay > TimeSpan . Zero ? delay : null ;
65+ }
66+ return null ;
2767 }
2868}
0 commit comments