@@ -13,7 +13,10 @@ The above copyright notice and this permission notice shall be included in all c
13
13
using System ;
14
14
using System . Collections . Generic ;
15
15
using System . IO ;
16
+ using System . Linq ;
16
17
using System . Net ;
18
+ using System . Net . Http ;
19
+ using System . Threading ;
17
20
using System . Threading . Tasks ;
18
21
19
22
namespace ExchangeSharp
@@ -26,66 +29,59 @@ public sealed class APIRequestMaker : IAPIRequestMaker
26
29
{
27
30
private readonly IAPIRequestHandler api ;
28
31
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
+ }
47
60
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
+ }
50
64
51
- }
52
- internal class InternalHttpWebRequest : IHttpWebRequest
65
+ internal class InternalHttpWebRequest : IHttpWebRequest
53
66
{
54
- internal readonly HttpWebRequest Request ;
67
+ internal readonly HttpRequestMessage Request ;
68
+ internal HttpResponseMessage ? Response ;
69
+ private string ? contentType ;
55
70
56
- public InternalHttpWebRequest ( Uri fullUri )
71
+ public InternalHttpWebRequest ( string method , Uri fullUri )
57
72
{
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 ) ;
61
74
}
62
75
63
76
public void AddHeader ( string header , string value )
64
77
{
65
- switch ( header . ToStringLowerInvariant ( ) )
78
+ switch ( header . ToLowerInvariant ( ) )
66
79
{
67
80
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 ;
81
82
break ;
82
-
83
- case "connection" :
84
- Request . Connection = value ;
85
- break ;
86
-
87
83
default :
88
- Request . Headers [ header ] = value ;
84
+ Request . Headers . Add ( header , value ) ;
89
85
break ;
90
86
}
91
87
}
@@ -97,55 +93,53 @@ public Uri RequestUri
97
93
98
94
public string Method
99
95
{
100
- get { return Request . Method ; }
101
- set { Request . Method = value ; }
96
+ get { return Request . Method . Method ; }
97
+ set { Request . Method = new HttpMethod ( value ) ; }
102
98
}
103
99
104
- public int Timeout
105
- {
106
- get { return Request . Timeout ; }
107
- set { Request . Timeout = value ; }
108
- }
100
+ public int Timeout { get ; set ; }
109
101
110
102
public int ReadWriteTimeout
111
103
{
112
- get { return Request . ReadWriteTimeout ; }
113
- set { Request . ReadWriteTimeout = value ; }
104
+ get => Timeout ;
105
+ set => Timeout = value ;
114
106
}
115
107
116
- public async Task WriteAllAsync ( byte [ ] data , int index , int length )
108
+
109
+ public Task WriteAllAsync ( byte [ ] data , int index , int length )
117
110
{
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 ;
122
114
}
123
115
}
124
116
125
117
internal class InternalHttpWebResponse : IHttpWebResponse
126
118
{
127
- private readonly HttpWebResponse response ;
119
+ private readonly HttpResponseMessage response ;
128
120
129
- public InternalHttpWebResponse ( HttpWebResponse response )
121
+ public InternalHttpWebResponse ( HttpResponseMessage response )
130
122
{
131
123
this . response = response ;
132
124
}
133
125
134
126
public IReadOnlyList < string > GetHeader ( string name )
135
127
{
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
+ }
138
137
139
138
public Dictionary < string , IReadOnlyList < string > > Headers
140
139
{
141
140
get
142
141
{
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 ( ) ) ;
149
143
}
150
144
}
151
145
}
@@ -178,58 +172,50 @@ public async Task<string> MakeRequestAsync(string url, string? baseUrl = null, D
178
172
url = "/" + url ;
179
173
}
180
174
175
+ // prepare the request
181
176
string fullUrl = ( baseUrl ?? api . BaseUrl ) + url ;
182
177
method ??= api . RequestMethod ;
183
178
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 ) ;
188
180
request . AddHeader ( "accept-language" , "en-US,en;q=0.5" ) ;
189
181
request . AddHeader ( "content-type" , api . RequestContentType ) ;
190
182
request . AddHeader ( "user-agent" , BaseAPI . RequestUserAgent ) ;
191
- request . Timeout = request . ReadWriteTimeout = ( int ) api . RequestTimeout . TotalMilliseconds ;
183
+ request . Timeout = ( int ) api . RequestTimeout . TotalMilliseconds ;
192
184
await api . ProcessRequestAsync ( request , payload ) ;
193
- HttpWebResponse ? response = null ;
194
- string responseString ;
195
185
186
+ // send the request
187
+ var response = request . Response ;
188
+ string responseString ;
189
+ using var cancel = new CancellationTokenSource ( request . Timeout ) ;
196
190
try
197
191
{
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 )
199
195
{
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" ) ;
214
197
}
215
- using ( Stream responseStream = response . GetResponseStream ( ) )
216
- using ( StreamReader responseStreamReader = new StreamReader ( responseStream ) )
217
- responseString = responseStreamReader . ReadToEnd ( ) ;
198
+ responseString = await response . Content . ReadAsStringAsync ( ) ;
218
199
219
200
if ( response . StatusCode != HttpStatusCode . OK && response . StatusCode != HttpStatusCode . Created )
220
201
{
221
- // 404 maybe return empty responseString
222
- if ( string . IsNullOrWhiteSpace ( responseString ) )
223
- {
202
+ // 404 maybe return empty responseString
203
+ if ( string . IsNullOrWhiteSpace ( responseString ) )
204
+ {
224
205
throw new APIException ( string . Format ( "{0} - {1}" , response . StatusCode . ConvertInvariant < int > ( ) , response . StatusCode ) ) ;
225
- }
206
+ }
226
207
227
- throw new APIException ( responseString ) ;
208
+ throw new APIException ( responseString ) ;
228
209
}
229
210
230
- api . ProcessResponse ( new InternalHttpWebResponse ( response ) ) ;
211
+ api . ProcessResponse ( new InternalHttpWebResponse ( response ) ) ;
231
212
RequestStateChanged ? . Invoke ( this , RequestMakerState . Finished , responseString ) ;
232
213
}
214
+ catch ( OperationCanceledException ex ) when ( cancel . IsCancellationRequested )
215
+ {
216
+ RequestStateChanged ? . Invoke ( this , RequestMakerState . Error , ex ) ;
217
+ throw new TimeoutException ( "APIRequest timeout" , ex ) ;
218
+ }
233
219
catch ( Exception ex )
234
220
{
235
221
RequestStateChanged ? . Invoke ( this , RequestMakerState . Error , ex ) ;
0 commit comments