Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the purpose of HttpClient.BaseAddress and why can't I change it after the first request

So most of us have probably read that we are supposed to reuse instances of HttpClient instead of using using and creating new ones. That means I can just create a single instance of HttpClient in my program and call GetAsync using a full uri string per request. This leads me to the BaseAddress property of HttpClient. Consider the following code:

HttpClient microsoftClient = new HttpClient() { BaseAddress = new Uri("https://www.microsoft.com/") };
HttpClient stackoverflowClient = new HttpClient() { BaseAddress = new Uri("https://stackoverflow.com/") };

var response = microsoftClient.GetAsync("about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access microsoft.com/about from the microsoft client");

response = microsoftClient.GetAsync("trademarks").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access microsoft.com/trademarks from the microsoft client");

response = stackoverflowClient.GetAsync("company/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access stackoverflow.com/company/about from the stackoverflow client");

response = stackoverflowClient.GetAsync("https://www.microsoft.com/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access microsoft.com/about from the stackoverflow client");

microsoftClient.BaseAddress = new Uri("https://stackoverflow.com");
response = microsoftClient.GetAsync("company/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access stackoverflow.com/company/about from the microsoft client, after changing the BaseAddress");

Up until the last block this code runs fine, even when using the client with the stackoverflow BaseAddress to access Microsoft. However this code throws an InvalidOperationException at the beginning of the last block, when reassigning the BaseAddress, stating

'This instance has already started one or more requests. Properties can only be modified before sending the first request.'

This leads me to the following questions:

  1. What is the benefit of using BaseAddress at all? I could just always use the full address in my GetAsync call. Is it just for the convenience/performance of not having to build the the full request string? My guess was that it would only create a single ServicePoint internally as described in the first paragraph of this blog post (or something similar as the post is quite old).
  2. What happens internally that we can't change a property of HttpClient, especially BaseAddress, after sending the first request? This seems quite inconvenient if using this property actually yields benefits.
like image 596
Jerome Reinländer Avatar asked Jul 13 '18 07:07

Jerome Reinländer


People also ask

What is BaseAddress HttpClient?

Gets or sets the base address of Uniform Resource Identifier (URI) of the Internet resource used when sending requests. public: property Uri ^ BaseAddress { Uri ^ get(); void set(Uri ^ value); };

How do I set HttpClient BaseAddress?

All you have to do is set the BaseAddress on the HttpClient: var httpClient = new HttpClient(); httpClient. BaseAddress = new Uri("https://peterdaugaardrasmussen.com/"); var response = await httpClient.

What is HttpClient GetAsync?

GetAsync(Uri) Send a GET request to the specified Uri as an asynchronous operation. GetAsync(String, HttpCompletionOption) Send a GET request to the specified Uri with an HTTP completion option as an asynchronous operation.


2 Answers

For (1), a common use case would be a client which interacts with exactly one server. Maybe it's the back-end API that this client was built to use. The exact details will be stored in a config file that the client reads during startup.

We could litter our code with direct accesses to the config, or inject the string read from config into every place that needs to construct a full URL. Or we could just configure the BaseAddress of the HttpClient we're putting into our Dependency Injection container and just let the consuming locations have that object injected. That for me is a somewhat expected use case.

For (2), I don't think there's a technical limitation. I think this is more there to save people from themselves. Since setting a BaseAddress and causing an actual request to go out via e.g. GetAsync are separate actions, it would be unsafe for two separate pieces of code to be doing such a thing at the same time - you could easily get races. So it's easier to reason about multi-threaded programs that may be sharing a single instance of HttpClient if such races aren't allowed in the first place.

like image 111
Damien_The_Unbeliever Avatar answered Nov 15 '22 07:11

Damien_The_Unbeliever


2 purposes:

  1. Convenience. If you're calling many endpoints off a single host, and you're managing the base address and endpoint segments as separate strings (very common), it helps you avoid doing ugly string concatenation on every call.

  2. Encouraging best practices. Although making calls via GetAsync, etc. is thread safe, HttpClient has several properties in addition to BaseAddress, such as DefaultRequestHeaders, that are not. Very typically, you want these to be the same for calls to the same host but not for calls to different ones. For this reason, an HttpClient instance per host being called is actually a very good practice. Unless you're calling thousands of different hosts, you don't need to worry about the infamous socket exhaustion problem here. (And even if you were using a singleton, the underlying network stack would need to open a different socket per host anyway.)

So why does specifying a full address on an HttpClient call even work at all? Again, convenience. The address might be coming from an external source or user input, and you wouldn't want to have to break it into pieces in order to use it. But in this case, you are on the hook for thread safety, and those non-thread-safe properties should probably just be avoided entirely.

like image 29
Todd Menier Avatar answered Nov 15 '22 07:11

Todd Menier