I have implementation of 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);
}
}
Main point of this class is dynamically change proxy Address and Credentials if needed with SetProxy(ProxyData? data)
method since it is no longer possible to change the HttpClientHandler
properties after the first request. However after chaging Proxy with new Credentials I catch HttpRequestException
the proxy tunnel request to proxy '...' failed with status code '407'
.
After practical tests, I realized that this only happens when changing Credentials. It turns out this data is cached somehow.
I would like to clarify that the whole point of this class is to avoid re-creating HttpClient
and HttpClientHandler
. It would be ideal if I could force the HttpClient (or Handler) to not cache/flush the cache.
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
which is used for sending request in HttpClientHandler
has private readonly field called _proxyCredentials
. So after first request it caches Proxies' Credentials. Possible solution looks like:
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;
}
}
Then put it into 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
}
Now DynamicProxy
will have reference to wrapper of credentials instead of direct NetworkCredentials
instance and this wrapper class will be cached.