OSDN Git Service

バージョン v3.15.2-dev 開発開始
[opentween/open-tween.git] / OpenTween / Connection / Networking.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2014 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
4 //
5 // This file is part of OpenTween.
6 //
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)
10 // any later version.
11 //
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
15 // for more details.
16 //
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.
21
22 #nullable enable
23
24 using System;
25 using System.Collections.Generic;
26 using System.Diagnostics.CodeAnalysis;
27 using System.Linq;
28 using System.Net;
29 using System.Net.Cache;
30 using System.Net.Http;
31 using System.Text;
32 using System.Threading;
33 using System.Threading.Tasks;
34
35 namespace OpenTween.Connection
36 {
37     public static class Networking
38     {
39         public static TimeSpan DefaultTimeout { get; set; }
40
41         public static TimeSpan UploadImageTimeout { get; set; }
42
43         /// <summary>
44         /// 通信に使用するプロキシの種類
45         /// </summary>
46         public static ProxyType ProxyType { get; private set; } = ProxyType.IE;
47
48         /// <summary>
49         /// 通信に使用するプロキシ
50         /// </summary>
51         public static IWebProxy? Proxy { get; private set; } = null;
52
53         /// <summary>
54         /// OpenTween 内で共通して使用する HttpClient インスタンス
55         /// </summary>
56         public static HttpClient Http => globalHttpClient;
57
58         /// <summary>
59         /// pbs.twimg.com で IPv4 を強制的に使用する
60         /// </summary>
61         public static bool ForceIPv4
62         {
63             get => forceIPv4;
64             set
65             {
66                 if (forceIPv4 == value)
67                     return;
68
69                 forceIPv4 = value;
70
71                 // Network.Http を再作成させる
72                 OnWebProxyChanged(EventArgs.Empty);
73             }
74         }
75
76         /// <summary>
77         /// Webプロキシの設定が変更された場合に発生します
78         /// </summary>
79         public static event EventHandler? WebProxyChanged;
80
81         private static bool initialized = false;
82         private static HttpClient globalHttpClient;
83         private static bool forceIPv4 = false;
84
85         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
86         static Networking()
87         {
88             DefaultTimeout = TimeSpan.FromSeconds(20);
89             UploadImageTimeout = TimeSpan.FromSeconds(60);
90             globalHttpClient = CreateHttpClientBuilder().Build();
91         }
92
93         /// <summary>
94         /// ネットワーク接続前に行う処理。起動時に一回だけ実行する必要があります。
95         /// </summary>
96         public static void Initialize()
97         {
98             Networking.initialized = true;
99
100             ServicePointManager.Expect100Continue = false;
101             ServicePointManager.CheckCertificateRevocationList = true;
102         }
103
104         public static void SetWebProxy(
105             ProxyType proxyType,
106             string proxyAddress,
107             int proxyPort,
108             string proxyUser,
109             string proxyPassword)
110         {
111             IWebProxy? proxy;
112             switch (proxyType)
113             {
114                 case ProxyType.None:
115                     proxy = null;
116                     break;
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);
121                     break;
122                 case ProxyType.IE:
123                 default:
124                     proxy = WebRequest.GetSystemWebProxy();
125                     break;
126             }
127
128             Networking.ProxyType = proxyType;
129             Networking.Proxy = proxy;
130
131             NativeMethods.SetProxy(proxyType, proxyAddress, proxyPort);
132
133             OnWebProxyChanged(EventArgs.Empty);
134         }
135
136         /// <summary>
137         /// OpenTween のユーザー設定を適用した <see cref="HttpClientBuilder"/> を返します
138         /// </summary>
139         /// <remarks>
140         /// 通常は Networking.Http を使用すべきです。
141         /// このメソッドを使用する場合は、WebProxyChanged イベントが発生する度に HttpClient を生成し直すように実装してください。
142         /// </remarks>
143         public static HttpClientBuilder CreateHttpClientBuilder()
144         {
145             var builder = new HttpClientBuilder();
146
147             builder.SetupHttpClientHandler(x =>
148             {
149                 x.UseCookies = false;
150                 x.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
151                 x.ReadWriteTimeout = (int)DefaultTimeout.TotalMilliseconds;
152
153                 if (Networking.Proxy != null)
154                 {
155                     x.UseProxy = true;
156                     x.Proxy = Networking.Proxy;
157                 }
158                 else
159                 {
160                     x.UseProxy = false;
161                 }
162             });
163
164             builder.SetupHttpClient(x => x.Timeout = Networking.DefaultTimeout);
165
166             if (forceIPv4)
167                 builder.AddHandler(x => new ForceIPv4Handler(x));
168
169             return builder;
170         }
171
172         public static string GetUserAgentString(bool fakeMSIE = false)
173         {
174             if (fakeMSIE)
175                 return ApplicationSettings.AssemblyName + "/" + MyCommon.FileVersion + " (compatible; MSIE 10.0)";
176             else
177                 return ApplicationSettings.AssemblyName + "/" + MyCommon.FileVersion;
178         }
179
180         /// <summary>
181         /// Initialize() メソッドが事前に呼ばれているか確認します
182         /// </summary>
183         internal static void CheckInitialized()
184         {
185             if (!Networking.initialized)
186                 throw new InvalidOperationException("Sequence error.(not initialized)");
187         }
188
189         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
190         private static void OnWebProxyChanged(EventArgs e)
191         {
192             var newClient = Networking.CreateHttpClientBuilder().Build();
193             var oldClient = Interlocked.Exchange(ref globalHttpClient, newClient);
194             oldClient.Dispose();
195
196             WebProxyChanged?.Invoke(null, e);
197         }
198
199         private class ForceIPv4Handler : DelegatingHandler
200         {
201             private readonly IPAddress? ipv4Address;
202
203             public ForceIPv4Handler(HttpMessageHandler innerHandler)
204                 : base(innerHandler)
205             {
206                 foreach (var address in Dns.GetHostAddresses("pbs.twimg.com"))
207                 {
208                     if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
209                         this.ipv4Address = address;
210                 }
211             }
212
213             protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
214             {
215                 if (this.ipv4Address != null)
216                 {
217                     var requestUri = request.RequestUri;
218                     if (requestUri.Host == "pbs.twimg.com")
219                     {
220                         var rewriteUriStr = requestUri.GetLeftPart(UriPartial.Scheme) + this.ipv4Address + requestUri.PathAndQuery;
221                         request.RequestUri = new Uri(rewriteUriStr);
222                         request.Headers.Host = "pbs.twimg.com";
223                     }
224                 }
225
226                 return base.SendAsync(request, cancellationToken);
227             }
228         }
229     }
230
231     public enum ProxyType
232     {
233         None,
234         IE,
235         Specified,
236     }
237 }