Skip to content

Commit babff91

Browse files
szmcdullRaNDom
andauthored
change System.Net.HttpWebRequest to System.Net.Http.HttpClient (#607)
* change APIRequestMaker from using System.Net.HttpWebRequest to using System.Net.Http.HttpClient, as HttpWebRequest in netcore does not implement Timeout * use a shared HttpClient * fix response.GetHeader may throw and some warnings Co-authored-by: RaNDom <[email protected]>
1 parent dffb3b1 commit babff91

File tree

1 file changed

+88
-102
lines changed

1 file changed

+88
-102
lines changed

src/ExchangeSharp/API/Common/APIRequestMaker.cs

Lines changed: 88 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ The above copyright notice and this permission notice shall be included in all c
1313
using System;
1414
using System.Collections.Generic;
1515
using System.IO;
16+
using System.Linq;
1617
using System.Net;
18+
using System.Net.Http;
19+
using System.Threading;
1720
using System.Threading.Tasks;
1821

1922
namespace 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

Comments
 (0)