New: Async HttpClient
This commit is contained in:
parent
f62bc59a73
commit
9bd2cbca54
|
@ -1,9 +1,10 @@
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace NzbDrone.Common.Http.Dispatchers
|
namespace NzbDrone.Common.Http.Dispatchers
|
||||||
{
|
{
|
||||||
public interface IHttpDispatcher
|
public interface IHttpDispatcher
|
||||||
{
|
{
|
||||||
HttpResponse GetResponse(HttpRequest request, CookieContainer cookies);
|
Task<HttpResponse> GetResponseAsync(HttpRequest request, CookieContainer cookies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||||
_credentialCache = cacheManager.GetCache<CredentialCache>(typeof(ManagedHttpDispatcher), "credentialcache");
|
_credentialCache = cacheManager.GetCache<CredentialCache>(typeof(ManagedHttpDispatcher), "credentialcache");
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
|
public async Task<HttpResponse> GetResponseAsync(HttpRequest request, CookieContainer cookies)
|
||||||
{
|
{
|
||||||
var requestMessage = new HttpRequestMessage(request.Method, (Uri)request.Url);
|
var requestMessage = new HttpRequestMessage(request.Method, (Uri)request.Url);
|
||||||
requestMessage.Headers.UserAgent.ParseAdd(_userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent));
|
requestMessage.Headers.UserAgent.ParseAdd(_userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent));
|
||||||
|
@ -98,7 +98,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||||
|
|
||||||
var httpClient = GetClient(request.Url);
|
var httpClient = GetClient(request.Url);
|
||||||
|
|
||||||
using var responseMessage = httpClient.Send(requestMessage, HttpCompletionOption.ResponseHeadersRead, cts.Token);
|
using var responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cts.Token);
|
||||||
{
|
{
|
||||||
byte[] data = null;
|
byte[] data = null;
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||||
{
|
{
|
||||||
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
|
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
responseMessage.Content.CopyTo(request.ResponseStream, null, cts.Token);
|
await responseMessage.Content.CopyToAsync(request.ResponseStream, null, cts.Token);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Cache;
|
using NzbDrone.Common.Cache;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
@ -25,6 +26,16 @@ namespace NzbDrone.Common.Http
|
||||||
HttpResponse Post(HttpRequest request);
|
HttpResponse Post(HttpRequest request);
|
||||||
HttpResponse<T> Post<T>(HttpRequest request)
|
HttpResponse<T> Post<T>(HttpRequest request)
|
||||||
where T : new();
|
where T : new();
|
||||||
|
|
||||||
|
Task<HttpResponse> ExecuteAsync(HttpRequest request);
|
||||||
|
Task DownloadFileAsync(string url, string fileName);
|
||||||
|
Task<HttpResponse> GetAsync(HttpRequest request);
|
||||||
|
Task<HttpResponse<T>> GetAsync<T>(HttpRequest request)
|
||||||
|
where T : new();
|
||||||
|
Task<HttpResponse> HeadAsync(HttpRequest request);
|
||||||
|
Task<HttpResponse> PostAsync(HttpRequest request);
|
||||||
|
Task<HttpResponse<T>> PostAsync<T>(HttpRequest request)
|
||||||
|
where T : new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HttpClient : IHttpClient
|
public class HttpClient : IHttpClient
|
||||||
|
@ -52,11 +63,11 @@ namespace NzbDrone.Common.Http
|
||||||
_cookieContainerCache = cacheManager.GetCache<CookieContainer>(typeof(HttpClient));
|
_cookieContainerCache = cacheManager.GetCache<CookieContainer>(typeof(HttpClient));
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResponse Execute(HttpRequest request)
|
public virtual async Task<HttpResponse> ExecuteAsync(HttpRequest request)
|
||||||
{
|
{
|
||||||
var cookieContainer = InitializeRequestCookies(request);
|
var cookieContainer = InitializeRequestCookies(request);
|
||||||
|
|
||||||
var response = ExecuteRequest(request, cookieContainer);
|
var response = await ExecuteRequestAsync(request, cookieContainer);
|
||||||
|
|
||||||
if (request.AllowAutoRedirect && response.HasHttpRedirect)
|
if (request.AllowAutoRedirect && response.HasHttpRedirect)
|
||||||
{
|
{
|
||||||
|
@ -82,7 +93,7 @@ namespace NzbDrone.Common.Http
|
||||||
request.ContentSummary = null;
|
request.ContentSummary = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
response = ExecuteRequest(request, cookieContainer);
|
response = await ExecuteRequestAsync(request, cookieContainer);
|
||||||
}
|
}
|
||||||
while (response.HasHttpRedirect);
|
while (response.HasHttpRedirect);
|
||||||
}
|
}
|
||||||
|
@ -112,6 +123,11 @@ namespace NzbDrone.Common.Http
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HttpResponse Execute(HttpRequest request)
|
||||||
|
{
|
||||||
|
return ExecuteAsync(request).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
private static bool RequestRequiresForceGet(HttpStatusCode statusCode, HttpMethod requestMethod)
|
private static bool RequestRequiresForceGet(HttpStatusCode statusCode, HttpMethod requestMethod)
|
||||||
{
|
{
|
||||||
return statusCode switch
|
return statusCode switch
|
||||||
|
@ -122,7 +138,7 @@ namespace NzbDrone.Common.Http
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpResponse ExecuteRequest(HttpRequest request, CookieContainer cookieContainer)
|
private async Task<HttpResponse> ExecuteRequestAsync(HttpRequest request, CookieContainer cookieContainer)
|
||||||
{
|
{
|
||||||
foreach (var interceptor in _requestInterceptors)
|
foreach (var interceptor in _requestInterceptors)
|
||||||
{
|
{
|
||||||
|
@ -131,14 +147,14 @@ namespace NzbDrone.Common.Http
|
||||||
|
|
||||||
if (request.RateLimit != TimeSpan.Zero)
|
if (request.RateLimit != TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
_rateLimitService.WaitAndPulse(request.Url.Host, request.RateLimitKey, request.RateLimit);
|
await _rateLimitService.WaitAndPulseAsync(request.Url.Host, request.RateLimitKey, request.RateLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Trace(request);
|
_logger.Trace(request);
|
||||||
|
|
||||||
var stopWatch = Stopwatch.StartNew();
|
var stopWatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
var response = _httpDispatcher.GetResponse(request, cookieContainer);
|
var response = await _httpDispatcher.GetResponseAsync(request, cookieContainer);
|
||||||
|
|
||||||
HandleResponseCookies(response, cookieContainer);
|
HandleResponseCookies(response, cookieContainer);
|
||||||
|
|
||||||
|
@ -246,7 +262,7 @@ namespace NzbDrone.Common.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DownloadFile(string url, string fileName)
|
public async Task DownloadFileAsync(string url, string fileName)
|
||||||
{
|
{
|
||||||
var fileNamePart = fileName + ".part";
|
var fileNamePart = fileName + ".part";
|
||||||
|
|
||||||
|
@ -261,12 +277,12 @@ namespace NzbDrone.Common.Http
|
||||||
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName);
|
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName);
|
||||||
|
|
||||||
var stopWatch = Stopwatch.StartNew();
|
var stopWatch = Stopwatch.StartNew();
|
||||||
using (var fileStream = new FileStream(fileNamePart, FileMode.Create, FileAccess.ReadWrite))
|
await using (var fileStream = new FileStream(fileNamePart, FileMode.Create, FileAccess.ReadWrite))
|
||||||
{
|
{
|
||||||
var request = new HttpRequest(url);
|
var request = new HttpRequest(url);
|
||||||
request.AllowAutoRedirect = true;
|
request.AllowAutoRedirect = true;
|
||||||
request.ResponseStream = fileStream;
|
request.ResponseStream = fileStream;
|
||||||
var response = Get(request);
|
var response = await GetAsync(request);
|
||||||
|
|
||||||
if (response.Headers.ContentType != null && response.Headers.ContentType.Contains("text/html"))
|
if (response.Headers.ContentType != null && response.Headers.ContentType.Contains("text/html"))
|
||||||
{
|
{
|
||||||
|
@ -292,38 +308,71 @@ namespace NzbDrone.Common.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResponse Get(HttpRequest request)
|
public void DownloadFile(string url, string fileName)
|
||||||
|
{
|
||||||
|
// https://docs.microsoft.com/en-us/archive/msdn-magazine/2015/july/async-programming-brownfield-async-development#the-thread-pool-hack
|
||||||
|
Task.Run(() => DownloadFileAsync(url, fileName)).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<HttpResponse> GetAsync(HttpRequest request)
|
||||||
{
|
{
|
||||||
request.Method = HttpMethod.Get;
|
request.Method = HttpMethod.Get;
|
||||||
return Execute(request);
|
return ExecuteAsync(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpResponse Get(HttpRequest request)
|
||||||
|
{
|
||||||
|
return Task.Run(() => GetAsync(request)).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<HttpResponse<T>> GetAsync<T>(HttpRequest request)
|
||||||
|
where T : new()
|
||||||
|
{
|
||||||
|
var response = await GetAsync(request);
|
||||||
|
CheckResponseContentType(response);
|
||||||
|
return new HttpResponse<T>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResponse<T> Get<T>(HttpRequest request)
|
public HttpResponse<T> Get<T>(HttpRequest request)
|
||||||
where T : new()
|
where T : new()
|
||||||
{
|
{
|
||||||
var response = Get(request);
|
return Task.Run(() => GetAsync<T>(request)).GetAwaiter().GetResult();
|
||||||
CheckResponseContentType(response);
|
}
|
||||||
return new HttpResponse<T>(response);
|
|
||||||
|
public Task<HttpResponse> HeadAsync(HttpRequest request)
|
||||||
|
{
|
||||||
|
request.Method = HttpMethod.Head;
|
||||||
|
return ExecuteAsync(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResponse Head(HttpRequest request)
|
public HttpResponse Head(HttpRequest request)
|
||||||
{
|
{
|
||||||
request.Method = HttpMethod.Head;
|
return Task.Run(() => HeadAsync(request)).GetAwaiter().GetResult();
|
||||||
return Execute(request);
|
}
|
||||||
|
|
||||||
|
public Task<HttpResponse> PostAsync(HttpRequest request)
|
||||||
|
{
|
||||||
|
request.Method = HttpMethod.Post;
|
||||||
|
return ExecuteAsync(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResponse Post(HttpRequest request)
|
public HttpResponse Post(HttpRequest request)
|
||||||
{
|
{
|
||||||
request.Method = HttpMethod.Post;
|
return Task.Run(() => PostAsync(request)).GetAwaiter().GetResult();
|
||||||
return Execute(request);
|
}
|
||||||
|
|
||||||
|
public async Task<HttpResponse<T>> PostAsync<T>(HttpRequest request)
|
||||||
|
where T : new()
|
||||||
|
{
|
||||||
|
var response = await PostAsync(request);
|
||||||
|
CheckResponseContentType(response);
|
||||||
|
return new HttpResponse<T>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResponse<T> Post<T>(HttpRequest request)
|
public HttpResponse<T> Post<T>(HttpRequest request)
|
||||||
where T : new()
|
where T : new()
|
||||||
{
|
{
|
||||||
var response = Post(request);
|
return Task.Run(() => PostAsync<T>(request)).GetAwaiter().GetResult();
|
||||||
CheckResponseContentType(response);
|
|
||||||
return new HttpResponse<T>(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckResponseContentType(HttpResponse response)
|
private void CheckResponseContentType(HttpResponse response)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Cache;
|
using NzbDrone.Common.Cache;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
@ -10,6 +11,7 @@ namespace NzbDrone.Common.TPL
|
||||||
{
|
{
|
||||||
void WaitAndPulse(string key, TimeSpan interval);
|
void WaitAndPulse(string key, TimeSpan interval);
|
||||||
void WaitAndPulse(string key, string subKey, TimeSpan interval);
|
void WaitAndPulse(string key, string subKey, TimeSpan interval);
|
||||||
|
Task WaitAndPulseAsync(string key, string subKey, TimeSpan interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RateLimitService : IRateLimitService
|
public class RateLimitService : IRateLimitService
|
||||||
|
@ -29,6 +31,28 @@ namespace NzbDrone.Common.TPL
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WaitAndPulse(string key, string subKey, TimeSpan interval)
|
public void WaitAndPulse(string key, string subKey, TimeSpan interval)
|
||||||
|
{
|
||||||
|
var delay = GetDelay(key, subKey, interval);
|
||||||
|
|
||||||
|
if (delay.TotalSeconds > 0.0)
|
||||||
|
{
|
||||||
|
_logger.Trace("Rate Limit triggered, delaying '{0}' for {1:0.000} sec", key, delay.TotalSeconds);
|
||||||
|
System.Threading.Thread.Sleep(delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WaitAndPulseAsync(string key, string subKey, TimeSpan interval)
|
||||||
|
{
|
||||||
|
var delay = GetDelay(key, subKey, interval);
|
||||||
|
|
||||||
|
if (delay.TotalSeconds > 0.0)
|
||||||
|
{
|
||||||
|
_logger.Trace("Rate Limit triggered, delaying '{0}' for {1:0.000} sec", key, delay.TotalSeconds);
|
||||||
|
await Task.Delay(delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TimeSpan GetDelay(string key, string subKey, TimeSpan interval)
|
||||||
{
|
{
|
||||||
var waitUntil = DateTime.UtcNow.Add(interval);
|
var waitUntil = DateTime.UtcNow.Add(interval);
|
||||||
|
|
||||||
|
@ -59,13 +83,7 @@ namespace NzbDrone.Common.TPL
|
||||||
|
|
||||||
waitUntil -= interval;
|
waitUntil -= interval;
|
||||||
|
|
||||||
var delay = waitUntil - DateTime.UtcNow;
|
return waitUntil - DateTime.UtcNow;
|
||||||
|
|
||||||
if (delay.TotalSeconds > 0.0)
|
|
||||||
{
|
|
||||||
_logger.Trace("Rate Limit triggered, delaying '{0}' for {1:0.000} sec", key, delay.TotalSeconds);
|
|
||||||
System.Threading.Thread.Sleep(delay);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue