OSDN Git Service

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