diff options
| author | LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> | 2019-08-06 00:26:19 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-08-06 00:26:19 -0700 |
| commit | 984e415c66cbd995d12ea95a3a9d3e2561ce4869 (patch) | |
| tree | 1799942f3836641786c0e29249801bdb46aac0f4 /Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs | |
| parent | c2667f99f4d50f4f7d9bbeec50e8491e52468962 (diff) | |
| parent | 89f592687ee7ae7f0e0fffd884dbf2890476410a (diff) | |
Merge pull request #5 from jellyfin/master
Merge up to latest master
Diffstat (limited to 'Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs')
| -rw-r--r-- | Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs | 534 |
1 files changed, 126 insertions, 408 deletions
diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 1bebdd163..331b5e29d 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Net; -using System.Net.Cache; +using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -24,30 +23,24 @@ namespace Emby.Server.Implementations.HttpClientManager /// </summary> public class HttpClientManager : IHttpClient { - /// <summary> - /// When one request to a host times out, we'll ban all other requests for this period of time, to prevent scans from stalling - /// </summary> - private const int TimeoutSeconds = 30; - - /// <summary> - /// The _logger - /// </summary> private readonly ILogger _logger; - - /// <summary> - /// The _app paths - /// </summary> private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; private readonly Func<string> _defaultUserAgentFn; /// <summary> + /// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests. + /// DON'T dispose it after use. + /// </summary> + /// <value>The HTTP clients.</value> + private readonly ConcurrentDictionary<string, HttpClient> _httpClients = new ConcurrentDictionary<string, HttpClient>(); + + /// <summary> /// Initializes a new instance of the <see cref="HttpClientManager" /> class. /// </summary> public HttpClientManager( IApplicationPaths appPaths, - ILoggerFactory loggerFactory, + ILogger<HttpClientManager> logger, IFileSystem fileSystem, Func<string> defaultUserAgentFn) { @@ -55,46 +48,33 @@ namespace Emby.Server.Implementations.HttpClientManager { throw new ArgumentNullException(nameof(appPaths)); } - if (loggerFactory == null) + + if (logger == null) { - throw new ArgumentNullException(nameof(loggerFactory)); + throw new ArgumentNullException(nameof(logger)); } - _logger = loggerFactory.CreateLogger("HttpClient"); + _logger = logger; _fileSystem = fileSystem; _appPaths = appPaths; _defaultUserAgentFn = defaultUserAgentFn; - - // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c - ServicePointManager.Expect100Continue = false; } /// <summary> - /// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests. - /// DON'T dispose it after use. - /// </summary> - /// <value>The HTTP clients.</value> - private readonly ConcurrentDictionary<string, HttpClientInfo> _httpClients = new ConcurrentDictionary<string, HttpClientInfo>(); - - /// <summary> - /// Gets + /// Gets the correct http client for the given url. /// </summary> - /// <param name="host">The host.</param> - /// <param name="enableHttpCompression">if set to <c>true</c> [enable HTTP compression].</param> + /// <param name="url">The url.</param> /// <returns>HttpClient.</returns> - /// <exception cref="ArgumentNullException">host</exception> - private HttpClientInfo GetHttpClient(string host, bool enableHttpCompression) + private HttpClient GetHttpClient(string url) { - if (string.IsNullOrEmpty(host)) - { - throw new ArgumentNullException(nameof(host)); - } - - var key = host + enableHttpCompression; + var key = GetHostFromUrl(url); if (!_httpClients.TryGetValue(key, out var client)) { - client = new HttpClientInfo(); + client = new HttpClient() + { + BaseAddress = new Uri(url) + }; _httpClients.TryAdd(key, client); } @@ -102,119 +82,84 @@ namespace Emby.Server.Implementations.HttpClientManager return client; } - private WebRequest GetRequest(HttpRequestOptions options, string method) + private HttpRequestMessage GetRequestMessage(HttpRequestOptions options, HttpMethod method) { string url = options.Url; - var uriAddress = new Uri(url); string userInfo = uriAddress.UserInfo; if (!string.IsNullOrWhiteSpace(userInfo)) { - _logger.LogInformation("Found userInfo in url: {0} ... url: {1}", userInfo, url); - url = url.Replace(userInfo + "@", string.Empty); + _logger.LogWarning("Found userInfo in url: {0} ... url: {1}", userInfo, url); + url = url.Replace(userInfo + '@', string.Empty); } - var request = WebRequest.Create(url); - - if (request is HttpWebRequest httpWebRequest) - { - AddRequestHeaders(httpWebRequest, options); - - if (options.EnableHttpCompression) - { - httpWebRequest.AutomaticDecompression = DecompressionMethods.Deflate; - if (options.DecompressionMethod.HasValue - && options.DecompressionMethod.Value == CompressionMethod.Gzip) - { - httpWebRequest.AutomaticDecompression = DecompressionMethods.GZip; - } - } - else - { - httpWebRequest.AutomaticDecompression = DecompressionMethods.None; - } - - httpWebRequest.KeepAlive = options.EnableKeepAlive; + var request = new HttpRequestMessage(method, url); - if (!string.IsNullOrEmpty(options.Host)) - { - httpWebRequest.Host = options.Host; - } + AddRequestHeaders(request, options); - if (!string.IsNullOrEmpty(options.Referer)) - { - httpWebRequest.Referer = options.Referer; - } + switch (options.DecompressionMethod) + { + case CompressionMethod.Deflate | CompressionMethod.Gzip: + request.Headers.Add(HeaderNames.AcceptEncoding, new[] { "gzip", "deflate" }); + break; + case CompressionMethod.Deflate: + request.Headers.Add(HeaderNames.AcceptEncoding, "deflate"); + break; + case CompressionMethod.Gzip: + request.Headers.Add(HeaderNames.AcceptEncoding, "gzip"); + break; + default: + break; } - request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); + if (options.EnableKeepAlive) + { + request.Headers.Add(HeaderNames.Connection, "Keep-Alive"); + } - request.Method = method; - request.Timeout = options.TimeoutMs; + //request.Headers.Add(HeaderNames.CacheControl, "no-cache"); + /* if (!string.IsNullOrWhiteSpace(userInfo)) { var parts = userInfo.Split(':'); if (parts.Length == 2) { - request.Credentials = GetCredential(url, parts[0], parts[1]); - // TODO: .net core ?? - request.PreAuthenticate = true; + request.Headers.Add(HeaderNames., GetCredential(url, parts[0], parts[1]); } } + */ return request; } - private static CredentialCache GetCredential(string url, string username, string password) - { - //ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3; - var credentialCache = new CredentialCache(); - credentialCache.Add(new Uri(url), "Basic", new NetworkCredential(username, password)); - return credentialCache; - } - - private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options) + private void AddRequestHeaders(HttpRequestMessage request, HttpRequestOptions options) { var hasUserAgent = false; foreach (var header in options.RequestHeaders) { - if (string.Equals(header.Key, HeaderNames.Accept, StringComparison.OrdinalIgnoreCase)) - { - request.Accept = header.Value; - } - else if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase)) { - SetUserAgent(request, header.Value); hasUserAgent = true; } - else - { - request.Headers.Set(header.Key, header.Value); - } + + request.Headers.Add(header.Key, header.Value); } if (!hasUserAgent && options.EnableDefaultUserAgent) { - SetUserAgent(request, _defaultUserAgentFn()); + request.Headers.Add(HeaderNames.UserAgent, _defaultUserAgentFn()); } } - private static void SetUserAgent(HttpWebRequest request, string userAgent) - { - request.UserAgent = userAgent; - } - /// <summary> /// Gets the response internal. /// </summary> /// <param name="options">The options.</param> /// <returns>Task{HttpResponseInfo}.</returns> public Task<HttpResponseInfo> GetResponse(HttpRequestOptions options) - { - return SendAsync(options, "GET"); - } + => SendAsync(options, HttpMethod.Get); /// <summary> /// Performs a GET request and returns the resulting stream @@ -233,9 +178,16 @@ namespace Emby.Server.Implementations.HttpClientManager /// <param name="options">The options.</param> /// <param name="httpMethod">The HTTP method.</param> /// <returns>Task{HttpResponseInfo}.</returns> - /// <exception cref="HttpException"> - /// </exception> - public async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod) + public Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod) + => SendAsync(options, new HttpMethod(httpMethod)); + + /// <summary> + /// send as an asynchronous operation. + /// </summary> + /// <param name="options">The options.</param> + /// <param name="httpMethod">The HTTP method.</param> + /// <returns>Task{HttpResponseInfo}.</returns> + public async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, HttpMethod httpMethod) { if (options.CacheMode == CacheMode.None) { @@ -294,186 +246,66 @@ namespace Emby.Server.Implementations.HttpClientManager } } - private async Task<HttpResponseInfo> SendAsyncInternal(HttpRequestOptions options, string httpMethod) + private async Task<HttpResponseInfo> SendAsyncInternal(HttpRequestOptions options, HttpMethod httpMethod) { ValidateParams(options); options.CancellationToken.ThrowIfCancellationRequested(); - var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression); - - if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < TimeoutSeconds) - { - throw new HttpException(string.Format("Cancelling connection to {0} due to a previous timeout.", options.Url)) - { - IsTimedOut = true - }; - } + var client = GetHttpClient(options.Url); - var httpWebRequest = GetRequest(options, httpMethod); + var httpWebRequest = GetRequestMessage(options, httpMethod); - if (options.RequestContentBytes != null || - !string.IsNullOrEmpty(options.RequestContent) || - string.Equals(httpMethod, "post", StringComparison.OrdinalIgnoreCase)) + if (options.RequestContentBytes != null + || !string.IsNullOrEmpty(options.RequestContent) + || httpMethod == HttpMethod.Post) { - try - { - var bytes = options.RequestContentBytes ?? Encoding.UTF8.GetBytes(options.RequestContent ?? string.Empty); - - var contentType = options.RequestContentType ?? "application/x-www-form-urlencoded"; - - if (options.AppendCharsetToMimeType) - { - contentType = contentType.TrimEnd(';') + "; charset=\"utf-8\""; - } - - httpWebRequest.ContentType = contentType; - (await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length); - } - catch (Exception ex) + if (options.RequestContentBytes != null) { - throw new HttpException(ex.Message) { IsTimedOut = true }; + httpWebRequest.Content = new ByteArrayContent(options.RequestContentBytes); } - } - - if (options.ResourcePool != null) - { - await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false); - } - - if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < TimeoutSeconds) - { - options.ResourcePool?.Release(); - - throw new HttpException($"Connection to {options.Url} timed out") { IsTimedOut = true }; - } - - if (options.LogRequest) - { - if (options.LogRequestAsDebug) + else if (options.RequestContent != null) { - _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url); + httpWebRequest.Content = new StringContent( + options.RequestContent, + null, + options.RequestContentType); } else { - _logger.LogInformation("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url); + httpWebRequest.Content = new ByteArrayContent(Array.Empty<byte>()); } } - try - { - options.CancellationToken.ThrowIfCancellationRequested(); - - if (!options.BufferContent) - { - var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false); - - var httpResponse = (HttpWebResponse)response; - - EnsureSuccessStatusCode(client, httpResponse, options); - - options.CancellationToken.ThrowIfCancellationRequested(); - - return GetResponseInfo(httpResponse, httpResponse.GetResponseStream(), GetContentLength(httpResponse), httpResponse); - } - - using (var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false)) - { - var httpResponse = (HttpWebResponse)response; - - EnsureSuccessStatusCode(client, httpResponse, options); - - options.CancellationToken.ThrowIfCancellationRequested(); - - using (var stream = httpResponse.GetResponseStream()) - { - var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream).ConfigureAwait(false); - - memoryStream.Position = 0; - - return GetResponseInfo(httpResponse, memoryStream, memoryStream.Length, null); - } - } - } - catch (OperationCanceledException ex) - { - throw GetCancellationException(options, client, options.CancellationToken, ex); - } - catch (Exception ex) - { - throw GetException(ex, options, client); - } - finally - { - options.ResourcePool?.Release(); - } - } - - private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, Stream content, long? contentLength, IDisposable disposable) - { - var responseInfo = new HttpResponseInfo(disposable) - { - Content = content, - StatusCode = httpResponse.StatusCode, - ContentType = httpResponse.ContentType, - ContentLength = contentLength, - ResponseUrl = httpResponse.ResponseUri.ToString() - }; - - if (httpResponse.Headers != null) + if (options.LogRequest) { - SetHeaders(httpResponse.Headers, responseInfo); + _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToString(), options.Url); } - return responseInfo; - } + options.CancellationToken.ThrowIfCancellationRequested(); - private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, string tempFile, long? contentLength) - { - var responseInfo = new HttpResponseInfo - { - TempFilePath = tempFile, - StatusCode = httpResponse.StatusCode, - ContentType = httpResponse.ContentType, - ContentLength = contentLength - }; + var response = await client.SendAsync( + httpWebRequest, + options.BufferContent ? HttpCompletionOption.ResponseContentRead : HttpCompletionOption.ResponseHeadersRead, + options.CancellationToken).ConfigureAwait(false); - if (httpResponse.Headers != null) - { - SetHeaders(httpResponse.Headers, responseInfo); - } + await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); - return responseInfo; - } + options.CancellationToken.ThrowIfCancellationRequested(); - private static void SetHeaders(WebHeaderCollection headers, HttpResponseInfo responseInfo) - { - foreach (var key in headers.AllKeys) + var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + return new HttpResponseInfo(response.Headers, response.Content.Headers) { - responseInfo.Headers[key] = headers[key]; - } + Content = stream, + StatusCode = response.StatusCode, + ContentType = response.Content.Headers.ContentType?.MediaType, + ContentLength = response.Content.Headers.ContentLength, + ResponseUrl = response.Content.Headers.ContentLocation?.ToString() + }; } public Task<HttpResponseInfo> Post(HttpRequestOptions options) - { - return SendAsync(options, "POST"); - } - - /// <summary> - /// Performs a POST request - /// </summary> - /// <param name="options">The options.</param> - /// <param name="postData">Params to add to the POST data.</param> - /// <returns>stream on success, null on failure</returns> - public async Task<Stream> Post(HttpRequestOptions options, Dictionary<string, string> postData) - { - options.SetPostData(postData); - - var response = await Post(options).ConfigureAwait(false); - - return response.Content; - } + => SendAsync(options, HttpMethod.Post); /// <summary> /// Downloads the contents of a given url into a temporary location @@ -483,7 +315,6 @@ namespace Emby.Server.Implementations.HttpClientManager public async Task<string> GetTempFile(HttpRequestOptions options) { var response = await GetTempFileResponse(options).ConfigureAwait(false); - return response.TempFilePath; } @@ -502,44 +333,28 @@ namespace Emby.Server.Implementations.HttpClientManager options.CancellationToken.ThrowIfCancellationRequested(); - var httpWebRequest = GetRequest(options, "GET"); - - if (options.ResourcePool != null) - { - await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false); - } + var httpWebRequest = GetRequestMessage(options, HttpMethod.Get); options.Progress.Report(0); if (options.LogRequest) { - if (options.LogRequestAsDebug) - { - _logger.LogDebug("HttpClientManager.GetTempFileResponse url: {0}", options.Url); - } - else - { - _logger.LogInformation("HttpClientManager.GetTempFileResponse url: {0}", options.Url); - } + _logger.LogDebug("HttpClientManager.GetTempFileResponse url: {0}", options.Url); } - var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression); + var client = GetHttpClient(options.Url); try { options.CancellationToken.ThrowIfCancellationRequested(); - using (var response = await httpWebRequest.GetResponseAsync().ConfigureAwait(false)) + using (var response = (await client.SendAsync(httpWebRequest, options.CancellationToken).ConfigureAwait(false))) { - var httpResponse = (HttpWebResponse)response; - - EnsureSuccessStatusCode(client, httpResponse, options); + await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); options.CancellationToken.ThrowIfCancellationRequested(); - var contentLength = GetContentLength(httpResponse); - - using (var stream = httpResponse.GetResponseStream()) + using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true)) { await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); @@ -547,35 +362,29 @@ namespace Emby.Server.Implementations.HttpClientManager options.Progress.Report(100); - return GetResponseInfo(httpResponse, tempFile, contentLength); + var responseInfo = new HttpResponseInfo(response.Headers, response.Content.Headers) + { + TempFilePath = tempFile, + StatusCode = response.StatusCode, + ContentType = response.Content.Headers.ContentType?.MediaType, + ContentLength = response.Content.Headers.ContentLength + }; + + return responseInfo; } } catch (Exception ex) { - DeleteTempFile(tempFile); - throw GetException(ex, options, client); - } - finally - { - options.ResourcePool?.Release(); - } - } - - private static long? GetContentLength(HttpWebResponse response) - { - var length = response.ContentLength; + if (File.Exists(tempFile)) + { + File.Delete(tempFile); + } - if (length == 0) - { - return null; + throw GetException(ex, options); } - - return length; } - protected static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - private Exception GetException(Exception ex, HttpRequestOptions options, HttpClientInfo client) + private Exception GetException(Exception ex, HttpRequestOptions options) { if (ex is HttpException) { @@ -589,7 +398,7 @@ namespace Emby.Server.Implementations.HttpClientManager { if (options.LogErrors) { - _logger.LogError(webException, "Error {status} getting response from {url}", webException.Status, options.Url); + _logger.LogError(webException, "Error {Status} getting response from {Url}", webException.Status, options.Url); } var exception = new HttpException(webException.Message, webException); @@ -599,11 +408,6 @@ namespace Emby.Server.Implementations.HttpClientManager if (response != null) { exception.StatusCode = response.StatusCode; - - if ((int)response.StatusCode == 429) - { - client.LastTimeout = DateTime.UtcNow; - } } } @@ -624,29 +428,17 @@ namespace Emby.Server.Implementations.HttpClientManager if (operationCanceledException != null) { - return GetCancellationException(options, client, options.CancellationToken, operationCanceledException); + return GetCancellationException(options, options.CancellationToken, operationCanceledException); } if (options.LogErrors) { - _logger.LogError(ex, "Error getting response from {url}", options.Url); + _logger.LogError(ex, "Error getting response from {Url}", options.Url); } return ex; } - private void DeleteTempFile(string file) - { - try - { - _fileSystem.DeleteFile(file); - } - catch (IOException) - { - // Might not have been created at all. No need to worry. - } - } - private void ValidateParams(HttpRequestOptions options) { if (string.IsNullOrEmpty(options.Url)) @@ -682,11 +474,10 @@ namespace Emby.Server.Implementations.HttpClientManager /// Throws the cancellation exception. /// </summary> /// <param name="options">The options.</param> - /// <param name="client">The client.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="exception">The exception.</param> /// <returns>Exception.</returns> - private Exception GetCancellationException(HttpRequestOptions options, HttpClientInfo client, CancellationToken cancellationToken, OperationCanceledException exception) + private Exception GetCancellationException(HttpRequestOptions options, CancellationToken cancellationToken, OperationCanceledException exception) { // If the HttpClient's timeout is reached, it will cancel the Task internally if (!cancellationToken.IsCancellationRequested) @@ -698,8 +489,6 @@ namespace Emby.Server.Implementations.HttpClientManager _logger.LogError(msg); } - client.LastTimeout = DateTime.UtcNow; - // Throw an HttpException so that the caller doesn't think it was cancelled by user code return new HttpException(msg, exception) { @@ -710,91 +499,20 @@ namespace Emby.Server.Implementations.HttpClientManager return exception; } - private void EnsureSuccessStatusCode(HttpClientInfo client, HttpWebResponse response, HttpRequestOptions options) + private async Task EnsureSuccessStatusCode(HttpResponseMessage response, HttpRequestOptions options) { - var statusCode = response.StatusCode; - - var isSuccessful = statusCode >= HttpStatusCode.OK && statusCode <= (HttpStatusCode)299; - - if (isSuccessful) + if (response.IsSuccessStatusCode) { return; } - if (options.LogErrorResponseBody) - { - try - { - using (var stream = response.GetResponseStream()) - { - if (stream != null) - { - using (var reader = new StreamReader(stream)) - { - var msg = reader.ReadToEnd(); - - _logger.LogError(msg); - } - } - } - } - catch - { + var msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + _logger.LogError("HTTP request failed with message: {Message}", msg); - } - } - - throw new HttpException(response.StatusDescription) + throw new HttpException(response.ReasonPhrase) { StatusCode = response.StatusCode }; } - - private static Task<WebResponse> GetResponseAsync(WebRequest request, TimeSpan timeout) - { - var taskCompletion = new TaskCompletionSource<WebResponse>(); - - var asyncTask = Task.Factory.FromAsync(request.BeginGetResponse, request.EndGetResponse, null); - - ThreadPool.RegisterWaitForSingleObject((asyncTask as IAsyncResult).AsyncWaitHandle, TimeoutCallback, request, timeout, true); - var callback = new TaskCallback { taskCompletion = taskCompletion }; - asyncTask.ContinueWith(callback.OnSuccess, TaskContinuationOptions.NotOnFaulted); - - // Handle errors - asyncTask.ContinueWith(callback.OnError, TaskContinuationOptions.OnlyOnFaulted); - - return taskCompletion.Task; - } - - private static void TimeoutCallback(object state, bool timedOut) - { - if (timedOut && state != null) - { - var request = (WebRequest)state; - request.Abort(); - } - } - - private class TaskCallback - { - public TaskCompletionSource<WebResponse> taskCompletion; - - public void OnSuccess(Task<WebResponse> task) - { - taskCompletion.TrySetResult(task.Result); - } - - public void OnError(Task<WebResponse> task) - { - if (task.Exception == null) - { - taskCompletion.TrySetException(Enumerable.Empty<Exception>()); - } - else - { - taskCompletion.TrySetException(task.Exception); - } - } - } } } |
