Search code examples
c#proxyhttpclient

Proxy credentials are cached after first request. Implementation of IWebProxy


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

    }
}

Solution

  • 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.