CodeAngry
CodeAngry

Reputation: 12985

HttpRequestMessage configure IP in multi-IP environment

I have several IPs on a host and want to choose which is used when I make a connection from a HttpClient with a HttpRequestMessage. Can this be done without me going down to sockets and writing a simple http client myself?

I'm looking for an equivalent of bind before connect at socket level but for HttpClient... or an alternative.

Upvotes: 1

Views: 1217

Answers (1)

spender
spender

Reputation: 120450

So, if you are using the default HttpClient message handler on a Windows machine, HttpClient will be using the .Net HttpWebRequest to do its work. This in turn, relies on the ServicePointManager to provide a ServicePoint to manage connections to the URI. For any domain, you can hook into its ServicePoint to change the way it binds to an IPEndPoint.

If you take the URI that you are trying to connect to and find its ServicePoint:

var sp = ServicePointManager.FindServicePoint(uri);

then you can:

var sp = ServicePointManager.FindServicePoint(new Uri("http://www.google.com"));
sp.BindIPEndPointDelegate = (servicePoint, remoteEndPoint, retryCount) => {
    IPAddress adapterIpAddress = //your chosen adapter's IP address
    return new IPEndPoint(adapterIpAddress, 0);
};

If you want to get really jazzy, you could fold this into a DelegatingHandler so that the HttpClient takes care of everything for you:

public class RebindingHandler : DelegatingHandler
{
    private BindIPEndPoint bindHandler;
    public RebindingHandler(IEnumerable<IPAddress> adapterAddresses, 
                            HttpMessageHandler innerHandler = null)
        : base(innerHandler ?? new WebRequestHandler())
    {
        var addresses = adapterAddresses.ToList();
        if(!addresses.Any())
        {
            throw new ArgumentException();
        }
        var idx = 0;
        bindHandler = (servicePoint, remoteEndPoint, retryCount) => {
            int i = Interlocked.Increment(ref idx);
            uint i2 = unchecked((uint)i);
            int index = (int)(((long)i2) % addresses.Count);

            IPAddress adapterIpAddress = addresses[index];
            return new IPEndPoint(adapterIpAddress, 0);
        };
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var sp = ServicePointManager.FindServicePoint(request.RequestUri);
        sp.BindIPEndPointDelegate = bindHandler;
        var httpResponseMessage = await base.SendAsync(request, cancellationToken);
        return httpResponseMessage;
    }
}

Then you can:

var addresses = new List<IPAddress>(); //this contains your adapter addresses
var client = new HttpClient(new RebindingHandler(addresses));

and when you use client, the inner handler will automatically enroll the relevant ServicePoint to use a BindIPEndPoint that rotates around a bunch of IPEndPoints.

Upvotes: 2

Related Questions