OSDN Git Service

既存のコードのnull条件演算子 (.?) が利用可能な箇所を置き換え
[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.Http;
28 using System.Text;
29 using System.Threading;
30 using System.Threading.Tasks;
31
32 namespace OpenTween.Connection
33 {
34     public static class Networking
35     {
36         public static TimeSpan DefaultTimeout { get; set; }
37
38         /// <summary>
39         /// 通信に使用するプロキシの種類
40         /// </summary>
41         public static ProxyType ProxyType
42         {
43             get { return proxyType; }
44         }
45
46         /// <summary>
47         /// 通信に使用するプロキシ
48         /// </summary>
49         public static IWebProxy Proxy
50         {
51             get { return proxy; }
52         }
53
54         /// <summary>
55         /// OpenTween 内で共通して使用する HttpClient インスタンス
56         /// </summary>
57         public static HttpClient Http
58         {
59             get { return globalHttpClient; }
60         }
61
62         /// <summary>
63         /// pbs.twimg.com で IPv4 を強制的に使用する
64         /// </summary>
65         public static bool ForceIPv4
66         {
67             get { return forceIPv4; }
68             set
69             {
70                 if (forceIPv4 == value)
71                     return;
72
73                 forceIPv4 = value;
74
75                 // Network.Http を再作成させる
76                 OnWebProxyChanged(EventArgs.Empty);
77             }
78         }
79
80         /// <summary>
81         /// Webプロキシの設定が変更された場合に発生します
82         /// </summary>
83         public static event EventHandler WebProxyChanged;
84
85         private static bool initialized = false;
86         private static HttpClient globalHttpClient;
87         private static ProxyType proxyType = ProxyType.IE;
88         private static IWebProxy proxy = null;
89         private static bool forceIPv4 = false;
90
91         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
92         static Networking()
93         {
94             DefaultTimeout = TimeSpan.FromSeconds(20);
95             globalHttpClient = CreateHttpClient(new HttpClientHandler());
96         }
97
98         /// <summary>
99         /// ネットワーク接続前に行う処理。起動時に一回だけ実行する必要があります。
100         /// </summary>
101         public static void Initialize()
102         {
103             Networking.initialized = true;
104
105             ServicePointManager.Expect100Continue = false;
106         }
107
108         public static void SetWebProxy(ProxyType proxyType, string proxyAddress, int proxyPort,
109             string proxyUser, 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 (!string.IsNullOrEmpty(proxyUser) || !string.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, proxyUser, proxyPassword);
132
133             OnWebProxyChanged(EventArgs.Empty);
134         }
135
136         /// <summary>
137         /// プロキシ等の設定を施した HttpClient インスタンスを生成します
138         /// </summary>
139         /// <remarks>
140         /// 通常は Networking.Http を使用すべきです。
141         /// このメソッドを使用する場合は、WebProxyChanged イベントが発生する度に HttpClient を生成し直すように実装してください。
142         /// </remarks>
143         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
144         public static HttpClient CreateHttpClient(HttpClientHandler handler)
145         {
146             if (Networking.Proxy != null)
147             {
148                 handler.UseProxy = true;
149                 handler.Proxy = Networking.Proxy;
150             }
151             else
152             {
153                 handler.UseProxy = false;
154             }
155
156             HttpClient client;
157             if (ForceIPv4)
158                 client = new HttpClient(new ForceIPv4Handler(handler));
159             else
160                 client = new HttpClient(handler);
161
162             client.Timeout = Networking.DefaultTimeout;
163             client.DefaultRequestHeaders.Add("User-Agent", Networking.GetUserAgentString());
164
165             return client;
166         }
167
168         public static string GetUserAgentString(bool fakeMSIE = false)
169         {
170             if (fakeMSIE)
171                 return MyCommon.GetAssemblyName() + "/" + MyCommon.FileVersion + " (compatible; MSIE 10.0)";
172             else
173                 return MyCommon.GetAssemblyName() + "/" + MyCommon.FileVersion;
174         }
175
176         /// <summary>
177         /// Initialize() メソッドが事前に呼ばれているか確認します
178         /// </summary>
179         internal static void CheckInitialized()
180         {
181             if (!Networking.initialized)
182                 throw new InvalidOperationException("Sequence error.(not initialized)");
183         }
184
185         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
186         private static void OnWebProxyChanged(EventArgs e)
187         {
188             var newClient = Networking.CreateHttpClient(new HttpClientHandler());
189             var oldClient = Interlocked.Exchange(ref globalHttpClient, newClient);
190             oldClient.Dispose();
191
192             WebProxyChanged?.Invoke(null, e);
193         }
194
195         private class ForceIPv4Handler : DelegatingHandler
196         {
197             private readonly IPAddress ipv4Address;
198
199             public ForceIPv4Handler(HttpMessageHandler innerHandler)
200                 : base(innerHandler)
201             {
202                 foreach (var address in Dns.GetHostAddresses("pbs.twimg.com"))
203                     if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
204                         this.ipv4Address = address;
205             }
206
207             protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
208             {
209                 var requestUri = request.RequestUri;
210                 if (requestUri.Host == "pbs.twimg.com")
211                 {
212                     var rewriteUriStr = requestUri.GetLeftPart(UriPartial.Scheme) + this.ipv4Address + requestUri.PathAndQuery;
213                     request.RequestUri = new Uri(rewriteUriStr);
214                     request.Headers.Host = "pbs.twimg.com";
215                 }
216
217                 return base.SendAsync(request, cancellationToken);
218             }
219         }
220     }
221
222     public enum ProxyType
223     {
224         None,
225         IE,
226         Specified,
227     }
228 }