Reputation: 12985
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
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