OSDN Git Service

ITwitterCredentialとアクセス手段ごとの具象クラスを追加
[opentween/open-tween.git] / OpenTween / Connection / TwitterApiConnection.cs
index 5932a24..bfcc99b 100644 (file)
 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
 // Boston, MA 02110-1301, USA.
 
+#nullable enable
+
 using System;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Linq;
 using System.Net;
@@ -28,6 +31,7 @@ using System.Net.Cache;
 using System.Net.Http;
 using System.Runtime.Serialization;
 using System.Text;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Web;
 using OpenTween.Api;
@@ -35,68 +39,158 @@ using OpenTween.Api.DataModel;
 
 namespace OpenTween.Connection
 {
-    public class TwitterApiConnection : IApiConnection, IDisposable
+    public class TwitterApiConnection : IApiConnection, IApiConnectionLegacy, IDisposable
     {
-        public static Uri RestApiBase { get; set; } = new Uri("https://api.twitter.com/1.1/");
+        public static Uri RestApiBase { get; set; } = new("https://api.twitter.com/1.1/");
 
         // SettingCommon.xml の TwitterUrl との互換性のために用意
         public static string RestApiHost
         {
-            get { return RestApiBase.Host; }
-            set { RestApiBase = new Uri($"https://{value}/1.1/"); }
+            get => RestApiBase.Host;
+            set => RestApiBase = new Uri($"https://{value}/1.1/");
         }
 
         public bool IsDisposed { get; private set; } = false;
 
-        public string AccessToken { get; }
-        public string AccessSecret { get; }
+        internal HttpClient Http;
+        internal HttpClient HttpUpload;
+        internal HttpClient HttpStreaming;
+
+        internal ITwitterCredential Credential { get; }
 
-        internal HttpClient http;
+        public TwitterApiConnection()
+            : this(new TwitterCredentialNone())
+        {
+        }
 
-        public TwitterApiConnection(string accessToken, string accessSecret)
+        public TwitterApiConnection(ITwitterCredential credential)
         {
-            this.AccessToken = accessToken;
-            this.AccessSecret = accessSecret;
+            this.Credential = credential;
 
-            this.http = InitializeHttpClient(accessToken, accessSecret);
+            this.InitializeHttpClients();
             Networking.WebProxyChanged += this.Networking_WebProxyChanged;
         }
 
-        public async Task<T> GetAsync<T>(Uri uri, IDictionary<string, string> param, string endpointName)
+        [MemberNotNull(nameof(Http), nameof(HttpUpload), nameof(HttpStreaming))]
+        private void InitializeHttpClients()
+        {
+            this.Http = InitializeHttpClient(this.Credential);
+
+            this.HttpUpload = InitializeHttpClient(this.Credential);
+            this.HttpUpload.Timeout = Networking.UploadImageTimeout;
+
+            this.HttpStreaming = InitializeHttpClient(this.Credential, disableGzip: true);
+            this.HttpStreaming.Timeout = Timeout.InfiniteTimeSpan;
+        }
+
+        public async Task<ApiResponse> SendAsync(IHttpRequest request)
+        {
+            var endpointName = request.EndpointName;
+
+            // レートリミット規制中はAPIリクエストを送信せずに直ちにエラーを発生させる
+            if (endpointName != null)
+                this.ThrowIfRateLimitExceeded(endpointName);
+
+            using var requestMessage = request.CreateMessage(RestApiBase);
+
+            HttpResponseMessage? responseMessage = null;
+            try
+            {
+                responseMessage = await HandleTimeout(
+                    (token) => this.Http.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, token),
+                    Networking.DefaultTimeout
+                );
+
+                if (endpointName != null)
+                    MyCommon.TwitterApiInfo.UpdateFromHeader(responseMessage.Headers, endpointName);
+
+                await TwitterApiConnection.CheckStatusCode(responseMessage)
+                    .ConfigureAwait(false);
+
+                var response = new ApiResponse(responseMessage);
+                responseMessage = null; // responseMessage は ApiResponse で使用するため破棄されないようにする
+
+                return response;
+            }
+            catch (HttpRequestException ex)
+            {
+                throw TwitterApiException.CreateFromException(ex);
+            }
+            catch (OperationCanceledException ex)
+            {
+                throw TwitterApiException.CreateFromException(ex);
+            }
+            finally
+            {
+                responseMessage?.Dispose();
+            }
+        }
+
+        public async Task<T> GetAsync<T>(Uri uri, IDictionary<string, string>? param, string? endpointName)
+        {
+            var request = new GetRequest
+            {
+                RequestUri = uri,
+                Query = param,
+                EndpointName = endpointName,
+            };
+
+            using var response = await this.SendAsync(request)
+                .ConfigureAwait(false);
+
+            return await response.ReadAsJson<T>()
+                .ConfigureAwait(false);
+        }
+
+        /// <summary>
+        /// 指定されたエンドポイントがレートリミット規制中であれば例外を発生させる
+        /// </summary>
+        private void ThrowIfRateLimitExceeded(string endpointName)
+        {
+            var limit = MyCommon.TwitterApiInfo.AccessLimit[endpointName];
+            if (limit == null)
+                return;
+
+            if (limit.AccessLimitRemain == 0 && limit.AccessLimitResetDate > DateTimeUtc.Now)
+            {
+                var error = new TwitterError
+                {
+                    Errors = new[]
+                    {
+                        new TwitterErrorItem { Code = TwitterErrorCode.RateLimit, Message = "" },
+                    },
+                };
+                throw new TwitterApiException(0, error, "");
+            }
+        }
+
+        public Task<Stream> GetStreamAsync(Uri uri, IDictionary<string, string>? param)
+            => this.GetStreamAsync(uri, param, null);
+
+        public async Task<Stream> GetStreamAsync(Uri uri, IDictionary<string, string>? param, string? endpointName)
         {
+            // レートリミット規制中はAPIリクエストを送信せずに直ちにエラーを発生させる
+            if (endpointName != null)
+                this.ThrowIfRateLimitExceeded(endpointName);
+
             var requestUri = new Uri(RestApiBase, uri);
 
             if (param != null)
                 requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param));
 
