-
Notifications
You must be signed in to change notification settings - Fork 6.1k
Description
Description
We added support for WebSockets over HTTP/2 in .NET 7 RC1. As part of that change, we also introduced a new overload of ConnectAsync that accepts an HttpMessageInvoker to allow reusing existing pooled connections.
This breaking change fixes two issues related to user input validation:
The first is dotnet/runtime#74416 where existing options available on ClientWebSocketOptions (e.g. Proxy, RemoteCertificateValidationCallback) would be silently ignored if a custom HttpMessageInvoker was also specified.
This change makes it so that if any incompatible options are set and an invoker is specified, we will throw. This gives the developer a clear signal of what must be changed.
The second issue is dotnet/runtime#74415, where a developer attempting to utilize the new feature may fall into a performance trap. THE point of WebSockets over H/2 is the ability to multiplex WebSocket streams over a single transport connection. If the user only changes the Version or VersionPolicy to allow for H/2, the implementation will use the requested protocol, but it will always open a new connection for each WebSocket, eliminating the benefit of said feature. To get the full benefit of H/2, the user must specify the invoker instance that should be reused by passing it to ConnectAsync.
This change makes it so that we throw if H/2 is requested but an invoker was not specified.
Version
.NET 7 RC 2
Previous behavior
ClientWebSocket.ConnectAsync(Uri, HttpMessageInvoker, CancellationToken) overload will silently ignore the following ClientWebSocketOptions: UseDefaultCredentials, Credentials, Proxy, ClientCertificates, RemoteCertificateValidationCallback, Cookies.
ClientWebSocket.ConnectAsync(Uri, CancellationToken) overload will accept it if the user specifies Version or VersionPolicy allowing HTTP/2, which will lead to poor performance and entirely defeating the purpose of the feature.
New behavior
ClientWebSocket.ConnectAsync(Uri, HttpMessageInvoker, CancellationToken) overload will throw an ArgumentException if any of the following ClientWebSocketOptions are set: UseDefaultCredentials, Credentials, Proxy, ClientCertificates, RemoteCertificateValidationCallback, Cookies.
ClientWebSocket.ConnectAsync(Uri, CancellationToken) overload will throw an ArgumentException if the user specifies Version or VersionPolicy allowing HTTP/2.
Type of breaking change
- Binary incompatible: Existing binaries may encounter a breaking change in behavior, such as failure to load/execute or different run-time behavior.
- Source incompatible: Source code may encounter a breaking change in behavior when targeting the new runtime/component/SDK, such as compile errors or different run-time behavior.
Reason for change
To raise awareness and propose solution to the user in the exception message instead of silently ignoring incompatible options.
Recommended action
If the user wants to use WebSockets over HTTP/2, they should use ClientWebSocket.ConnectAsync(Uri, HttpMessageInvoker, CancellationToken) overload instead of ClientWebSocket.ConnectAsync(Uri, CancellationToken) and pass a custom invoker which will be used for multiplexing.
If the user wants to pass a custom invoker to a WebSocket and at the same time set up UseDefaultCredentials, Credentials, Proxy, ClientCertificates, RemoteCertificateValidationCallback or Cookies, these must not be set on ClientWebSocketOptions. These options should be set on the HttpMessageInvoker's underlying HttpMessageHandler instead, e.g.:
var handler = new HttpClientHandler();
handler.CookieContainer = cookies;
handler.UseCookies = cookies != null;
handler.ServerCertificateCustomValidationCallback = remoteCertificateValidationCallback;
handler.Credentials = useDefaultCredentials ?
CredentialCache.DefaultCredentials :
credentials;
if (proxy == null)
{
handler.UseProxy = false;
}
else
{
handler.Proxy = proxy;
}
if (clientCertificates?.Count > 0)
{
handler.ClientCertificates.AddRange(clientCertificates);
}
var invoker = new HttpMessageInvoker(handler);
var cws = new ClientWebSocket();
await cws.ConnectAsync(uri, invoker, cancellationToken);Feature area
Networking
Affected APIs
ClientWebSocket.ConnectAsync(Uri, HttpMessageInvoker, CancellationToken);
ClientWebSocket.ConnectAsync(Uri, CancellationToken) when ClientWebSocket.Options.HttpVersion or ClientWebSocket.Options.HttpVersionPolicy is set.