1 // OpenTween - Client of Twitter
2 // Copyright (c) 2014 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
5 // This file is part of OpenTween.
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 // You should have received a copy of the GNU General Public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
25 using System.Collections.Generic;
26 using System.Diagnostics.CodeAnalysis;
29 using System.Net.Cache;
30 using System.Net.Http;
32 using System.Threading;
33 using System.Threading.Tasks;
35 namespace OpenTween.Connection
37 public static class Networking
39 public static TimeSpan DefaultTimeout { get; set; }
41 public static TimeSpan UploadImageTimeout { get; set; }
46 public static ProxyType ProxyType { get; private set; } = ProxyType.IE;
51 public static IWebProxy? Proxy { get; private set; } = null;
54 /// OpenTween 内で共通して使用する HttpClient インスタンス
56 public static HttpClient Http => globalHttpClient;
59 /// pbs.twimg.com で IPv4 を強制的に使用する
61 public static bool ForceIPv4
66 if (forceIPv4 == value)
71 // Network.Http を再作成させる
72 OnWebProxyChanged(EventArgs.Empty);
77 /// Webプロキシの設定が変更された場合に発生します
79 public static event EventHandler? WebProxyChanged;
81 private static bool initialized = false;
82 private static HttpClient globalHttpClient;
83 private static bool forceIPv4 = false;
85 [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
88 DefaultTimeout = TimeSpan.FromSeconds(20);
89 UploadImageTimeout = TimeSpan.FromSeconds(60);
90 globalHttpClient = CreateHttpClientBuilder().Build();
94 /// ネットワーク接続前に行う処理。起動時に一回だけ実行する必要があります。
96 public static void Initialize()
98 Networking.initialized = true;
100 ServicePointManager.Expect100Continue = false;
101 ServicePointManager.CheckCertificateRevocationList = true;
104 public static void SetWebProxy(
109 string proxyPassword)
117 case ProxyType.Specified:
118 proxy = new WebProxy(proxyAddress, proxyPort);
119 if (!MyCommon.IsNullOrEmpty(proxyUser) || !MyCommon.IsNullOrEmpty(proxyPassword))
120 proxy.Credentials = new NetworkCredential(proxyUser, proxyPassword);
124 proxy = WebRequest.GetSystemWebProxy();
128 Networking.ProxyType = proxyType;
129 Networking.Proxy = proxy;
131 NativeMethods.SetProxy(proxyType, proxyAddress, proxyPort);
133 OnWebProxyChanged(EventArgs.Empty);
137 /// OpenTween のユーザー設定を適用した <see cref="HttpClientBuilder"/> を返します
140 /// 通常は Networking.Http を使用すべきです。
141 /// このメソッドを使用する場合は、WebProxyChanged イベントが発生する度に HttpClient を生成し直すように実装してください。
143 public static HttpClientBuilder CreateHttpClientBuilder()
145 var builder = new HttpClientBuilder();
147 builder.SetupHttpClientHandler(x =>
149 x.UseCookies = false;
150 x.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
151 x.ReadWriteTimeout = (int)DefaultTimeout.TotalMilliseconds;
153 if (Networking.Proxy != null)
156 x.Proxy = Networking.Proxy;
164 builder.SetupHttpClient(x => x.Timeout = Networking.DefaultTimeout);
167 builder.AddHandler(x => new ForceIPv4Handler(x));
172 public static string GetUserAgentString(bool fakeMSIE = false)
175 return ApplicationSettings.AssemblyName + "/" + MyCommon.FileVersion + " (compatible; MSIE 10.0)";
177 return ApplicationSettings.AssemblyName + "/" + MyCommon.FileVersion;
181 /// Initialize() メソッドが事前に呼ばれているか確認します
183 internal static void CheckInitialized()
185 if (!Networking.initialized)
186 throw new InvalidOperationException("Sequence error.(not initialized)");
189 [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
190 private static void OnWebProxyChanged(EventArgs e)
192 var newClient = Networking.CreateHttpClientBuilder().Build();
193 var oldClient = Interlocked.Exchange(ref globalHttpClient, newClient);
196 WebProxyChanged?.Invoke(null, e);
199 private class ForceIPv4Handler : DelegatingHandler
201 private readonly IPAddress? ipv4Address;
203 public ForceIPv4Handler(HttpMessageHandler innerHandler)
206 foreach (var address in Dns.GetHostAddresses("pbs.twimg.com"))
208 if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
209 this.ipv4Address = address;
213 protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
215 if (this.ipv4Address != null)
217 var requestUri = request.RequestUri;
218 if (requestUri.Host == "pbs.twimg.com")
220 var rewriteUriStr = requestUri.GetLeftPart(UriPartial.Scheme) + this.ipv4Address + requestUri.PathAndQuery;
221 request.RequestUri = new Uri(rewriteUriStr);
222 request.Headers.Host = "pbs.twimg.com";
226 return base.SendAsync(request, cancellationToken);
231 public enum ProxyType