-            var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
-
             try
             {
-                using (var response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
-                    .ConfigureAwait(false))
-                {
-                    await this.CheckStatusCode(response)
-                        .ConfigureAwait(false);
+                var response = await this.Http.GetAsync(requestUri)
+                    .ConfigureAwait(false);
 
-                    if (endpointName != null)
-                        MyCommon.TwitterApiInfo.UpdateFromHeader(response.Headers, endpointName);
+                if (endpointName != null)
+                    MyCommon.TwitterApiInfo.UpdateFromHeader(response.Headers, endpointName);
 
-                    using (var content = response.Content)
-                    {
-                        var responseText = await content.ReadAsStringAsync()
-                            .ConfigureAwait(false);
-
-                        try
-                        {
-                            return MyCommon.CreateDataFromJson<T>(responseText);
-                        }
-                        catch (SerializationException ex)
-                        {
-                            throw TwitterApiException.CreateFromException(ex, responseText);
-                        }
-                    }
-                }
+                await TwitterApiConnection.CheckStatusCode(response)
+                    .ConfigureAwait(false);
+
+                return await response.Content.ReadAsStreamAsync()
+                    .ConfigureAwait(false);
             }
             catch (HttpRequestException ex)
             {
@@ -108,7 +202,7 @@ namespace OpenTween.Connection
             }
         }
 
