diff --git a/docs/fundamentals/networking/websockets.md b/docs/fundamentals/networking/websockets.md new file mode 100644 index 0000000000000..48e1592b565f9 --- /dev/null +++ b/docs/fundamentals/networking/websockets.md @@ -0,0 +1,102 @@ +--- +title: WebSockets support in .NET +description: Learn how to web sockets with the ClientWebSockets in .NET. +author: greenEkatherine +ms.author: esokolov +ms.date: 10/27/2022 +--- + +# WebSockets support in .NET + +The WebSocket protocol enables two-way communication between a client and a remote host. The exposes the ability to establish WebSocket connection via an opening handshake, it is created and sent by the `ConnectAsync` method. + +## Differences in HTTP/1.1 and HTTP/2 WebSockets + +WebSockets over HTTP/1.1 uses a single TCP connection, therefore it is managed by connection-wide headers, for more information, see [RFC 6455](https://www.rfc-editor.org/rfc/rfc6455). Consider the following example of how to establish WebSocket over HTTP/1.1: + +```c# +Uri uri = new("ws://corefx-net-http11.azurewebsites.net/WebSocket/EchoWebSocket.ashx"); + +using ClientWebSocket ws = new(); +await ws.ConnectAsync(uri, default); + +var bytes = new byte[1024]; +var result = await ws.ReceiveAsync(bytes, default); +string res = Encoding.UTF8.GetString(bytes, 0, result.Count); + +await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client closed", default); +``` + +A different approach must be taken with HTTP/2 due to its multiplexing nature. WebSockets are established per stream, for more information, see [RFC 8441](https://www.rfc-editor.org/rfc/rfc8441). With HTTP/2 it is possible to use one connection for multiple web socket streams together with ordinary HTTP streams and extend HTTP/2's more efficient use of the network to WebSockets. There is a special overload of which accepts an to allow reusing existing pooled connections: + +```c# +using SocketsHttpHandler handler = new(); +using ClientWebSocket ws = new(); +await ws.ConnectAsync(uri, new HttpMessageInvoker(handler), cancellationToken); +``` + +## Set up HTTP version and policy + +By default, `ClientWebSocket` uses HTTP/1.1 to send an opening handshake and allows downgrade. In .NET 7 web sockets over HTTP/2 are available. It can be changed before calling `ConnectAsync`: + +```c# +using SocketsHttpHandler handler = new(); +using ClientWebSocket ws = new(); + +ws.Options.HttpVersion = HttpVersion.Version20; +ws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher; + +await ws.ConnectAsync(uri, new HttpMessageInvoker(handler), cancellationToken); +``` + +## Incompatible options + +`ClientWebSocket` has properties that the user can set up before the connection is established. However, when `HttpMessageInvoker` is provided, it also has these properties. To avoid ambiguity, in that case, properties should be set on `HttpMessageInvoker`, and `ClientWebSocketOptions` should have default values. Otherwise, if `ClientWebSocketOptions` are changed, overload of `ConnectAsync` will throw an . + +```c# +using HttpClientHandler handler = new() +{ + CookieContainer = cookies; + UseCookies = cookies != null; + ServerCertificateCustomValidationCallback = remoteCertificateValidationCallback; + Credentials = useDefaultCredentials + ? CredentialCache.DefaultCredentials + : credentials; +}; +if (proxy is null) +{ + handler.UseProxy = false; +} +else +{ + handler.Proxy = proxy; +} +if (clientCertificates?.Count > 0) +{ + handler.ClientCertificates.AddRange(clientCertificates); +} +HttpMessageInvoker invoker = new(handler); +using ClientWebSocket cws = new(); +await cws.ConnectAsync(uri, invoker, cancellationToken); +``` + +## Compression + +The WebSocket protocol supports per-message deflate as defined in [RFC 7692](https://tools.ietf.org/html/rfc7692#section-7). It is controlled by . When present, the options are sent to the server during the handshake phase. If the server supports per-message-deflate and the options are accepted, the `ClientWebSocket` instance will be created with compression enabled by default for all messages. + +```c# +using ClientWebSocket ws = new() +{ + Options = + { + DangerousDeflateOptions = new WebSocketDeflateOptions() + { + ClientMaxWindowBits = 10, + ServerMaxWindowBits = 10 + } + } +}; +``` + +> [!IMPORTANT] +> Before using compression, please be aware that enabling it makes the application subject to CRIME/BREACH type of attacks, for more information, see [CRIME](https://en.wikipedia.org/wiki/CRIME) and [BREACH](https://en.wikipedia.org/wiki/BREACH). It is strongly advised to turn off compression when sending data containing secrets by specifying the `DisableCompression` flag for such messages. diff --git a/docs/fundamentals/toc.yml b/docs/fundamentals/toc.yml index 6f176aeab6873..4143ad9b511d7 100644 --- a/docs/fundamentals/toc.yml +++ b/docs/fundamentals/toc.yml @@ -2617,6 +2617,10 @@ items: href: networking/sockets/socket-services.md - name: Use TcpClient and TcpListener href: networking/sockets/tcp-classes.md + - name: WebSockets + items: + - name: WebSockets support + href: networking/websockets.md - name: File globbing href: ../core/extensions/file-globbing.md - name: Primitives library