HttpClient.BaseAddress 的用途是什么,为什么我不能在第一次请求后更改它
What is the purpose of HttpClient.BaseAddress and why can't I change it after the first request
所以我们大多数人可能已经读到我们应该重用 HttpClient
的实例而不是使用 using
并创建新实例。这意味着我可以在我的程序中创建 HttpClient
的单个实例,并在每个请求中使用完整的 uri 字符串调用 GetAsync
。这将我带到 HttpClient
的 BaseAddress
属性。考虑以下代码:
HttpClient microsoftClient = new HttpClient() { BaseAddress = new Uri("https://www.microsoft.com/") };
HttpClient WhosebugClient = new HttpClient() { BaseAddress = new Uri("https://whosebug.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 = WhosebugClient.GetAsync("company/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access whosebug.com/company/about from the Whosebug client");
response = WhosebugClient.GetAsync("https://www.microsoft.com/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access microsoft.com/about from the Whosebug client");
microsoftClient.BaseAddress = new Uri("https://whosebug.com");
response = microsoftClient.GetAsync("company/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access whosebug.com/company/about from the microsoft client, after changing the BaseAddress");
直到最后一个块,此代码运行良好,即使使用带有 Whosebug BaseAddress
的客户端访问 Microsoft。然而,这段代码在最后一个块的开头抛出一个 InvalidOperationException
,当重新分配 BaseAddress
时,说明
'This instance has already started one or more requests. Properties can only be modified before sending the first request.'
这让我想到以下问题:
- 使用
BaseAddress
有什么好处?我总是可以在我的 GetAsync
电话中使用完整地址。它只是为了 convenience/performance 不必构建完整的请求字符串吗?我的猜测是,它只会在内部创建一个 ServicePoint
,如 this blog post 的第一段所述(或类似于 post 的东西很旧)。
- 在发送第一个请求后,我们无法更改
HttpClient
的 属性,尤其是 BaseAddress
,内部发生了什么?如果使用这个 属性 实际上会产生好处,这似乎很不方便。
对于 (1),一个常见的用例是与一台服务器交互的客户端。也许这是构建此客户端使用的后端 API。确切的详细信息将存储在客户端在启动期间读取的配置文件中。
我们可以直接访问配置,或者将从配置中读取的字符串注入每个需要构建完整 URL 的地方。或者我们可以只配置要放入依赖注入容器的 HttpClient 的 BaseAddress
,然后让消费位置注入该对象。这对我来说是一个有点意料之中的用例。
对于 (2),我认为没有 技术 限制。我认为这更多是为了拯救人们自己。由于设置 BaseAddress
并导致实际请求通过例如发出GetAsync
是单独的操作,两段单独的代码同时做这样的事情是不安全的——你可以很容易地进行比赛。因此,如果首先不允许此类竞争,则更容易推断出可能共享 HttpClient
的单个实例的多线程程序。
2 个用途:
方便。如果您从单个主机调用多个端点,并且您将基地址和端点段作为单独的字符串进行管理(非常常见),它可以帮助您避免在每次调用时进行难看的字符串连接。
鼓励最佳实践。虽然通过 GetAsync
等进行调用是线程安全的,但 HttpClient
除了 BaseAddress
之外还有几个属性,例如 DefaultRequestHeaders
,但它们不是。通常,您希望这些对于同一主机的调用是相同的,但对于不同主机的调用则不同。出于这个原因,一个 HttpClient
每个主机被调用的实例 实际上是一个很好的做法。除非你正在调用成千上万个不同的主机,否则你不需要担心这里的 the infamous socket exhaustion problem。 (即使您使用单例,底层网络堆栈也需要为每个主机打开不同的套接字。)
那么,为什么在 HttpClient
调用中指定完整地址甚至完全有效?再次,方便。该地址可能来自外部来源或用户输入,您不希望为了使用它而将其分解成多个部分。但在这种情况下,您将陷入线程安全的困境,那些非线程安全的属性可能应该完全避免。
所以我们大多数人可能已经读到我们应该重用 HttpClient
的实例而不是使用 using
并创建新实例。这意味着我可以在我的程序中创建 HttpClient
的单个实例,并在每个请求中使用完整的 uri 字符串调用 GetAsync
。这将我带到 HttpClient
的 BaseAddress
属性。考虑以下代码:
HttpClient microsoftClient = new HttpClient() { BaseAddress = new Uri("https://www.microsoft.com/") };
HttpClient WhosebugClient = new HttpClient() { BaseAddress = new Uri("https://whosebug.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 = WhosebugClient.GetAsync("company/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access whosebug.com/company/about from the Whosebug client");
response = WhosebugClient.GetAsync("https://www.microsoft.com/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access microsoft.com/about from the Whosebug client");
microsoftClient.BaseAddress = new Uri("https://whosebug.com");
response = microsoftClient.GetAsync("company/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access whosebug.com/company/about from the microsoft client, after changing the BaseAddress");
直到最后一个块,此代码运行良好,即使使用带有 Whosebug BaseAddress
的客户端访问 Microsoft。然而,这段代码在最后一个块的开头抛出一个 InvalidOperationException
,当重新分配 BaseAddress
时,说明
'This instance has already started one or more requests. Properties can only be modified before sending the first request.'
这让我想到以下问题:
- 使用
BaseAddress
有什么好处?我总是可以在我的GetAsync
电话中使用完整地址。它只是为了 convenience/performance 不必构建完整的请求字符串吗?我的猜测是,它只会在内部创建一个ServicePoint
,如 this blog post 的第一段所述(或类似于 post 的东西很旧)。 - 在发送第一个请求后,我们无法更改
HttpClient
的 属性,尤其是BaseAddress
,内部发生了什么?如果使用这个 属性 实际上会产生好处,这似乎很不方便。
对于 (1),一个常见的用例是与一台服务器交互的客户端。也许这是构建此客户端使用的后端 API。确切的详细信息将存储在客户端在启动期间读取的配置文件中。
我们可以直接访问配置,或者将从配置中读取的字符串注入每个需要构建完整 URL 的地方。或者我们可以只配置要放入依赖注入容器的 HttpClient 的 BaseAddress
,然后让消费位置注入该对象。这对我来说是一个有点意料之中的用例。
对于 (2),我认为没有 技术 限制。我认为这更多是为了拯救人们自己。由于设置 BaseAddress
并导致实际请求通过例如发出GetAsync
是单独的操作,两段单独的代码同时做这样的事情是不安全的——你可以很容易地进行比赛。因此,如果首先不允许此类竞争,则更容易推断出可能共享 HttpClient
的单个实例的多线程程序。
2 个用途:
方便。如果您从单个主机调用多个端点,并且您将基地址和端点段作为单独的字符串进行管理(非常常见),它可以帮助您避免在每次调用时进行难看的字符串连接。
鼓励最佳实践。虽然通过
GetAsync
等进行调用是线程安全的,但HttpClient
除了BaseAddress
之外还有几个属性,例如DefaultRequestHeaders
,但它们不是。通常,您希望这些对于同一主机的调用是相同的,但对于不同主机的调用则不同。出于这个原因,一个HttpClient
每个主机被调用的实例 实际上是一个很好的做法。除非你正在调用成千上万个不同的主机,否则你不需要担心这里的 the infamous socket exhaustion problem。 (即使您使用单例,底层网络堆栈也需要为每个主机打开不同的套接字。)
那么,为什么在 HttpClient
调用中指定完整地址甚至完全有效?再次,方便。该地址可能来自外部来源或用户输入,您不希望为了使用它而将其分解成多个部分。但在这种情况下,您将陷入线程安全的困境,那些非线程安全的属性可能应该完全避免。