-        public async Task<Stream> GetStreamAsync(Uri uri, IDictionary<string, string> param)
+        public async Task<Stream> GetStreamingStreamAsync(Uri uri, IDictionary<string, string>? param)
         {
             var requestUri = new Uri(RestApiBase, uri);
 
@@ -117,7 +211,14 @@ namespace OpenTween.Connection
 
             try
             {
-                return await this.http.GetStreamAsync(requestUri)
+                var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
+                var response = await this.HttpStreaming.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
+                    .ConfigureAwait(false);
+
+                await TwitterApiConnection.CheckStatusCode(response)
+                    .ConfigureAwait(false);
+
+                return await response.Content.ReadAsStreamAsync()
                     .ConfigureAwait(false);
             }
             catch (HttpRequestException ex)
@@ -130,92 +231,215 @@ namespace OpenTween.Connection
             }
         }
 
-        public async Task<LazyJson<T>> PostLazyAsync<T>(Uri uri, IDictionary<string, string> param)
+        public async Task<LazyJson<T>> PostLazyAsync<T>(Uri uri, IDictionary<string, string>? param)
         {
             var requestUri = new Uri(RestApiBase, uri);
             var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
 
-            using (var postContent = new FormUrlEncodedContent(param))
+            using var postContent = new FormUrlEncodedContent(param);
+            request.Content = postContent;
+
+            HttpResponseMessage? response = null;
+            try
             {
-                request.Content = postContent;
+                response = await this.Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
+                    .ConfigureAwait(false);
 
-                HttpResponseMessage response = null;
-                try
-                {
-                    response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
-                        .ConfigureAwait(false);
+                await TwitterApiConnection.CheckStatusCode(response)
+                    .ConfigureAwait(false);
+
+                var result = new LazyJson<T>(response);
+                response = null;
 
-                    await this.CheckStatusCode(response)
-                        .ConfigureAwait(false);
+                return result;
+            }
+            catch (HttpRequestException ex)
+            {
+                throw TwitterApiException.CreateFromException(ex);
+            }
+            catch (OperationCanceledException ex)
+            {
+                throw TwitterApiException.CreateFromException(ex);
+            }
+            finally
+            {
+                response?.Dispose();
+            }
+        }
 
-                    var result = new LazyJson<T>(response);
-                    response = null;
+        public async Task<LazyJson<T>> PostLazyAsync<T>(Uri uri, IDictionary<string, string>? param, IDictionary<string, IMediaItem>? media)
+        {
+            var requestUri = new Uri(RestApiBase, uri);
+            var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
 
-                    return result;
-                }
-                catch (HttpRequestException ex)
-                {
-                    throw TwitterApiException.CreateFromException(ex);
-                }
-                catch (OperationCanceledException ex)
-                {
-                    throw TwitterApiException.CreateFromException(ex);
-                }
-                finally
-                {
-                    response?.Dispose();
-                }
+            using var postContent = new MultipartFormDataContent();
+            if (param != null)
+            {
+                foreach (var (key, value) in param)
+                    postContent.Add(new StringContent(value), key);
+            }
+            if (media != null)
+            {
+                foreach (var (key, value) in media)
+                    postContent.Add(new StreamContent(value.OpenRead()), key, value.Name);
+            }
+
+            request.Content = postContent;
+
+            HttpResponseMessage? response = null;
+            try
+            {
+                response = await this.HttpUpload.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
+                    .ConfigureAwait(false);
+
+                await TwitterApiConnection.CheckStatusCode(response)
+                    .ConfigureAwait(false);
+
+                var result = new LazyJson<T>(response);
+                response = null;
+
+                return result;
+            }
+            catch (HttpRequestException ex)
+            {
+                throw TwitterApiException.CreateFromException(ex);
+            }
+            catch (OperationCanceledException ex)
+            {
+                throw TwitterApiException.CreateFromException(ex);
+            }
+            finally
+            {
+                response?.Dispose();
             }
         }
 
