// ------------------------------------------------------------------------------ // 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.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; /// /// An implementation using standard .NET libraries. /// public class HttpProvider : IHttpProvider { internal bool disposeHandler; internal HttpClient httpClient; internal HttpMessageHandler httpMessageHandler; /// /// Constructs a new . /// /// A serializer for serializing and deserializing JSON objects. public HttpProvider(ISerializer serializer = null) : this((HttpMessageHandler)null, true, serializer) { } /// /// Constructs a new . /// /// An HTTP client handler to pass to the for sending requests. /// Whether or not to dispose the client handler on Dispose(). /// A serializer for serializing and deserializing JSON objects. /// /// By default, HttpProvider disables automatic redirects and handles redirects to preserve authentication headers. If providing /// an to the constructor and enabling automatic redirects this could cause issues with authentication /// over the redirect. /// public HttpProvider(HttpClientHandler httpClientHandler, bool disposeHandler, ISerializer serializer = null) : this((HttpMessageHandler)httpClientHandler, disposeHandler, serializer) { } /// /// Constructs a new . /// /// An HTTP message handler to pass to the for sending requests. /// Whether or not to dispose the client handler on Dispose(). /// A serializer for serializing and deserializing JSON objects. public HttpProvider(HttpMessageHandler httpMessageHandler, bool disposeHandler, ISerializer serializer) { this.disposeHandler = disposeHandler; this.httpMessageHandler = httpMessageHandler; this.Serializer = serializer ?? new Serializer(); // NOTE: Override our pipeline when a httpMessageHandler is provided - httpMessageHandler can implement custom pipeline. // This check won't be needed once we re-write the HttpProvider to work with GraphClientFactory. if (this.httpMessageHandler == null) { this.httpMessageHandler = GraphClientFactory.GetNativePlatformHttpHandler(); this.httpClient = GraphClientFactory.Create(authenticationProvider: null, version: "v1.0", nationalCloud: GraphClientFactory.Global_Cloud, finalHandler: this.httpMessageHandler); } else { this.httpClient = new HttpClient(this.httpMessageHandler, this.disposeHandler); } this.httpClient.SetFeatureFlag(FeatureFlag.DefaultHttpProvider); } /// /// Gets or sets the cache control header for requests; /// public CacheControlHeaderValue CacheControlHeader { get { return this.httpClient.DefaultRequestHeaders.CacheControl; } set { this.httpClient.DefaultRequestHeaders.CacheControl = value; } } /// /// Gets or sets the overall request timeout. /// public TimeSpan OverallTimeout { get { return 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); } } } /// /// Gets a serializer for serializing and deserializing JSON objects. /// public ISerializer Serializer { get; private set; } /// /// Disposes the HttpClient and HttpClientHandler instances. /// public void Dispose() { if (this.httpClient != null) { this.httpClient.Dispose(); } } /// /// 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); 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 = null; if (errorResponse == null || errorResponse.Error == null) { if (response != null && 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)) { IEnumerable throwsiteValues; if (response.Headers.TryGetValues(CoreConstants.Headers.ThrowSiteHeaderName, out throwsiteValues)) { error.ThrowSite = throwsiteValues.FirstOrDefault(); } } if (string.IsNullOrEmpty(error.ClientRequestId)) { IEnumerable clientRequestId; if (response.Headers.TryGetValues(CoreConstants.Headers.ClientRequestId, out 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; } internal 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 exception) { throw exception; } catch (Exception exception) { throw new ServiceException( new Error { Code = ErrorConstants.Codes.GeneralException, Message = ErrorConstants.Messages.UnexpectedExceptionOnSend, }, exception); } } /// /// 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; } } } }