Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HttpClientBuilderクラスを追加 #265

Merged
merged 1 commit into from
Dec 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions OpenTween.Tests/Connection/HttpClientBuilderTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// OpenTween - Client of Twitter
// Copyright (c) 2023 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
// All rights reserved.
//
// This file is part of OpenTween.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
// Boston, MA 02110-1301, USA.

using System;
using System.Net.Http;
using Moq;
using Xunit;

namespace OpenTween.Connection
{
public class HttpClientBuilderTest
{
[Fact]
public void Build_Test()
{
var builder = new HttpClientBuilder();
using var client = builder.Build();
}

[Fact]
public void SetupHttpClientHandler_Test()
{
var builder = new HttpClientBuilder();
builder.SetupHttpClientHandler(x => x.AllowAutoRedirect = true);
builder.AddHandler(x =>
{
var httpClientHandler = (HttpClientHandler)x;
Assert.True(httpClientHandler.AllowAutoRedirect);
return x;
});
using var client = builder.Build();
}

[Fact]
public void AddHandler_Test()
{
var count = 0;

var builder = new HttpClientBuilder();
builder.AddHandler(x =>
{
count++;
Assert.IsType<WebRequestHandler>(x);
return x;
});
using var client = builder.Build();

Assert.Equal(1, count);
}

[Fact]
public void AddHandler_NestingTest()
{
var count = 0;
HttpMessageHandler? handler = null;

var builder = new HttpClientBuilder();
builder.AddHandler(x =>
{
count++;
handler = Mock.Of<HttpMessageHandler>();
return handler;
});
builder.AddHandler(x =>
{
count++;
Assert.NotNull(x);
Assert.Same(handler, x);
return x;
});
using var client = builder.Build();

Assert.Equal(2, count);
}

[Fact]
public void SetupHttpClient_Test()
{
var builder = new HttpClientBuilder();
builder.SetupHttpClient(x => x.Timeout = TimeSpan.FromSeconds(10));
using var client = builder.Build();

Assert.Equal(TimeSpan.FromSeconds(10), client.Timeout);
}
}
}
1 change: 1 addition & 0 deletions OpenTween.Tests/OpenTween.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Web" />
<Reference Include="System.Windows.Forms" />
Expand Down
6 changes: 4 additions & 2 deletions OpenTween/Api/ImgurApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ public ImgurApi(ApiKey clientId, HttpClient? http)
}
else
{
this.http = Networking.CreateHttpClient(Networking.CreateHttpClientHandler());
this.http.Timeout = Networking.UploadImageTimeout;
var builder = Networking.CreateHttpClientBuilder();
builder.SetupHttpClient(x => x.Timeout = Networking.UploadImageTimeout);

this.http = builder.Build();
}
}

Expand Down
8 changes: 5 additions & 3 deletions OpenTween/Api/MobypictureApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ public MobypictureApi(ApiKey apiKey, TwitterApi twitterApi)
{
this.apiKey = apiKey;

var handler = twitterApi.CreateOAuthEchoHandler(AuthServiceProvider, OAuthRealm);
this.http = Networking.CreateHttpClient(handler);
this.http.Timeout = Networking.UploadImageTimeout;
var builder = Networking.CreateHttpClientBuilder();
builder.SetupHttpClient(x => x.Timeout = Networking.UploadImageTimeout);
builder.AddHandler(x => twitterApi.CreateOAuthEchoHandler(x, AuthServiceProvider, OAuthRealm));

this.http = builder.Build();
}

public MobypictureApi(ApiKey apiKey, HttpClient http)
Expand Down
5 changes: 3 additions & 2 deletions OpenTween/Api/TwitterApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -815,8 +816,8 @@ public Task MediaMetadataCreate(long mediaId, string altText)
return this.Connection.PostJsonAsync(endpoint, json);
}

public OAuthEchoHandler CreateOAuthEchoHandler(Uri authServiceProvider, Uri? realm = null)
=> ((TwitterApiConnection)this.Connection).CreateOAuthEchoHandler(authServiceProvider, realm);
public OAuthEchoHandler CreateOAuthEchoHandler(HttpMessageHandler innerHandler, Uri authServiceProvider, Uri? realm = null)
=> ((TwitterApiConnection)this.Connection).CreateOAuthEchoHandler(innerHandler, authServiceProvider, realm);

