// ------------------------------------------------------------------------------ // 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.Reflection; using System.Net.Http.Headers; /// /// GraphClientFactory class to create the HTTP client /// public static class GraphClientFactory { /// The key for the SDK version header. private static readonly string SdkVersionHeaderName = CoreConstants.Headers.SdkVersionHeaderName; /// The version for current assembly. private static Version assemblyVersion = typeof(GraphClientFactory).GetTypeInfo().Assembly.GetName().Version; /// The value for the SDK version header. private static string SdkVersionHeaderValue = string.Format( CoreConstants.Headers.SdkVersionHeaderValueFormatString, "Graph", assemblyVersion.Major, assemblyVersion.Minor, assemblyVersion.Build); /// The default value for the overall request timeout. private static readonly TimeSpan defaultTimeout = TimeSpan.FromSeconds(100); /// Microsoft Graph service national cloud endpoints private static readonly Dictionary cloudList = new Dictionary { { Global_Cloud, "https://graph.microsoft.com" }, { USGOV_Cloud, "https://graph.microsoft.us" }, { China_Cloud, "https://microsoftgraph.chinacloudapi.cn" }, { Germany_Cloud, "https://graph.microsoft.de" } }; /// Global endpoint public const string Global_Cloud = "Global"; /// US_GOV endpoint public const string USGOV_Cloud = "US_GOV"; /// China endpoint public const string China_Cloud = "China"; /// Germany endpoint public const string Germany_Cloud = "Germany"; /// /// Creates a new instance configured with the handlers provided. /// /// The to authenticate requests. /// The graph version to use. /// The national cloud endpoint to use. /// The proxy to be used with created client. /// The last HttpMessageHandler to HTTP calls. /// The default implementation creates a new instance of for each HttpClient. /// public static HttpClient Create( IAuthenticationProvider authenticationProvider, string version = "v1.0", string nationalCloud = Global_Cloud, IWebProxy proxy = null, HttpMessageHandler finalHandler = null) { IList handlers = CreateDefaultHandlers(authenticationProvider); return Create(handlers, version, nationalCloud, proxy, finalHandler); } /// /// Creates a new instance configured with the handlers provided. /// /// The graph version to use. /// The national cloud endpoint to use. /// An ordered list of instances to be invoked as an /// travels from the to the network and an /// travels from the network back to . /// The handlers are invoked in a top-down fashion. That is, the first entry is invoked first for /// an outbound request message but last for an inbound response message. /// The proxy to be used with created client. /// The last HttpMessageHandler to HTTP calls. /// An instance with the configured handlers. public static HttpClient Create( IEnumerable handlers, string version = "v1.0", string nationalCloud = Global_Cloud, IWebProxy proxy = null, HttpMessageHandler finalHandler = null) { if (finalHandler == null) { finalHandler = GetNativePlatformHttpHandler(proxy); } else if ((finalHandler is HttpClientHandler) && (finalHandler as HttpClientHandler).Proxy == null && proxy != null) { (finalHandler as HttpClientHandler).Proxy = proxy; } else if ((finalHandler is HttpClientHandler) && (finalHandler as HttpClientHandler).Proxy != null && proxy != null) { throw new ArgumentException(ErrorConstants.Messages.InvalidProxyArgument); } var pipelineWithFlags = CreatePipelineWithFeatureFlags(handlers, finalHandler); HttpClient client = new HttpClient(pipelineWithFlags.Pipeline); client.DefaultRequestHeaders.Add(SdkVersionHeaderName, SdkVersionHeaderValue); client.SetFeatureFlag(pipelineWithFlags.FeatureFlags); client.Timeout = defaultTimeout; client.BaseAddress = DetermineBaseAddress(nationalCloud, version); client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue { NoCache = true, NoStore = true }; return client; } /// /// Create a default set of middleware for calling Microsoft Graph /// /// The to authenticate requests. /// public static IList CreateDefaultHandlers(IAuthenticationProvider authenticationProvider) { return new List { new AuthenticationHandler(authenticationProvider), new CompressionHandler(), new RetryHandler(), new RedirectHandler() }; } /// /// Creates an instance of an using the instances /// provided by . The resulting pipeline can be used to manually create /// or instances with customized message handlers. /// /// The inner handler represents the destination of the HTTP message channel. /// An ordered list of instances to be invoked as part /// of sending an and receiving an . /// The handlers are invoked in a top-down fashion. That is, the first entry is invoked first for /// an outbound request message but last for an inbound response message. /// The HTTP message channel. public static HttpMessageHandler CreatePipeline(IEnumerable handlers, HttpMessageHandler finalHandler = null) { return CreatePipelineWithFeatureFlags(handlers, finalHandler).Pipeline; } /// /// Creates an instance of an using the instances /// provided by . The resulting pipeline can be used to manually create /// or instances with customized message handlers. /// /// The inner handler represents the destination of the HTTP message channel. /// An ordered list of instances to be invoked as part /// of sending an and receiving an . /// The handlers are invoked in a top-down fashion. That is, the first entry is invoked first for /// an outbound request message but last for an inbound response message. /// A tuple with The HTTP message channel and FeatureFlag for the handlers. internal static (HttpMessageHandler Pipeline, FeatureFlag FeatureFlags) CreatePipelineWithFeatureFlags(IEnumerable handlers, HttpMessageHandler finalHandler = null) { FeatureFlag handlerFlags = FeatureFlag.None; if (finalHandler == null) { finalHandler = GetNativePlatformHttpHandler(); } if (handlers == null) { return (Pipeline: finalHandler, FeatureFlags: handlerFlags); } HttpMessageHandler httpPipeline = finalHandler; IEnumerable reversedHandlers = handlers.Reverse(); HashSet existingHandlerTypes = new HashSet(); foreach (DelegatingHandler handler in reversedHandlers) { if (handler == null) { throw new ArgumentNullException(nameof(handlers), "DelegatingHandler array contains null item."); } #if iOS // Skip CompressionHandler since NSUrlSessionHandler automatically handles decompression on iOS and it can't be turned off. // See issue https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/481 for more details. if (finalHandler.GetType().Equals(typeof(NSUrlSessionHandler)) && handler.GetType().Equals(typeof(CompressionHandler))) { // Skip chaining of CompressionHandler. continue; } #endif // Check for duplicate handler by type. if (!existingHandlerTypes.Add(handler.GetType())) { throw new ArgumentException($"DelegatingHandler array has a duplicate handler. {handler} has a duplicate handler.", "handlers"); } // Existing InnerHandlers on handlers will be overwritten handler.InnerHandler = httpPipeline; httpPipeline = handler; // Register feature flag for the handler. handlerFlags |= GetHandlerFeatureFlag(handler); } return (Pipeline: httpPipeline, FeatureFlags: handlerFlags); } /// /// Gets a platform's native http handler i.e. NSUrlSessionHandler for Xamarin.iOS, AndroidClientHandler for Xamarin.Android and HttpClientHandler for others. /// /// The proxy to be used with created client. /// /// 1. NSUrlSessionHandler for Xamarin.iOS /// 2. AndroidClientHandler for Xamarin.Android. /// 3. HttpClientHandler for other platforms. /// internal static HttpMessageHandler GetNativePlatformHttpHandler(IWebProxy proxy = null) { #if iOS return new NSUrlSessionHandler { AllowAutoRedirect = false }; #elif ANDROID return new Xamarin.Android.Net.AndroidClientHandler { Proxy = proxy, AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.None }; #else return new HttpClientHandler { Proxy = proxy, AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.None }; #endif } /// /// Gets feature flag for the specified handler. /// /// The to get its feaure flag. /// Delegating handler feature flag. private static FeatureFlag GetHandlerFeatureFlag(DelegatingHandler delegatingHandler) { if (delegatingHandler is AuthenticationHandler) return FeatureFlag.AuthHandler; else if (delegatingHandler is CompressionHandler) return FeatureFlag.CompressionHandler; else if (delegatingHandler is RetryHandler) return FeatureFlag.RetryHandler; else if (delegatingHandler is RedirectHandler) return FeatureFlag.RedirectHandler; else return FeatureFlag.None; } private static Uri DetermineBaseAddress(string nationalCloud, string version) { string cloud = ""; if (!cloudList.TryGetValue(nationalCloud, out cloud)) { throw new ArgumentException(String.Format("{0} is an unexpected national cloud.", nationalCloud, "nationalCloud")); } string cloudAddress = $"{cloud}/{version}/"; return new Uri(cloudAddress); } } }