OSDN Git Service

メソッドに式本体を使用する (IDE0021, IDE0022, IDE0025, IDE0027)
[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 using System;
23 using System.Collections.Generic;
24 using System.Diagnostics.CodeAnalysis;
25 using System.Linq;
26 using System.Net;
27 using System.Net.Cache;
28 using System.Net.Http;
29 using System.Text;
30 using System.Threading;
31 using System.Threading.Tasks;
32
33 namespace OpenTween.Connection
34 {
35     public static class Networking
36     {
37         public static TimeSpan DefaultTimeout { get; set; }
38         public static TimeSpan UploadImageTimeout { get; set; }
39
40         /// <summary>
41         /// 通信に使用するプロキシの種類
42         /// </summary>
43         public static ProxyType ProxyType { get; private set; } = ProxyType.IE;
44
45         /// <summary>
46         /// 通信に使用するプロキシ
47         /// </summary>
48         public static IWebProxy Proxy { get; private set; } = null;
49
50         /// <summary>
51         /// OpenTween 内で共通して使用する HttpClient インスタンス
52         /// </summary>
53         public static HttpClient Http => globalHttpClient;
54
55         /// <summary>
56         /// pbs.twimg.com で IPv4 を強制的に使用する
57         /// </summary>
58         public static bool ForceIPv4
59         {
60             get => forceIPv4;
61             set
62             {
63                 if (forceIPv4 == value)
64                     return;
65
66                 forceIPv4 = value;
67
68                 // Network.Http を再作成させる
69                 OnWebProxyChanged(EventArgs.Empty);
70             }
71         }
72
73         /// <summary>
74         /// Webプロキシの設定が変更された場合に発生します
75         /// </summary>
76         public static event EventHandler WebProxyChanged;
77
78         private static bool initialized = false;
79         private static HttpClient globalHttpClient;
80         private static bool forceIPv4 = false;
81
82         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
83         static Networking()
84         {
85             DefaultTimeout = TimeSpan.FromSeconds(20);
86             UploadImageTimeout = TimeSpan.FromSeconds(60);
87             globalHttpClient = CreateHttpClient(new HttpClientHandler());
88         }
89
90         /// <summary>
91         /// ネットワーク接続前に行う処理。起動時に一回だけ実行する必要があります。
92         /// </summary>
93         public static void Initialize()
94         {
95             Networking.initialized = true;
96
97             ServicePointManager.Expect100Continue = false;
98
99             // 明示的に指定しないと、デフォルトでは SSL3.0, TLS1.0 のみ有効となる (.NET Framework 4.5.1)
100             // https://blogs.technet.microsoft.com/jpieblog/2015/04/07/net-framework-tls1-1-1-2/
101             ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
102         }
103
104         public static void SetWebProxy(ProxyType proxyType, string proxyAddress, int proxyPort,
105             string proxyUser, string proxyPassword)
106         {
107             IWebProxy proxy;
108             switch (proxyType)
109             {
110                 case ProxyType.None:
111                     proxy = null;
112                     break;
113                 case ProxyType.Specified:
114                     proxy = new WebProxy(proxyAddress, proxyPort);
115                     if (!string.IsNullOrEmpty(proxyUser) || !string.IsNullOrEmpty(proxyPassword))
116                         proxy.Credentials = new NetworkCredential(proxyUser, proxyPassword);
117                     break;
118                 case ProxyType.IE:
119                 default:
120                     proxy = WebRequest.GetSystemWebProxy();
121                     break;
122             }
123
124             Networking.ProxyType = proxyType;
125             Networking.Proxy = proxy;
126
127             NativeMethods.SetProxy(proxyType, proxyAddress, proxyPort, proxyUser, proxyPassword);
128
129             OnWebProxyChanged(EventArgs.Empty);
130         }
131
132         /// <summary>
133         /// OpenTween で必要な設定を施した HttpClientHandler インスタンスを生成します
134         /// </summary>
135         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
136         public static WebRequestHandler CreateHttpClientHandler()
137         {
138             var handler = new WebRequestHandler
139             {
140                 AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
141             };
142
143             if (Networking.Proxy != null)
144             {
145                 handler.UseProxy = true;
146                 handler.Proxy = Networking.Proxy;
147             }
148             else
149             {
150                 handler.UseProxy = false;
151             }
152
153             return handler;
154         }
155
156         /// <summary>
157         /// OpenTween で必要な設定を施した HttpClient インスタンスを生成します
158         /// </summary>
159         /// <remarks>
160         /// 通常は Networking.Http を使用すべきです。
161         /// このメソッドを使用する場合は、WebProxyChanged イベントが発生する度に HttpClient を生成し直すように実装してください。
162         /// </remarks>
163         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
164         public static HttpClient CreateHttpClient(HttpMessageHandler handler)
165         {
166             HttpClient client;
167             if (ForceIPv4)
168                 client = new HttpClient(new ForceIPv4Handler(handler));
169             else
170                 client = new HttpClient(handler);
171
172             client.Timeout = Networking.DefaultTimeout;
173             client.DefaultRequestHeaders.Add("User-Agent", Networking.GetUserAgentString());
174
175             return client;
176         }
177
178         public static string GetUserAgentString(bool fakeMSIE = false)
179         {
180             if (fakeMSIE)
181                 return ApplicationSettings.AssemblyName + "/" + MyCommon.FileVersion + " (compatible; MSIE 10.0)";
182             else
183                 return ApplicationSettings.AssemblyName + "/" + MyCommon.FileVersion;
184         }
185
186         /// <summary>
187         /// Initialize() メソッドが事前に呼ばれているか確認します
188         /// </summary>
189         internal static void CheckInitialized()
190         {
191             if (!Networking.initialized)
192                 throw new InvalidOperationException("Sequence error.(not initialized)");
193         }
194
195         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
196         private static void OnWebProxyChanged(EventArgs e)
197         {
198             var newClient = Networking.CreateHttpClient(Networking.CreateHttpClientHandler());
199             var oldClient = Interlocked.Exchange(ref globalHttpClient, newClient);
200             oldClient.Dispose();
201
202             WebProxyChanged?.Invoke(null, e);
203         }
204
205         private class ForceIPv4Handler : DelegatingHandler
206         {
207             private readonly IPAddress ipv4Address;
208
209             public ForceIPv4Handler(HttpMessageHandler innerHandler)
210                 : base(innerHandler)
211             {
212                 foreach (var address in Dns.GetHostAddresses("pbs.twimg.com"))
213                     if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
214                         this.ipv4Address = address;
215             }
216
217             protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
218             {
219                 var requestUri = request.RequestUri;
220                 if (requestUri.Host == "pbs.twimg.com")
221                 {
222                     var rewriteUriStr = requestUri.GetLeftPart(UriPartial.Scheme) + this.ipv4Address + requestUri.PathAndQuery;
223                     request.RequestUri = new Uri(rewriteUriStr);
224                     request.Headers.Host = "pbs.twimg.com";
225                 }
226
227                 return base.SendAsync(request, cancellationToken);
228             }
229         }
230     }
231
232     public enum ProxyType
233     {
234         None,
235         IE,
236         Specified,
237     }
238 }