-        public async Task<LazyJson<T>> PostLazyAsync<T>(Uri uri, IDictionary<string, string> param, IDictionary<string, IMediaItem> media)
+        public async Task PostAsync(Uri uri, IDictionary<string, string>? param, IDictionary<string, IMediaItem>? media)
         {
             var requestUri = new Uri(RestApiBase, uri);
             var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
 
-            using (var postContent = new MultipartFormDataContent())
+            using var postContent = new MultipartFormDataContent();
+            if (param != null)
+            {
+                foreach (var (key, value) in param)
+                    postContent.Add(new StringContent(value), key);
+            }
+            if (media != null)
             {
-                foreach (var kv in param)
-                    postContent.Add(new StringContent(kv.Value), kv.Key);
+                foreach (var (key, value) in media)
+                    postContent.Add(new StreamContent(value.OpenRead()), key, value.Name);
+            }
 
-                foreach (var kv in media)
-                    postContent.Add(new StreamContent(kv.Value.OpenRead()), kv.Key, kv.Value.Name);
+            request.Content = postContent;
 
-                request.Content = postContent;
+            try
+            {
+                using var response = await this.HttpUpload.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
+                    .ConfigureAwait(false);
 
-                HttpResponseMessage response = null;
-                try
-                {
-                    response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
-                        .ConfigureAwait(false);
+                await TwitterApiConnection.CheckStatusCode(response)
+                    .ConfigureAwait(false);
+            }
+            catch (HttpRequestException ex)
+            {
+                throw TwitterApiException.CreateFromException(ex);
+            }
+            catch (OperationCanceledException ex)
+            {
+                throw TwitterApiException.CreateFromException(ex);
+            }
+        }
 
-                    await this.CheckStatusCode(response)
-                        .ConfigureAwait(false);
+        public async Task<string> PostJsonAsync(Uri uri, string json)
+        {
+            var request = new PostJsonRequest
+            {
+                RequestUri = uri,
+                JsonString = json,
+            };
 
-                    var result = new LazyJson<T>(response);
-                    response = null;
+            using var response = await this.SendAsync(request)
+                .ConfigureAwait(false);
 
-                    return result;
-                }
-                catch (HttpRequestException ex)
-                {
-                    throw TwitterApiException.CreateFromException(ex);
-                }
-                catch (OperationCanceledException ex)
-                {
-                    throw TwitterApiException.CreateFromException(ex);
-                }
-                finally
+            return await response.ReadAsString()
+                .ConfigureAwait(false);
+        }
+
+        public async Task<LazyJson<T>> PostJsonAsync<T>(Uri uri, string json)
+        {
+            var request = new PostJsonRequest
+            {
+                RequestUri = uri,
+                JsonString = json,
+            };
+
+            using var response = await this.SendAsync(request)
+                .ConfigureAwait(false);
+
+            return response.ReadAsLazyJson<T>();
+        }
+
+        public async Task DeleteAsync(Uri uri)
+        {
+            var requestUri = new Uri(RestApiBase, uri);
+            using var request = new HttpRequestMessage(HttpMethod.Delete, requestUri);
+
+            try
+            {
+                using var response = await this.Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
+                    .ConfigureAwait(false);
+
+                await TwitterApiConnection.CheckStatusCode(response)
+                    .ConfigureAwait(false);
+            }
+            catch (HttpRequestException ex)
+            {
+                throw TwitterApiException.CreateFromException(ex);
+            }
+            catch (OperationCanceledException ex)
+            {
+                throw TwitterApiException.CreateFromException(ex);
+            }
+        }
+
+        public static async Task<T> HandleTimeout<T>(Func<CancellationToken, Task<T>> func, TimeSpan timeout)
+        {
+            using var cts = new CancellationTokenSource();
+            var cancellactionToken = cts.Token;
+
+            var task = Task.Run(() => func(cancellactionToken), cancellactionToken);
+            var timeoutTask = Task.Delay(timeout);
+
+            if (await Task.WhenAny(task, timeoutTask) == timeoutTask)
+            {
+                // タイムアウト
+
+                // キャンセル後のタスクで発生した例外は無視する
+                static async Task IgnoreExceptions(Task task)
                 {
-                    response?.Dispose();
+                    try
+                    {
+                        await task.ConfigureAwait(false);
+                    }
+                    catch
+                    {
+                    }
                 }
+                _ = IgnoreExceptions(task);
+                cts.Cancel();
+
+                throw new OperationCanceledException("Timeout", cancellactionToken);
             }
+
+            return await task;
         }
 
