@@ -13,7 +13,10 @@ The above copyright notice and this permission notice shall be included in all c
1313using System ;
1414using System . Collections . Generic ;
1515using System . IO ;
16+ using System . Linq ;
1617using System . Net ;
18+ using System . Net . Http ;
19+ using System . Threading ;
1720using System . Threading . Tasks ;
1821
1922namespace ExchangeSharp
@@ -26,66 +29,59 @@ public sealed class APIRequestMaker : IAPIRequestMaker
2629 {
2730 private readonly IAPIRequestHandler api ;
2831
29- /// <summary>
30- /// Proxy for http requests, reads from HTTP_PROXY environment var by default
31- /// You can also set via code if you like
32- /// </summary>
33- public static WebProxy ? Proxy { get ; set ; }
34-
35- /// <summary>
36- /// Static constructor
37- /// </summary>
38- static APIRequestMaker ( )
39- {
40- var httpProxy = Environment . GetEnvironmentVariable ( "http_proxy" ) ;
41- httpProxy ??= Environment . GetEnvironmentVariable ( "HTTP_PROXY" ) ;
42-
43- if ( string . IsNullOrWhiteSpace ( httpProxy ) )
44- {
45- return ;
46- }
32+ /// <summary>
33+ /// Proxy for http requests, reads from HTTP_PROXY environment var by default
34+ /// You can also set via code if you like
35+ /// </summary>
36+ private static readonly HttpClientHandler ClientHandler = new HttpClientHandler ( ) ;
37+ public static IWebProxy ? Proxy
38+ {
39+ get => ClientHandler . Proxy ;
40+ set
41+ {
42+ ClientHandler . Proxy = value ;
43+ }
44+ }
45+ public static readonly HttpClient Client = new HttpClient ( ClientHandler ) ;
46+
47+ /// <summary>
48+ /// Static constructor
49+ /// </summary>
50+ static APIRequestMaker ( )
51+ {
52+ var httpProxy = Environment . GetEnvironmentVariable ( "http_proxy" ) ;
53+ httpProxy ??= Environment . GetEnvironmentVariable ( "HTTP_PROXY" ) ;
54+
55+ if ( ! string . IsNullOrWhiteSpace ( httpProxy ) )
56+ {
57+ var uri = new Uri ( httpProxy ) ;
58+ Proxy = new WebProxy ( uri ) ;
59+ }
4760
48- var uri = new Uri ( httpProxy ) ;
49- Proxy = new WebProxy ( uri ) ;
61+ Client . DefaultRequestHeaders . ConnectionClose = true ; // disable keep-alive
62+ Client . Timeout = Timeout . InfiniteTimeSpan ; // we handle timeout by ourselves
63+ }
5064
51- }
52- internal class InternalHttpWebRequest : IHttpWebRequest
65+ internal class InternalHttpWebRequest : IHttpWebRequest
5366 {
54- internal readonly HttpWebRequest Request ;
67+ internal readonly HttpRequestMessage Request ;
68+ internal HttpResponseMessage ? Response ;
69+ private string ? contentType ;
5570
56- public InternalHttpWebRequest ( Uri fullUri )
71+ public InternalHttpWebRequest ( string method , Uri fullUri )
5772 {
58- Request = ( HttpWebRequest . Create ( fullUri ) as HttpWebRequest ?? throw new NullReferenceException ( "Failed to create HttpWebRequest" ) ) ;
59- Request . Proxy = Proxy ;
60- Request . KeepAlive = false ;
73+ Request = new HttpRequestMessage ( new HttpMethod ( method ) , fullUri ) ;
6174 }
6275
6376 public void AddHeader ( string header , string value )
6477 {
65- switch ( header . ToStringLowerInvariant ( ) )
78+ switch ( header . ToLowerInvariant ( ) )
6679 {
6780 case "content-type" :
68- Request . ContentType = value ;
69- break ;
70-
71- case "content-length" :
72- Request . ContentLength = value . ConvertInvariant < long > ( ) ;
73- break ;
74-
75- case "user-agent" :
76- Request . UserAgent = value ;
77- break ;
78-
79- case "accept" :
80- Request . Accept = value ;
81+ contentType = value ;
8182 break ;
82-
83- case "connection" :
84- Request . Connection = value ;
85- break ;
86-
8783 default :
88- Request . Headers [ header ] = value ;
84+ Request . Headers . Add ( header , value ) ;
8985 break ;
9086 }
9187 }
@@ -97,55 +93,53 @@ public Uri RequestUri
9793
9894 public string Method
9995 {
100- get { return Request . Method ; }
101- set { Request . Method = value ; }
96+ get { return Request . Method . Method ; }
97+ set { Request . Method = new HttpMethod ( value ) ; }
10298 }
10399
104- public int Timeout
105- {
106- get { return Request . Timeout ; }
107- set { Request . Timeout = value ; }
108- }
100+ public int Timeout { get ; set ; }
109101
110102 public int ReadWriteTimeout
111103 {
112- get { return Request . ReadWriteTimeout ; }
113- set { Request . ReadWriteTimeout = value ; }
104+ get => Timeout ;
105+ set => Timeout = value ;
114106 }
115107
116- public async Task WriteAllAsync ( byte [ ] data , int index , int length )
108+
109+ public Task WriteAllAsync ( byte [ ] data , int index , int length )
117110 {
118- using ( Stream stream = await Request . GetRequestStreamAsync ( ) )
119- {
120- await stream . WriteAsync ( data , 0 , data . Length ) ;
121- }
111+ Request . Content = new ByteArrayContent ( data , index , length ) ;
112+ Request . Content . Headers . Add ( "content-type" , contentType ) ;
113+ return Task . CompletedTask ;
122114 }
123115 }
124116
125117 internal class InternalHttpWebResponse : IHttpWebResponse
126118 {
127- private readonly HttpWebResponse response ;
119+ private readonly HttpResponseMessage response ;
128120
129- public InternalHttpWebResponse ( HttpWebResponse response )
121+ public InternalHttpWebResponse ( HttpResponseMessage response )
130122 {
131123 this . response = response ;
132124 }
133125
134126 public IReadOnlyList < string > GetHeader ( string name )
135127 {
136- return response . Headers . GetValues ( name ) ?? CryptoUtility . EmptyStringArray ;
137- }
128+ try
129+ {
130+ return response . Headers . GetValues ( name ) . ToArray ( ) ; // throws InvalidOperationException when name not exist
131+ }
132+ catch ( Exception )
133+ {
134+ return CryptoUtility . EmptyStringArray ;
135+ }
136+ }
138137
139138 public Dictionary < string , IReadOnlyList < string > > Headers
140139 {
141140 get
142141 {
143- Dictionary < string , IReadOnlyList < string > > headers = new Dictionary < string , IReadOnlyList < string > > ( ) ;
144- foreach ( var header in response . Headers . AllKeys )
145- {
146- headers [ header ] = new List < string > ( response . Headers . GetValues ( header ) ) ;
147- }
148- return headers ;
142+ return response . Headers . ToDictionary ( x => x . Key , x => ( IReadOnlyList < string > ) x . Value . ToArray ( ) ) ;
149143 }
150144 }
151145 }
@@ -178,58 +172,50 @@ public async Task<string> MakeRequestAsync(string url, string? baseUrl = null, D
178172 url = "/" + url ;
179173 }
180174
175+ // prepare the request
181176 string fullUrl = ( baseUrl ?? api . BaseUrl ) + url ;
182177 method ??= api . RequestMethod ;
183178 Uri uri = api . ProcessRequestUrl ( new UriBuilder ( fullUrl ) , payload , method ) ;
184- InternalHttpWebRequest request = new InternalHttpWebRequest ( uri )
185- {
186- Method = method
187- } ;
179+ var request = new InternalHttpWebRequest ( method , uri ) ;
188180 request . AddHeader ( "accept-language" , "en-US,en;q=0.5" ) ;
189181 request . AddHeader ( "content-type" , api . RequestContentType ) ;
190182 request . AddHeader ( "user-agent" , BaseAPI . RequestUserAgent ) ;
191- request . Timeout = request . ReadWriteTimeout = ( int ) api . RequestTimeout . TotalMilliseconds ;
183+ request . Timeout = ( int ) api . RequestTimeout . TotalMilliseconds ;
192184 await api . ProcessRequestAsync ( request , payload ) ;
193- HttpWebResponse ? response = null ;
194- string responseString ;
195185
186+ // send the request
187+ var response = request . Response ;
188+ string responseString ;
189+ using var cancel = new CancellationTokenSource ( request . Timeout ) ;
196190 try
197191 {
198- try
192+ RequestStateChanged ? . Invoke ( this , RequestMakerState . Begin , uri . AbsoluteUri ) ; // when start make a request we send the uri, this helps developers to track the http requests.
193+ response = await Client . SendAsync ( request . Request , cancel . Token ) ;
194+ if ( response == null )
199195 {
200- RequestStateChanged ? . Invoke ( this , RequestMakerState . Begin , uri . AbsoluteUri ) ; // when start make a request we send the uri, this helps developers to track the http requests.
201- response = await request . Request . GetResponseAsync ( ) as HttpWebResponse ;
202- if ( response == null )
203- {
204- throw new APIException ( "Unknown response from server" ) ;
205- }
206- }
207- catch ( WebException we )
208- {
209- response = we . Response as HttpWebResponse ;
210- if ( response == null )
211- {
212- throw new APIException ( we . Message ?? "Unknown response from server" ) ;
213- }
196+ throw new APIException ( "Unknown response from server" ) ;
214197 }
215- using ( Stream responseStream = response . GetResponseStream ( ) )
216- using ( StreamReader responseStreamReader = new StreamReader ( responseStream ) )
217- responseString = responseStreamReader . ReadToEnd ( ) ;
198+ responseString = await response . Content . ReadAsStringAsync ( ) ;
218199
219200 if ( response . StatusCode != HttpStatusCode . OK && response . StatusCode != HttpStatusCode . Created )
220201 {
221- // 404 maybe return empty responseString
222- if ( string . IsNullOrWhiteSpace ( responseString ) )
223- {
202+ // 404 maybe return empty responseString
203+ if ( string . IsNullOrWhiteSpace ( responseString ) )
204+ {
224205 throw new APIException ( string . Format ( "{0} - {1}" , response . StatusCode . ConvertInvariant < int > ( ) , response . StatusCode ) ) ;
225- }
206+ }
226207
227- throw new APIException ( responseString ) ;
208+ throw new APIException ( responseString ) ;
228209 }
229210
230- api . ProcessResponse ( new InternalHttpWebResponse ( response ) ) ;
211+ api . ProcessResponse ( new InternalHttpWebResponse ( response ) ) ;
231212 RequestStateChanged ? . Invoke ( this , RequestMakerState . Finished , responseString ) ;
232213 }
214+ catch ( OperationCanceledException ex ) when ( cancel . IsCancellationRequested )
215+ {
216+ RequestStateChanged ? . Invoke ( this , RequestMakerState . Error , ex ) ;
217+ throw new TimeoutException ( "APIRequest timeout" , ex ) ;
218+ }
233219 catch ( Exception ex )
234220 {
235221 RequestStateChanged ? . Invoke ( this , RequestMakerState . Error , ex ) ;
0 commit comments