1- using System . Text ;
21using Microsoft . Extensions . Logging ;
32using Microsoft . Extensions . Options ;
43using Octans . Core . Http ;
54
65namespace Octans . Core . Downloaders ;
76
87internal sealed class DownloaderService (
9- IHttpClientFactory clientFactory ,
108 IDownloaderFactory downloaderFactory ,
11- IDownloadRequestHeaderProvider requestHeaderProvider ,
9+ IHttpDocumentFetcher documentFetcher ,
1210 IOptions < DownloaderResolverOptions > options ,
1311 ILogger < DownloaderService > logger )
1412{
@@ -19,10 +17,6 @@ public async Task<IReadOnlyList<Uri>> ResolveAsync(Uri uri, CancellationToken ca
1917
2018 var downloaders = await downloaderFactory . GetDownloaders ( ) ;
2119
22- #pragma warning disable CA2000
23- var client = clientFactory . CreateClient ( "DownloadClient" ) ;
24- #pragma warning restore CA2000
25-
2620 string ? raw = null ;
2721
2822 foreach ( var downloader in downloaders )
@@ -45,7 +39,7 @@ public async Task<IReadOnlyList<Uri>> ResolveAsync(Uri uri, CancellationToken ca
4539
4640 if ( raw is null )
4741 {
48- raw = await TryRunAsync ( downloader , "fetch_html" , ( ) => GetStringAsync ( client , uri , resolverToken ) ) ;
42+ raw = await TryRunAsync ( downloader , "fetch_html" , ( ) => documentFetcher . GetStringAsync ( uri , resolverToken ) ) ;
4943 if ( raw is null )
5044 {
5145 return [ ] ;
@@ -68,7 +62,7 @@ public async Task<IReadOnlyList<Uri>> ResolveAsync(Uri uri, CancellationToken ca
6862 content = await TryRunAsync (
6963 downloader ,
7064 "fetch_gallery_html" ,
71- ( ) => GetStringAsync ( client , galleryUrl , resolverToken ) ) ;
65+ ( ) => documentFetcher . GetStringAsync ( galleryUrl , resolverToken ) ) ;
7266 if ( content is null )
7367 {
7468 continue ;
@@ -92,76 +86,6 @@ public async Task<IReadOnlyList<Uri>> ResolveAsync(Uri uri, CancellationToken ca
9286 return [ ] ;
9387 }
9488
95- private async Task < string > GetStringAsync ( HttpClient client , Uri uri , CancellationToken cancellationToken )
96- {
97- using var request = new HttpRequestMessage ( HttpMethod . Get , uri ) ;
98- requestHeaderProvider . ApplyHeaders ( request ) ;
99-
100- using var response = await client . SendAsync (
101- request ,
102- HttpCompletionOption . ResponseHeadersRead ,
103- cancellationToken ) ;
104- response . EnsureSuccessStatusCode ( ) ;
105- return await ReadBoundedStringAsync ( response , uri , cancellationToken ) ;
106- }
107-
108- private async Task < string > ReadBoundedStringAsync (
109- HttpResponseMessage response ,
110- Uri uri ,
111- CancellationToken cancellationToken )
112- {
113- var maxResponseBytes = options . Value . MaxResponseBytes ;
114- var reportedBytes = response . Content . Headers . ContentLength ;
115- if ( maxResponseBytes > 0 && reportedBytes > maxResponseBytes )
116- {
117- throw new DownloaderContractException (
118- $ "Downloader response from { uri . Host } reported { reportedBytes } bytes, exceeding the configured { maxResponseBytes } byte limit.") ;
119- }
120-
121- await using var stream = await response . Content . ReadAsStreamAsync ( cancellationToken ) ;
122- using var buffer = new MemoryStream ( ) ;
123- var bytes = new byte [ 81920 ] ;
124- long totalBytes = 0 ;
125-
126- while ( true )
127- {
128- var bytesRead = await stream . ReadAsync ( bytes , cancellationToken ) ;
129- if ( bytesRead <= 0 )
130- {
131- break ;
132- }
133-
134- if ( maxResponseBytes > 0 && totalBytes > maxResponseBytes - bytesRead )
135- {
136- throw new DownloaderContractException (
137- $ "Downloader response from { uri . Host } exceeded the configured { maxResponseBytes } byte limit.") ;
138- }
139-
140- await buffer . WriteAsync ( bytes . AsMemory ( 0 , bytesRead ) , cancellationToken ) ;
141- totalBytes += bytesRead ;
142- }
143-
144- return GetResponseEncoding ( response ) . GetString ( buffer . ToArray ( ) ) ;
145- }
146-
147- private static Encoding GetResponseEncoding ( HttpResponseMessage response )
148- {
149- var charset = response . Content . Headers . ContentType ? . CharSet ;
150- if ( string . IsNullOrWhiteSpace ( charset ) )
151- {
152- return Encoding . UTF8 ;
153- }
154-
155- try
156- {
157- return Encoding . GetEncoding ( charset ) ;
158- }
159- catch ( ArgumentException )
160- {
161- return Encoding . UTF8 ;
162- }
163- }
164-
16589 private CancellationTokenSource CreateOperationTimeout ( CancellationToken cancellationToken )
16690 {
16791 var timeout = options . Value . OperationTimeout ;
@@ -213,6 +137,16 @@ private static Uri CreateHttpUri(string value, string source)
213137 {
214138 return await action ( ) ;
215139 }
140+ catch ( HttpDocumentFetchException ex )
141+ {
142+ logger . LogWarning (
143+ ex ,
144+ "Skipping downloader {DownloaderName} after failure during {DownloaderOperation}: {Message}" ,
145+ GetDownloaderName ( downloader ) ,
146+ operation ,
147+ ex . Message ) ;
148+ return default ;
149+ }
216150 catch ( DownloaderContractException ex )
217151 {
218152 logger . LogWarning (
0 commit comments