-        protected async Task CheckStatusCode(HttpResponseMessage response)
+        protected static async Task CheckStatusCode(HttpResponseMessage response)
         {
             var statusCode = response.StatusCode;
-            if (statusCode == HttpStatusCode.OK)
+
+            if ((int)statusCode >= 200 && (int)statusCode <= 299)
             {
                 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
                 return;
@@ -249,7 +473,7 @@ namespace OpenTween.Connection
                     Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
                 }
 
-                throw new TwitterApiException(error, responseText);
+                throw new TwitterApiException(statusCode, error, responseText);
             }
             catch (SerializationException)
             {
@@ -257,13 +481,33 @@ namespace OpenTween.Connection
             }
         }
 
-        public OAuthEchoHandler CreateOAuthEchoHandler(Uri authServiceProvider, Uri realm = null)
+        public OAuthEchoHandler CreateOAuthEchoHandler(HttpMessageHandler innerHandler, Uri authServiceProvider, Uri? realm = null)
         {
             var uri = new Uri(RestApiBase, authServiceProvider);
 
-            return OAuthEchoHandler.CreateHandler(Networking.CreateHttpClientHandler(), uri,
-                ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
-                this.AccessToken, this.AccessSecret, realm);
+            if (this.Credential is TwitterCredentialOAuth1 oauthCredential)
+            {
+                return OAuthEchoHandler.CreateHandler(
+                    innerHandler,
+                    uri,
+                    oauthCredential.AppToken.OAuth1ConsumerKey,
+                    oauthCredential.AppToken.OAuth1ConsumerSecret,
+                    oauthCredential.Token,
+                    oauthCredential.TokenSecret,
+                    realm);
+            }
+            else
+            {
+                // MobipictureApi クラス向けの暫定対応
+                return OAuthEchoHandler.CreateHandler(
+                    innerHandler,
+                    uri,
+                    ApiKey.Create(""),
+                    ApiKey.Create(""),
+                    "",
+                    "",
+                    realm);
+            }
         }
 
         public void Dispose()
@@ -282,37 +526,36 @@ namespace OpenTween.Connection
             if (disposing)
             {
                 Networking.WebProxyChanged -= this.Networking_WebProxyChanged;
-                this.http.Dispose();
+                this.Http.Dispose();
+                this.HttpUpload.Dispose();
+                this.HttpStreaming.Dispose();
             }
         }
 
         ~TwitterApiConnection()
-        {
-            this.Dispose(false);
-        }
+            => this.Dispose(false);
 
         private void Networking_WebProxyChanged(object sender, EventArgs e)
-        {
-            this.http = InitializeHttpClient(this.AccessToken, this.AccessSecret);
-        }
+            => this.InitializeHttpClients();
 
-        public static async Task<Tuple<string, string>> GetRequestTokenAsync()
+        public static async Task<TwitterCredentialOAuth1> GetRequestTokenAsync(TwitterAppToken appToken)
         {
+            var emptyCredential = new TwitterCredentialOAuth1(appToken, "", "");
             var param = new Dictionary<string, string>
             {
                 ["oauth_callback"] = "oob",
             };
-            var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/request_token"), param, oauthToken: null)
+            var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/request_token"), param, emptyCredential)
                 .ConfigureAwait(false);
 
-            return Tuple.Create(response["oauth_token"], response["oauth_token_secret"]);
+            return new(appToken, response["oauth_token"], response["oauth_token_secret"]);
         }
 
