// OpenTween - Client of Twitter
// Copyright (c) 2014 kim_upsilon (@kim_upsilon)
// All rights reserved.
//
// This file is part of OpenTween.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see , or write to
// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
// Boston, MA 02110-1301, USA.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using System.Net.Cache;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace OpenTween.Connection
{
public static class Networking
{
public static TimeSpan DefaultTimeout { get; set; }
///
/// 通信に使用するプロキシの種類
///
public static ProxyType ProxyType
{
get { return proxyType; }
}
///
/// 通信に使用するプロキシ
///
public static IWebProxy Proxy
{
get { return proxy; }
}
///
/// OpenTween 内で共通して使用する HttpClient インスタンス
///
public static HttpClient Http
{
get { return globalHttpClient; }
}
///
/// pbs.twimg.com で IPv4 を強制的に使用する
///
public static bool ForceIPv4
{
get { return forceIPv4; }
set
{
if (forceIPv4 == value)
return;
forceIPv4 = value;
// Network.Http を再作成させる
OnWebProxyChanged(EventArgs.Empty);
}
}
///
/// Webプロキシの設定が変更された場合に発生します
///
public static event EventHandler WebProxyChanged;
private static bool initialized = false;
private static HttpClient globalHttpClient;
private static ProxyType proxyType = ProxyType.IE;
private static IWebProxy proxy = null;
private static bool forceIPv4 = false;
[SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
static Networking()
{
DefaultTimeout = TimeSpan.FromSeconds(20);
globalHttpClient = CreateHttpClient(new HttpClientHandler());
}
///
/// ネットワーク接続前に行う処理。起動時に一回だけ実行する必要があります。
///
public static void Initialize()
{
Networking.initialized = true;
ServicePointManager.Expect100Continue = false;
WebRequest.DefaultCachePolicy = new RequestCachePolicy(RequestCacheLevel.Default);
}
public static void SetWebProxy(ProxyType proxyType, string proxyAddress, int proxyPort,
string proxyUser, string proxyPassword)
{
IWebProxy proxy;
switch (proxyType)
{
case ProxyType.None:
proxy = null;
break;
case ProxyType.Specified:
proxy = new WebProxy(proxyAddress, proxyPort);
if (!string.IsNullOrEmpty(proxyUser) || !string.IsNullOrEmpty(proxyPassword))
proxy.Credentials = new NetworkCredential(proxyUser, proxyPassword);
break;
case ProxyType.IE:
default:
proxy = WebRequest.GetSystemWebProxy();
break;
}
Networking.proxyType = proxyType;
Networking.proxy = proxy;
NativeMethods.SetProxy(proxyType, proxyAddress, proxyPort, proxyUser, proxyPassword);
OnWebProxyChanged(EventArgs.Empty);
}
///
/// プロキシ等の設定を施した HttpClient インスタンスを生成します
///
///
/// 通常は Networking.Http を使用すべきです。
/// このメソッドを使用する場合は、WebProxyChanged イベントが発生する度に HttpClient を生成し直すように実装してください。
///
[SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
public static HttpClient CreateHttpClient(HttpClientHandler handler)
{
if (Networking.Proxy != null)
{
handler.UseProxy = true;
handler.Proxy = Networking.Proxy;
}
else
{
handler.UseProxy = false;
}
HttpClient client;
if (ForceIPv4)
client = new HttpClient(new ForceIPv4Handler(handler));
else
client = new HttpClient(handler);
client.Timeout = Networking.DefaultTimeout;
client.DefaultRequestHeaders.Add("User-Agent", Networking.GetUserAgentString());
return client;
}
public static string GetUserAgentString(bool fakeMSIE = false)
{
if (fakeMSIE)
return MyCommon.GetAssemblyName() + "/" + MyCommon.FileVersion + " (compatible; MSIE 10.0)";
else
return MyCommon.GetAssemblyName() + "/" + MyCommon.FileVersion;
}
///
/// Initialize() メソッドが事前に呼ばれているか確認します
///
internal static void CheckInitialized()
{
if (!Networking.initialized)
throw new InvalidOperationException("Sequence error.(not initialized)");
}
[SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
private static void OnWebProxyChanged(EventArgs e)
{
var newClient = Networking.CreateHttpClient(new HttpClientHandler());
var oldClient = Interlocked.Exchange(ref globalHttpClient, newClient);
oldClient.Dispose();
WebProxyChanged?.Invoke(null, e);
}
private class ForceIPv4Handler : DelegatingHandler
{
private readonly IPAddress ipv4Address;
public ForceIPv4Handler(HttpMessageHandler innerHandler)
: base(innerHandler)
{
foreach (var address in Dns.GetHostAddresses("pbs.twimg.com"))
if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
this.ipv4Address = address;
}
protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var requestUri = request.RequestUri;
if (requestUri.Host == "pbs.twimg.com")
{
var rewriteUriStr = requestUri.GetLeftPart(UriPartial.Scheme) + this.ipv4Address + requestUri.PathAndQuery;
request.RequestUri = new Uri(rewriteUriStr);
request.Headers.Host = "pbs.twimg.com";
}
return base.SendAsync(request, cancellationToken);
}
}
}
public enum ProxyType
{
None,
IE,
Specified,
}
}