// ------------------------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. // ------------------------------------------------------------------------------ namespace Microsoft.Graph { using System; using System.Linq; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; /// /// An implementation using standard .NET libraries. /// public class SimpleHttpProvider : IHttpProvider { internal readonly HttpClient httpClient; /// /// Constructs a new . /// /// Custom http client to be used for making requests /// A serializer for serializing and deserializing JSON objects. public SimpleHttpProvider(HttpClient httpClient, ISerializer serializer = null) { // Null authProvider addresses https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/605. // We're reenabling this functionality that allowed setting a null authprovider. this.httpClient = httpClient ?? GraphClientFactory.Create(authenticationProvider: null); Serializer = serializer ?? new Serializer(); } /// /// Gets a serializer for serializing and deserializing JSON objects. /// public ISerializer Serializer { get; private set; } /// /// Gets or sets the overall request timeout. /// public TimeSpan OverallTimeout { get => this.httpClient.Timeout; set { try { this.httpClient.Timeout = value; } catch (InvalidOperationException exception) { throw new ServiceException( new Error { Code = ErrorConstants.Codes.NotAllowed, Message = ErrorConstants.Messages.OverallTimeoutCannotBeSet, }, exception); } } } /// /// Sends the request. /// /// The to send. /// The . public Task SendAsync(HttpRequestMessage request) { return this.SendAsync(request, HttpCompletionOption.ResponseContentRead, CancellationToken.None); } /// /// Sends the request. /// /// The to send. /// The to pass to the on send. /// The for the request. /// The . public async Task SendAsync( HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken) { var response = await this.SendRequestAsync(request, completionOption, cancellationToken).ConfigureAwait(false); // check if the response is of a successful nature. if (!response.IsSuccessStatusCode) { using (response) { if (null != response.Content) { await response.Content.LoadIntoBufferAsync().ConfigureAwait(false); } var errorResponse = await this.ConvertErrorResponseAsync(response).ConfigureAwait(false); Error error; if (errorResponse == null || errorResponse.Error == null) { if (response.StatusCode == HttpStatusCode.NotFound) { error = new Error { Code = ErrorConstants.Codes.ItemNotFound }; } else { error = new Error { Code = ErrorConstants.Codes.GeneralException, Message = ErrorConstants.Messages.UnexpectedExceptionResponse, }; } } else { error = errorResponse.Error; } if (string.IsNullOrEmpty(error.ThrowSite)) { if (response.Headers.TryGetValues(CoreConstants.Headers.ThrowSiteHeaderName, out var throwSiteValues)) { error.ThrowSite = throwSiteValues.FirstOrDefault(); } } if (string.IsNullOrEmpty(error.ClientRequestId)) { if (response.Headers.TryGetValues(CoreConstants.Headers.ClientRequestId, out var clientRequestId)) { error.ClientRequestId = clientRequestId.FirstOrDefault(); } } if (response.Content?.Headers.ContentType.MediaType == "application/json") { string rawResponseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false); throw new ServiceException(error, response.Headers, response.StatusCode, rawResponseBody); } else { // Pass through the response headers and status code to the ServiceException. // System.Net.HttpStatusCode does not support RFC 6585, Additional HTTP Status Codes. // Throttling status code 429 is in RFC 6586. The status code 429 will be passed through. throw new ServiceException(error, response.Headers, response.StatusCode); } } } return response; } /// /// Disposes the HttpClient and HttpClientHandler instances. /// public void Dispose() { httpClient?.Dispose(); } /// /// Converts the into an object; /// /// The to convert. /// The object. private async Task ConvertErrorResponseAsync(HttpResponseMessage response) { try { using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) { return this.Serializer.DeserializeObject(responseStream); } } catch (Exception) { // If there's an exception deserializing the error response return null and throw a generic // ServiceException later. return null; } } private async Task SendRequestAsync( HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken) { try { return await this.httpClient.SendAsync(request, completionOption, cancellationToken).ConfigureAwait(false); } catch (TaskCanceledException exception) { throw new ServiceException( new Error { Code = ErrorConstants.Codes.Timeout, Message = ErrorConstants.Messages.RequestTimedOut, }, exception); } catch (ServiceException) { throw; } catch (Exception exception) { throw new ServiceException( new Error { Code = ErrorConstants.Codes.GeneralException, Message = ErrorConstants.Messages.UnexpectedExceptionOnSend, }, exception); } } } }