public void Dispose()
=> this.ApiConnection?.Dispose();
Expand Down
76 changes: 76 additions & 0 deletions OpenTween/Connection/HttpClientBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// OpenTween - Client of Twitter
// Copyright (c) 2023 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
// All rights reserved.
//
// This file is part of OpenTween.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
// 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.Net.Http;

namespace OpenTween.Connection
{
public class HttpClientBuilder
{
private readonly List<Action<WebRequestHandler>> setupHttpClientHandler = new();
private readonly List<Func<HttpMessageHandler, HttpMessageHandler>> customHandlers = new();
private readonly List<Action<HttpClient>> setupHttpClient = new();

public void SetupHttpClientHandler(Action<WebRequestHandler> func)
=> this.setupHttpClientHandler.Add(func);

public void AddHandler(Func<HttpMessageHandler, HttpMessageHandler> func)
=> this.customHandlers.Add(func);

public void SetupHttpClient(Action<HttpClient> func)
=> this.setupHttpClient.Add(func);

public HttpClient Build()
{
WebRequestHandler? handler = null;
HttpMessageHandler? wrappedHandler = null;
HttpClient? client = null;
try
{
handler = new();
foreach (var setupFunc in this.setupHttpClientHandler)
setupFunc(handler);

wrappedHandler = handler;
foreach (var handlerFunc in this.customHandlers)
wrappedHandler = handlerFunc(wrappedHandler);

client = new(wrappedHandler, disposeHandler: true);

foreach (var setupFunc in this.setupHttpClient)
setupFunc(client);

return client;
}
catch
{
client?.Dispose();
wrappedHandler?.Dispose();
handler?.Dispose();
throw;
}
}
}
}
65 changes: 27 additions & 38 deletions OpenTween/Connection/Networking.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ static Networking()
{
DefaultTimeout = TimeSpan.FromSeconds(20);
UploadImageTimeout = TimeSpan.FromSeconds(60);
globalHttpClient = CreateHttpClient(new HttpClientHandler());
globalHttpClient = CreateHttpClientBuilder().Build();
}

/// <summary>
Expand Down Expand Up @@ -134,50 +134,39 @@ public static void SetWebProxy(
}

/// <summary>
/// OpenTween で必要な設定を施した HttpClientHandler インスタンスを生成します
/// </summary>
[SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
public static WebRequestHandler CreateHttpClientHandler()
{
var handler = new WebRequestHandler
{
UseCookies = false,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
ReadWriteTimeout = (int)DefaultTimeout.TotalMilliseconds,
};

if (Networking.Proxy != null)
{
handler.UseProxy = true;
handler.Proxy = Networking.Proxy;
}
else
{
handler.UseProxy = false;
}

return handler;
}

/// <summary>
/// OpenTween で必要な設定を施した HttpClient インスタンスを生成します
/// OpenTween のユーザー設定を適用した <see cref="HttpClientBuilder"/> を返します
/// </summary>
/// <remarks>
/// 通常は Networking.Http を使用すべきです。
/// このメソッドを使用する場合は、WebProxyChanged イベントが発生する度に HttpClient を生成し直すように実装してください。
/// </remarks>
[SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
public static HttpClient CreateHttpClient(HttpMessageHandler handler)
public static HttpClientBuilder CreateHttpClientBuilder()
{
HttpClient client;
if (ForceIPv4)
client = new HttpClient(new ForceIPv4Handler(handler));
else
client = new HttpClient(handler);
var builder = new HttpClientBuilder();

builder.SetupHttpClientHandler(x =>
{
x.UseCookies = false;
x.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
x.ReadWriteTimeout = (int)DefaultTimeout.TotalMilliseconds;

if (Networking.Proxy != null)
{
x.UseProxy = true;
x.Proxy = Networking.Proxy;
}
else
{
x.UseProxy = false;
}
});

builder.SetupHttpClient(x => x.Timeout = Networking.DefaultTimeout);

client.Timeout = Networking.DefaultTimeout;
if (forceIPv4)
builder.AddHandler(x => new ForceIPv4Handler(x));

return client;
return builder;
}

public static string GetUserAgentString(bool fakeMSIE = false)
Expand All @@ -200,7 +189,7 @@ internal static void CheckInitialized()
[SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
private static void OnWebProxyChanged(EventArgs e)
{
var newClient = Networking.CreateHttpClient(Networking.CreateHttpClientHandler());
var newClient = Networking.CreateHttpClientBuilder().Build();
var oldClient = Interlocked.Exchange(ref globalHttpClient, newClient);
oldClient.Dispose();

Expand Down
Loading