2020-06-29 04:44:35 +02:00
using System ;
2020-01-18 13:48:04 +01:00
using System.Collections.Generic ;
using System.Net ;
using System.Net.Http ;
2020-06-28 10:55:27 +02:00
using System.Threading ;
2020-01-18 13:48:04 +01:00
using System.Threading.Tasks ;
using ExchangeSharp ;
namespace BTCPayServer.Services.Rates
{
internal class HttpClientRequestMaker : IAPIRequestMaker
{
2021-12-27 05:46:31 +01:00
#nullable enable
internal class InternalHttpWebRequest : IHttpWebRequest
2020-01-18 13:48:04 +01:00
{
2021-12-27 05:46:31 +01:00
internal readonly HttpRequestMessage Request ;
private string? contentType ;
2020-01-18 13:48:04 +01:00
2021-12-27 05:46:31 +01:00
public InternalHttpWebRequest ( string method , Uri fullUri )
2020-01-18 13:48:04 +01:00
{
2021-12-27 05:46:31 +01:00
Request = new HttpRequestMessage ( new HttpMethod ( method ) , fullUri ) ;
2020-01-18 13:48:04 +01:00
}
public void AddHeader ( string header , string value )
{
2021-12-27 05:46:31 +01:00
switch ( header . ToLowerInvariant ( ) )
2020-01-18 13:48:04 +01:00
{
case "content-type" :
2021-12-27 05:46:31 +01:00
contentType = value ;
2020-01-18 13:48:04 +01:00
break ;
default :
2021-12-27 05:46:31 +01:00
Request . Headers . TryAddWithoutValidation ( header , value ) ;
2020-01-18 13:48:04 +01:00
break ;
}
}
2021-12-27 05:46:31 +01:00
public Uri RequestUri
2020-01-18 13:48:04 +01:00
{
2021-12-27 05:46:31 +01:00
get { return Request . RequestUri ! ; }
2020-01-18 13:48:04 +01:00
}
2021-12-27 05:46:31 +01:00
public string Method
2020-01-18 13:48:04 +01:00
{
2021-12-27 05:46:31 +01:00
get { return Request . Method . Method ; }
set { Request . Method = new HttpMethod ( value ) ; }
2020-01-18 13:48:04 +01:00
}
2021-12-27 05:46:31 +01:00
public int Timeout { get ; set ; }
public int ReadWriteTimeout
2020-01-18 13:48:04 +01:00
{
2021-12-27 05:46:31 +01:00
get = > Timeout ;
set = > Timeout = value ;
}
public Task WriteAllAsync ( byte [ ] data , int index , int length )
{
Request . Content = new ByteArrayContent ( data , index , length ) ;
Request . Content . Headers . Add ( "content-type" , contentType ) ;
return Task . CompletedTask ;
2020-01-18 13:48:04 +01:00
}
}
2021-12-27 05:46:31 +01:00
#nullable restore
2020-01-18 13:48:04 +01:00
class InternalHttpWebResponse : IHttpWebResponse
{
public InternalHttpWebResponse ( HttpResponseMessage httpResponseMessage )
{
var headers = new Dictionary < string , List < string > > ( ) ;
foreach ( var h in httpResponseMessage . Headers )
{
if ( ! headers . TryGetValue ( h . Key , out var list ) )
{
list = new List < string > ( ) ;
headers . Add ( h . Key , list ) ;
}
list . AddRange ( h . Value ) ;
}
Headers = new Dictionary < string , IReadOnlyList < string > > ( headers . Count ) ;
foreach ( var item in headers )
{
Headers . Add ( item . Key , item . Value . AsReadOnly ( ) ) ;
}
}
public Dictionary < string , IReadOnlyList < string > > Headers { get ; }
2020-06-29 05:07:48 +02:00
static readonly IReadOnlyList < string > Empty = new List < string > ( ) . AsReadOnly ( ) ;
2020-01-18 13:48:04 +01:00
public IReadOnlyList < string > GetHeader ( string name )
{
Headers . TryGetValue ( name , out var list ) ;
return list ? ? Empty ;
}
}
private readonly IAPIRequestHandler api ;
private readonly HttpClient _httpClient ;
private readonly CancellationToken _cancellationToken ;
public HttpClientRequestMaker ( IAPIRequestHandler api , HttpClient httpClient , CancellationToken cancellationToken )
{
2021-12-28 09:39:54 +01:00
ArgumentNullException . ThrowIfNull ( api ) ;
ArgumentNullException . ThrowIfNull ( httpClient ) ;
2020-01-18 13:48:04 +01:00
this . api = api ;
_httpClient = httpClient ;
_cancellationToken = cancellationToken ;
}
public Action < IAPIRequestMaker , RequestMakerState , object > RequestStateChanged
{
get ;
set ;
}
public async Task < string > MakeRequestAsync ( string url , string baseUrl = null , Dictionary < string , object > payload = null , string method = null )
{
await default ( SynchronizationContextRemover ) ;
await api . RateLimit . WaitToProceedAsync ( ) ;
if ( url [ 0 ] ! = '/' )
{
url = "/" + url ;
}
2021-12-27 05:46:31 +01:00
// prepare the request
string fullUrl = ( baseUrl ? ? api . BaseUrl ) + url ;
method ? ? = api . RequestMethod ;
Uri uri = api . ProcessRequestUrl ( new UriBuilder ( fullUrl ) , payload , method ) ;
InternalHttpWebRequest request = new InternalHttpWebRequest ( method , uri ) ;
request . AddHeader ( "accept-language" , "en-US,en;q=0.5" ) ;
2020-01-18 13:48:04 +01:00
request . AddHeader ( "content-type" , api . RequestContentType ) ;
2021-12-27 05:46:31 +01:00
request . AddHeader ( "user-agent" , "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36" ) ;
request . Timeout = ( int ) api . RequestTimeout . TotalMilliseconds ;
2020-01-18 13:48:04 +01:00
await api . ProcessRequestAsync ( request , payload ) ;
2021-12-27 05:46:31 +01:00
// send the request
2021-12-27 06:26:03 +01:00
HttpResponseMessage response = null ;
2021-12-27 05:46:31 +01:00
string responseString ;
using var cancel = new CancellationTokenSource ( request . Timeout ) ;
2020-01-18 13:48:04 +01:00
try
{
2021-12-27 05:46:31 +01:00
RequestStateChanged ? . Invoke ( this , RequestMakerState . Begin , uri . AbsoluteUri ) ; // when start make a request we send the uri, this helps developers to track the http requests.
response = await _httpClient . SendAsync ( request . Request , cancel . Token ) ;
if ( response = = null )
2020-01-18 13:48:04 +01:00
{
2021-12-27 05:46:31 +01:00
throw new APIException ( "Unknown response from server" ) ;
}
responseString = await response . Content . ReadAsStringAsync ( ) ;
if ( response . StatusCode ! = HttpStatusCode . OK & & response . StatusCode ! = HttpStatusCode . Created )
{
// 404 maybe return empty responseString
if ( string . IsNullOrWhiteSpace ( responseString ) )
2020-01-18 13:48:04 +01:00
{
2021-12-27 05:46:31 +01:00
throw new APIException ( string . Format ( "{0} - {1}" , response . StatusCode . ConvertInvariant < int > ( ) , response . StatusCode ) ) ;
2020-01-18 13:48:04 +01:00
}
2021-12-27 05:46:31 +01:00
throw new APIException ( responseString ) ;
2020-01-18 13:48:04 +01:00
}
2020-02-25 23:08:57 +01:00
2021-12-27 05:46:31 +01:00
api . ProcessResponse ( new InternalHttpWebResponse ( response ) ) ;
RequestStateChanged ? . Invoke ( this , RequestMakerState . Finished , responseString ) ;
}
catch ( OperationCanceledException ex ) when ( cancel . IsCancellationRequested )
{
RequestStateChanged ? . Invoke ( this , RequestMakerState . Error , ex ) ;
throw new TimeoutException ( "APIRequest timeout" , ex ) ;
2020-01-18 13:48:04 +01:00
}
2021-12-27 05:46:31 +01:00
catch ( Exception ex )
2020-01-18 13:48:04 +01:00
{
2021-12-27 05:46:31 +01:00
RequestStateChanged ? . Invoke ( this , RequestMakerState . Error , ex ) ;
2020-01-18 13:48:04 +01:00
throw ;
}
2021-12-27 05:46:31 +01:00
finally
{
response ? . Dispose ( ) ;
}
return responseString ;
2020-01-18 13:48:04 +01:00
}
}
}