我已经实现了 IWebProxy
public class DynamicProxy : IWebProxy
{
public Uri? Address { get; private set; }
public ProxyData? Data
{
get => _data;
set => SetData(value);
}
private ProxyData? _data;
public HashSet<string> BypassHosts { get; init; } = new();
/// <summary>
/// Property is readonly
/// </summary>
public ICredentials? Credentials
{
get => _credentials;
set => throw new MemberAccessException("Credentials property of DynamicProxy is readonly");
}
private ICredentials? _credentials;
public DynamicProxy(ProxyData? data)
{
SetData(data);
}
public void SetData(ProxyData? data)
{
_data = data;
if (_data == null)
{
Address = null;
return;
};
var address = _data.ToString();
Address = new Uri(address);
if (_data.AuthEnabled)
{
_credentials = new NetworkCredential(_data.Username, _data.Password);
}
}
public Uri? GetProxy(Uri destination)
{
return IsBypassed(destination) ? destination : Address;
}
public bool IsBypassed(Uri host)
{
return BypassHosts.Contains(host.Host);
}
public void AddToBypass(Uri host)
{
BypassHosts.Add(host.Host);
}
}
此类的要点是根据需要使用
SetProxy(ProxyData? data)
方法动态更改代理地址和凭据,因为在第一次请求后不再可能更改 HttpClientHandler
属性。但是,在使用新凭证更改代理后,我发现了HttpRequestException
the proxy tunnel request to proxy '...' failed with status code '407'
。HttpClient
和 HttpClientHandler
。如果我可以强制 HttpClient(或处理程序)不缓存/刷新缓存,那就太理想了。
class Example
{
DynamicProxy Proxy;
HttpClient Client;
public Example()
{
Proxy = new DynamicProxy(null);
var handler = new HttpClientHandler();
handler.Proxy = Proxy;
Client = new HttpClient(handler);
}
//ProxyData here have different Addresses and Credentials
public async Task Test(ProxyData first, ProxyData second)
{
Proxy.SetData(first);
var req1 = await Client.GetStringAsync("https://www.gstatic.com/generate_204"); // Ok
Proxy.SetData(second);
var req2 = await Client.GetStringAsync("https://www.gstatic.com/generate_204"); //Exception with code 407
}
}
HttpConnectionPoolManager
用于在 HttpClientHandler
中发送请求,具有名为 _proxyCredentials
的私有只读字段。因此,在第一次请求后,它会缓存代理的凭据。可能的解决方案如下:
internal class ProxyCredentials : ICredentials
{
private string? Username { get; set; }
private string? Password { get; set; }
private NetworkCredential Credential => _credential ?? new NetworkCredential(Username, Password);
private NetworkCredential? _credential;
private bool _isSet;
public ProxyCredentials(){}
public ProxyCredentials(string? username, string? password)
{
if (username != null && password != null)
{
_isSet = true;
Username = username;
Password = password;
}
}
public void Set(string username, string password)
{
Username = username;
Password = password;
if (username != Username || password != Password)
{
_credential = null;
}
_isSet = true;
}
public void Reset()
{
Username = null;
Password = null;
_isSet = false;
}
public NetworkCredential? GetCredential(Uri uri, string authType)
{
return _isSet ? Credential : null;
}
}
然后放入
DynamicProxy
public class DynamicProxy : IWebProxy
{
public Uri? Address { get; private set; }
public ProxyData? Data
{
get => _data;
set => SetData(value);
}
private ProxyData? _data;
public HashSet<string> BypassHosts { get; init; } = new();
/// <summary>
/// Property is readonly
/// </summary>
public ICredentials? Credentials
{
get => _credentials;
set => throw new MemberAccessException("Credentials property of DynamicProxy is readonly");
}
private ProxyCredentials _credentials = new();
//Other code
}
现在
DynamicProxy
将引用凭据包装器而不是直接 NetworkCredentials
实例,并且该包装器类将被缓存。