-        public static Uri GetAuthorizeUri(Tuple<string, string> requestToken, string screenName = null)
+        public static Uri GetAuthorizeUri(TwitterCredentialOAuth1 requestToken, string? screenName = null)
         {
             var param = new Dictionary<string, string>
             {
-                ["oauth_token"] = requestToken.Item1,
+                ["oauth_token"] = requestToken.Token,
             };
 
             if (screenName != null)
@@ -321,47 +564,45 @@ namespace OpenTween.Connection
             return new Uri("https://api.twitter.com/oauth/authorize?" + MyCommon.BuildQueryString(param));
         }
 
-        public static async Task<IDictionary<string, string>> GetAccessTokenAsync(Tuple<string, string> requestToken, string verifier)
+        public static async Task<IDictionary<string, string>> GetAccessTokenAsync(TwitterCredentialOAuth1 credential, string verifier)
         {
             var param = new Dictionary<string, string>
             {
                 ["oauth_verifier"] = verifier,
             };
-            var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/access_token"), param, requestToken)
+            var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/access_token"), param, credential)
                 .ConfigureAwait(false);
 
             return response;
         }
 
-        private static async Task<IDictionary<string, string>> GetOAuthTokenAsync(Uri uri, IDictionary<string, string> param,
-            Tuple<string, string> oauthToken)
+        private static async Task<IDictionary<string, string>> GetOAuthTokenAsync(
+            Uri uri,
+            IDictionary<string, string> param,
+            TwitterCredentialOAuth1 credential
+        )
         {
-            HttpClient authorizeClient;
-            if (oauthToken != null)
-                authorizeClient = InitializeHttpClient(oauthToken.Item1, oauthToken.Item2);
-            else
-                authorizeClient = InitializeHttpClient("", "");
+            using var authorizeClient = InitializeHttpClient(credential);
 
             var requestUri = new Uri(uri, "?" + MyCommon.BuildQueryString(param));
 
             try
             {
-                using (var request = new HttpRequestMessage(HttpMethod.Post, requestUri))
-                using (var response = await authorizeClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
-                    .ConfigureAwait(false))
-                using (var content = response.Content)
-                {
-                    var responseText = await content.ReadAsStringAsync()
-                        .ConfigureAwait(false);
+                using var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
+                using var response = await authorizeClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
+                    .ConfigureAwait(false);
+
+                using var content = response.Content;
+                var responseText = await content.ReadAsStringAsync()
+                    .ConfigureAwait(false);
 
-                    if (!response.IsSuccessStatusCode)
-                        throw new TwitterApiException(response.StatusCode, responseText);
+                await TwitterApiConnection.CheckStatusCode(response)
+                    .ConfigureAwait(false);
 
-                    var responseParams = HttpUtility.ParseQueryString(responseText);
+                var responseParams = HttpUtility.ParseQueryString(responseText);
 
-                    return responseParams.Cast<string>()
-                        .ToDictionary(x => x, x => responseParams[x]);
-                }
+                return responseParams.Cast<string>()
+                    .ToDictionary(x => x, x => responseParams[x]);
             }
             catch (HttpRequestException ex)
             {
@@ -373,16 +614,21 @@ namespace OpenTween.Connection
             }
         }
 
-        private static HttpClient InitializeHttpClient(string accessToken, string accessSecret)
+        private static HttpClient InitializeHttpClient(ITwitterCredential credential, bool disableGzip = false)
         {
-            var innerHandler = Networking.CreateHttpClientHandler();
-            innerHandler.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
+            var builder = Networking.CreateHttpClientBuilder();
+
+            builder.SetupHttpClientHandler(x =>
+            {
+                x.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
+
+                if (disableGzip)
+                    x.AutomaticDecompression = DecompressionMethods.None;
+            });
 
-            var handler = new OAuthHandler(innerHandler,
-                ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
-                accessToken, accessSecret);
+            builder.AddHandler(x => credential.CreateHttpHandler(x));
 
-            return Networking.CreateHttpClient(handler);
+            return builder.Build();
         }
     }
 }