From 6658c005cd9709f6cb8a6bcee7467b16dfeb5754 Mon Sep 17 00:00:00 2001 From: Wade Pickett Date: Wed, 28 Aug 2024 11:11:32 -0700 Subject: [PATCH] WN: WebSocket Keep-Alive Timeout: Edit & code moved to project (#33460) * WN: WebSocket Keep-Alive Timeout: Edit & code moved to project * Updates per review suggestions --- .../includes/websockets-keep-alive-timeout.md | 27 ++- .../Controllers/WebSocketController.cs | 47 +++++ .../Program.cs | 46 +++++ .../WebSocketsSample.csproj | 9 + .../wwwroot/index.html | 168 ++++++++++++++++++ 5 files changed, 283 insertions(+), 14 deletions(-) create mode 100644 aspnetcore/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/Controllers/WebSocketController.cs create mode 100644 aspnetcore/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/Program.cs create mode 100644 aspnetcore/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/WebSocketsSample.csproj create mode 100644 aspnetcore/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/wwwroot/index.html diff --git a/aspnetcore/release-notes/aspnetcore-9/includes/websockets-keep-alive-timeout.md b/aspnetcore/release-notes/aspnetcore-9/includes/websockets-keep-alive-timeout.md index 1bd5564cf2..e12e7daf58 100644 --- a/aspnetcore/release-notes/aspnetcore-9/includes/websockets-keep-alive-timeout.md +++ b/aspnetcore/release-notes/aspnetcore-9/includes/websockets-keep-alive-timeout.md @@ -1,21 +1,20 @@ ### Keep-Alive Timeout for WebSockets -The [WebSockets middleware](https://learn.microsoft.com/aspnet/core/fundamentals/websockets#configure-the-middleware) can now be configured for keep alive timeouts. +The [WebSockets middleware](https://learn.microsoft.com/aspnet/core/fundamentals/websockets#configure-the-middleware) can now be configured for keep-alive timeouts. -The keep alive timeout will abort the WebSocket and throw from `WebSocket.ReceiveAsync` if a ping frame from the websocket protocol is sent by the server and the client doesn't reply with a pong frame within the specified timeout. The ping frame is automatically sent by the server and configured with `KeepAliveInterval`. This option is useful when wanting to detect connections that might be slow or ungracefully disconnected. +The keep-alive timeout aborts the WebSocket connection and throws an exception from `WebSocket.ReceiveAsync` if both of the following conditions are met: -The keep alive timeout can be configured globally for the WebSocket middleware: -```csharp -app.UseWebSockets(new WebSocketOptions { KeepAliveInterval = TimeSpan.FromSeconds(15) }); -``` +* The server sends a ping frame using the websocket protocol. +* The client doesn't reply with a pong frame within the specified timeout. + +The server automatically sends the ping frame and configures it with `KeepAliveInterval`. + +The keep-alive timeout setting is useful for detecting connections that might be slow or ungracefully disconnected. + +The keep-alive timeout can be configured globally for the WebSocket middleware: + +[!code-csharp[](~/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/Program.cs?name=snippet_WebSocket_KeepAliveTimeout_Global)] Or configured per accepted WebSocket: -```csharp -app.Run(async (context) => -{ - using var webSocket = await context.WebSockets.AcceptWebSocketAsync( - new WebSocketAcceptContext { KeepAliveTimeout = TimeSpan.FromSeconds(15) }); - // ... -} -``` +[!code-csharp[](~/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/Program.cs?name=snippet_KeepAliveTimeout_Per_Accepted_WebSocket)] diff --git a/aspnetcore/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/Controllers/WebSocketController.cs b/aspnetcore/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/Controllers/WebSocketController.cs new file mode 100644 index 0000000000..5e178a6945 --- /dev/null +++ b/aspnetcore/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/Controllers/WebSocketController.cs @@ -0,0 +1,47 @@ +using System.Net.WebSockets; +using Microsoft.AspNetCore.Mvc; + +namespace WebSocketsSample.Controllers; + +#region snippet_Controller_Connect +public class WebSocketController : ControllerBase +{ + [Route("/ws")] + public async Task Get() + { + if (HttpContext.WebSockets.IsWebSocketRequest) + { + using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); + await Echo(webSocket); + } + else + { + HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest; + } + } + #endregion + + private static async Task Echo(WebSocket webSocket) + { + var buffer = new byte[1024 * 4]; + var receiveResult = await webSocket.ReceiveAsync( + new ArraySegment(buffer), CancellationToken.None); + + while (!receiveResult.CloseStatus.HasValue) + { + await webSocket.SendAsync( + new ArraySegment(buffer, 0, receiveResult.Count), + receiveResult.MessageType, + receiveResult.EndOfMessage, + CancellationToken.None); + + receiveResult = await webSocket.ReceiveAsync( + new ArraySegment(buffer), CancellationToken.None); + } + + await webSocket.CloseAsync( + receiveResult.CloseStatus.Value, + receiveResult.CloseStatusDescription, + CancellationToken.None); + } +} diff --git a/aspnetcore/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/Program.cs b/aspnetcore/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/Program.cs new file mode 100644 index 0000000000..79062c5502 --- /dev/null +++ b/aspnetcore/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/Program.cs @@ -0,0 +1,46 @@ +#define KEEP_ALIVE_GLOBAL //KEEP_ALIVE_PER_WEBSOCKET + +#if KEEP_ALIVE_GLOBAL + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); + +var app = builder.Build(); + +// +app.UseWebSockets(new WebSocketOptions { KeepAliveTimeout = TimeSpan.FromSeconds(15) }); +// + +app.UseDefaultFiles(); +app.UseStaticFiles(); + +app.MapControllers(); + +app.Run(); + +#elif KEEP_ALIVE_PER_WEBSOCKET + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); + +var app = builder.Build(); + +app.UseDefaultFiles(); +app.UseStaticFiles(); + +app.MapControllers(); + +// Configured per accepted WebSocket: +// +app.Run(async (context) => +{ + using var webSocket = await context.WebSockets.AcceptWebSocketAsync( + new WebSocketAcceptContext { KeepAliveTimeout = TimeSpan.FromSeconds(15) }); + + // ... +}); +// + +#endif diff --git a/aspnetcore/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/WebSocketsSample.csproj b/aspnetcore/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/WebSocketsSample.csproj new file mode 100644 index 0000000000..6568b3dcfb --- /dev/null +++ b/aspnetcore/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/WebSocketsSample.csproj @@ -0,0 +1,9 @@ + + + + net9.0 + enable + enable + + + diff --git a/aspnetcore/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/wwwroot/index.html b/aspnetcore/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/wwwroot/index.html new file mode 100644 index 0000000000..f4d96a68c0 --- /dev/null +++ b/aspnetcore/release-notes/aspnetcore-9/samples/WebSocketsKeepAliveTimeoutExample/wwwroot/index.html @@ -0,0 +1,168 @@ + + + + + + + + +

WebSocket Sample Application

+

Ready to connect...

+
+ + + +
+

+
+ + + + +
+ +

Communication Log

+ + + + + + + + + + +
FromToData
+ + + +