aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs')
-rw-r--r--MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs268
1 files changed, 199 insertions, 69 deletions
diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
index 5702af6d1..1f82c5eb0 100644
--- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
+++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
@@ -1,4 +1,6 @@
-using MediaBrowser.Common.Configuration;
+using System.Net.Sockets;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
@@ -12,7 +14,6 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Net.Cache;
-using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -40,7 +41,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
- private readonly IConfigurationManager _config;
/// <summary>
/// Initializes a new instance of the <see cref="HttpClientManager" /> class.
@@ -51,7 +51,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
/// <exception cref="System.ArgumentNullException">appPaths
/// or
/// logger</exception>
- public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem, IConfigurationManager config)
+ public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem)
{
if (appPaths == null)
{
@@ -64,7 +64,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
_logger = logger;
_fileSystem = fileSystem;
- _config = config;
_appPaths = appPaths;
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
@@ -122,7 +121,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
}
request.Method = method;
- request.Timeout = 20000;
+ request.Timeout = options.TimeoutMs;
if (!string.IsNullOrEmpty(options.Host))
{
@@ -134,9 +133,22 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
request.Referer = options.Referer;
}
+ //request.ServicePoint.BindIPEndPointDelegate = BindIPEndPointCallback;
+
return request;
}
+ private static IPEndPoint BindIPEndPointCallback(ServicePoint servicePoint, IPEndPoint remoteEndPoint, int retryCount)
+ {
+ // Prefer local ipv4
+ if (remoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ return new IPEndPoint(IPAddress.IPv6Any, 0);
+ }
+
+ return new IPEndPoint(IPAddress.Any, 0);
+ }
+
private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options)
{
foreach (var header in options.RequestHeaders.ToList())
@@ -157,6 +169,20 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
}
/// <summary>
+ /// The _semaphoreLocks
+ /// </summary>
+ private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks = new ConcurrentDictionary<string, SemaphoreSlim>(StringComparer.OrdinalIgnoreCase);
+ /// <summary>
+ /// Gets the lock.
+ /// </summary>
+ /// <param name="url">The filename.</param>
+ /// <returns>System.Object.</returns>
+ private SemaphoreSlim GetLock(string url)
+ {
+ return _semaphoreLocks.GetOrAdd(url, key => new SemaphoreSlim(1, 1));
+ }
+
+ /// <summary>
/// Gets the response internal.
/// </summary>
/// <param name="options">The options.</param>
@@ -216,6 +242,110 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
/// </exception>
public async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod)
{
+ HttpResponseInfo response;
+
+ if (options.CacheMode == CacheMode.None)
+ {
+ response = await SendAsyncInternal(options, httpMethod).ConfigureAwait(false);
+ return response;
+ }
+
+ var url = options.Url;
+ var urlHash = url.ToLower().GetMD5().ToString("N");
+ var semaphore = GetLock(url);
+
+ var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash);
+
+ response = await GetCachedResponse(responseCachePath, options.CacheLength, url).ConfigureAwait(false);
+ if (response != null)
+ {
+ return response;
+ }
+
+ await semaphore.WaitAsync(options.CancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ response = await GetCachedResponse(responseCachePath, options.CacheLength, url).ConfigureAwait(false);
+ if (response != null)
+ {
+ return response;
+ }
+
+ response = await SendAsyncInternal(options, httpMethod).ConfigureAwait(false);
+
+ if (response.StatusCode == HttpStatusCode.OK)
+ {
+ await CacheResponse(response, responseCachePath).ConfigureAwait(false);
+ }
+
+ return response;
+ }
+ finally
+ {
+ semaphore.Release();
+ }
+ }
+
+ private async Task<HttpResponseInfo> GetCachedResponse(string responseCachePath, TimeSpan cacheLength, string url)
+ {
+ try
+ {
+ if (_fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
+ {
+ using (var stream = _fileSystem.GetFileStream(responseCachePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
+ {
+ var memoryStream = new MemoryStream();
+
+ await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
+ memoryStream.Position = 0;
+
+ return new HttpResponseInfo
+ {
+ ResponseUrl = url,
+ Content = memoryStream,
+ StatusCode = HttpStatusCode.OK,
+ Headers = new NameValueCollection(),
+ ContentLength = memoryStream.Length
+ };
+ }
+ }
+ }
+ catch (FileNotFoundException)
+ {
+
+ }
+ catch (DirectoryNotFoundException)
+ {
+
+ }
+
+ return null;
+ }
+
+ private async Task CacheResponse(HttpResponseInfo response, string responseCachePath)
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(responseCachePath));
+
+ using (var responseStream = response.Content)
+ {
+ using (var fileStream = _fileSystem.GetFileStream(responseCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
+ {
+ var memoryStream = new MemoryStream();
+
+ await responseStream.CopyToAsync(memoryStream).ConfigureAwait(false);
+
+ memoryStream.Position = 0;
+ await memoryStream.CopyToAsync(fileStream).ConfigureAwait(false);
+
+ memoryStream.Position = 0;
+ response.Content = memoryStream;
+ }
+ }
+ }
+
+ private async Task<HttpResponseInfo> SendAsyncInternal(HttpRequestOptions options, string httpMethod)
+ {
ValidateParams(options);
options.CancellationToken.ThrowIfCancellationRequested();
@@ -236,11 +366,11 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
!string.IsNullOrEmpty(options.RequestContent) ||
string.Equals(httpMethod, "post", StringComparison.OrdinalIgnoreCase))
{
- var bytes = options.RequestContentBytes ??
+ var bytes = options.RequestContentBytes ??
Encoding.UTF8.GetBytes(options.RequestContent ?? string.Empty);
httpWebRequest.ContentType = options.RequestContentType ?? "application/x-www-form-urlencoded";
-
+
httpWebRequest.ContentLength = bytes.Length;
httpWebRequest.GetRequestStream().Write(bytes, 0, bytes.Length);
}
@@ -271,7 +401,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
if (!options.BufferContent)
{
- var response = await httpWebRequest.GetResponseAsync().ConfigureAwait(false);
+ var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false);
var httpResponse = (HttpWebResponse)response;
@@ -279,10 +409,10 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
options.CancellationToken.ThrowIfCancellationRequested();
- return GetResponseInfo(httpResponse, httpResponse.GetResponseStream(), GetContentLength(httpResponse));
+ return GetResponseInfo(httpResponse, httpResponse.GetResponseStream(), GetContentLength(httpResponse), httpResponse);
}
- using (var response = await httpWebRequest.GetResponseAsync().ConfigureAwait(false))
+ using (var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false))
{
var httpResponse = (HttpWebResponse)response;
@@ -298,7 +428,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
memoryStream.Position = 0;
- return GetResponseInfo(httpResponse, memoryStream, memoryStream.Length);
+ return GetResponseInfo(httpResponse, memoryStream, memoryStream.Length, null);
}
}
}
@@ -315,21 +445,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
throw exception;
}
- catch (HttpRequestException ex)
- {
- _logger.ErrorException("Error getting response from " + options.Url, ex);
-
- throw new HttpException(ex.Message, ex);
- }
- catch (WebException ex)
- {
- throw GetException(ex, options);
- }
catch (Exception ex)
{
- _logger.ErrorException("Error getting response from " + options.Url, ex);
-
- throw;
+ throw GetException(ex, options);
}
finally
{
@@ -361,9 +479,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
return exception;
}
- private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, Stream content, long? contentLength)
+ private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, Stream content, long? contentLength, IDisposable disposable)
{
- return new HttpResponseInfo
+ return new HttpResponseInfo(disposable)
{
Content = content,
@@ -517,21 +635,10 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
return GetResponseInfo(httpResponse, tempFile, contentLength);
}
}
- catch (OperationCanceledException ex)
- {
- throw GetTempFileException(ex, options, tempFile);
- }
- catch (HttpRequestException ex)
- {
- throw GetTempFileException(ex, options, tempFile);
- }
- catch (WebException ex)
- {
- throw GetTempFileException(ex, options, tempFile);
- }
catch (Exception ex)
{
- throw GetTempFileException(ex, options, tempFile);
+ DeleteTempFile(tempFile);
+ throw GetException(ex, options);
}
finally
{
@@ -556,44 +663,25 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
- /// <summary>
- /// Handles the temp file exception.
- /// </summary>
- /// <param name="ex">The ex.</param>
- /// <param name="options">The options.</param>
- /// <param name="tempFile">The temp file.</param>
- /// <returns>Task.</returns>
- /// <exception cref="HttpException"></exception>
- private Exception GetTempFileException(Exception ex, HttpRequestOptions options, string tempFile)
+ private Exception GetException(Exception ex, HttpRequestOptions options)
{
- var operationCanceledException = ex as OperationCanceledException;
+ var webException = ex as WebException
+ ?? ex.InnerException as WebException;
- if (operationCanceledException != null)
+ if (webException != null)
{
- // Cleanup
- DeleteTempFile(tempFile);
-
- return GetCancellationException(options.Url, options.CancellationToken, operationCanceledException);
+ return GetException(webException, options);
}
- _logger.ErrorException("Error getting response from " + options.Url, ex);
-
- // Cleanup
- DeleteTempFile(tempFile);
+ var operationCanceledException = ex as OperationCanceledException
+ ?? ex.InnerException as OperationCanceledException;
- var httpRequestException = ex as HttpRequestException;
-
- if (httpRequestException != null)
+ if (operationCanceledException != null)
{
- return new HttpException(ex.Message, ex);
+ return GetCancellationException(options.Url, options.CancellationToken, operationCanceledException);
}
- var webException = ex as WebException;
-
- if (webException != null)
- {
- throw GetException(webException, options);
- }
+ _logger.ErrorException("Error getting response from " + options.Url, ex);
return ex;
}
@@ -724,5 +812,47 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
{
return Post(url, postData, null, cancellationToken);
}
+
+ private Task<WebResponse> GetResponseAsync(WebRequest request, TimeSpan timeout)
+ {
+ var taskCompletion = new TaskCompletionSource<WebResponse>();
+
+ Task<WebResponse> asyncTask = Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse, request.EndGetResponse, null);
+
+ ThreadPool.RegisterWaitForSingleObject((asyncTask as IAsyncResult).AsyncWaitHandle, TimeoutCallback, request, timeout, true);
+ asyncTask.ContinueWith(task =>
+ {
+ taskCompletion.TrySetResult(task.Result);
+
+ }, TaskContinuationOptions.NotOnFaulted);
+
+ // Handle errors
+ asyncTask.ContinueWith(task =>
+ {
+ if (task.Exception != null)
+ {
+ taskCompletion.TrySetException(task.Exception);
+ }
+ else
+ {
+ taskCompletion.TrySetException(new List<Exception>());
+ }
+
+ }, TaskContinuationOptions.OnlyOnFaulted);
+
+ return taskCompletion.Task;
+ }
+
+ private static void TimeoutCallback(object state, bool timedOut)
+ {
+ if (timedOut)
+ {
+ WebRequest request = (WebRequest)state;
+ if (state != null)
+ {
+ request.Abort();
+ }
+ }
+ }
}
}