WN: WebSocket Keep-Alive Timeout: Edit & code moved to project (#33460)
* WN: WebSocket Keep-Alive Timeout: Edit & code moved to project * Updates per review suggestionspull/33471/head
parent
b957833ad8
commit
6658c005cd
|
@ -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)]
|
||||
|
|
|
@ -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<byte>(buffer), CancellationToken.None);
|
||||
|
||||
while (!receiveResult.CloseStatus.HasValue)
|
||||
{
|
||||
await webSocket.SendAsync(
|
||||
new ArraySegment<byte>(buffer, 0, receiveResult.Count),
|
||||
receiveResult.MessageType,
|
||||
receiveResult.EndOfMessage,
|
||||
CancellationToken.None);
|
||||
|
||||
receiveResult = await webSocket.ReceiveAsync(
|
||||
new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||
}
|
||||
|
||||
await webSocket.CloseAsync(
|
||||
receiveResult.CloseStatus.Value,
|
||||
receiveResult.CloseStatusDescription,
|
||||
CancellationToken.None);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
// <snippet_WebSocket_KeepAliveTimeout_Global>
|
||||
app.UseWebSockets(new WebSocketOptions { KeepAliveTimeout = TimeSpan.FromSeconds(15) });
|
||||
// </snippet_WebSocket_KeepAliveTimeout_Global>
|
||||
|
||||
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:
|
||||
// <snippet_KeepAliveTimeout_Per_Accepted_WebSocket>
|
||||
app.Run(async (context) =>
|
||||
{
|
||||
using var webSocket = await context.WebSockets.AcceptWebSocketAsync(
|
||||
new WebSocketAcceptContext { KeepAliveTimeout = TimeSpan.FromSeconds(15) });
|
||||
|
||||
// ...
|
||||
});
|
||||
// </snippet_KeepAliveTimeout_Per_Accepted_WebSocket>
|
||||
|
||||
#endif
|
|
@ -0,0 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,168 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title></title>
|
||||
<style>
|
||||
table {
|
||||
border: 0
|
||||
}
|
||||
|
||||
.commslog-data {
|
||||
font-family: Consolas, Courier New, Courier, monospace;
|
||||
}
|
||||
|
||||
.commslog-server {
|
||||
background-color: red;
|
||||
color: white
|
||||
}
|
||||
|
||||
.commslog-client {
|
||||
background-color: green;
|
||||
color: white
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>WebSocket Sample Application</h1>
|
||||
<p id="stateLabel">Ready to connect...</p>
|
||||
<div>
|
||||
<label for="connectionUrl">WebSocket Server URL:</label>
|
||||
<input id="connectionUrl" />
|
||||
<button id="connectButton" type="submit">Connect</button>
|
||||
</div>
|
||||
<p></p>
|
||||
<div>
|
||||
<label for="sendMessage">Message to send:</label>
|
||||
<input id="sendMessage" disabled />
|
||||
<button id="sendButton" type="submit" disabled>Send</button>
|
||||
<button id="closeButton" disabled>Close Socket</button>
|
||||
</div>
|
||||
|
||||
<h2>Communication Log</h2>
|
||||
<table style="width: 800px">
|
||||
<thead>
|
||||
<tr>
|
||||
<td style="width: 100px">From</td>
|
||||
<td style="width: 100px">To</td>
|
||||
<td>Data</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="commsLog">
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
var connectionUrl = document.getElementById("connectionUrl");
|
||||
var connectButton = document.getElementById("connectButton");
|
||||
var stateLabel = document.getElementById("stateLabel");
|
||||
var sendMessage = document.getElementById("sendMessage");
|
||||
var sendButton = document.getElementById("sendButton");
|
||||
var commsLog = document.getElementById("commsLog");
|
||||
var closeButton = document.getElementById("closeButton");
|
||||
var socket;
|
||||
|
||||
var scheme = document.location.protocol === "https:" ? "wss" : "ws";
|
||||
var port = document.location.port ? (":" + document.location.port) : "";
|
||||
|
||||
connectionUrl.value = scheme + "://" + document.location.hostname + port + "/ws" ;
|
||||
|
||||
function updateState() {
|
||||
function disable() {
|
||||
sendMessage.disabled = true;
|
||||
sendButton.disabled = true;
|
||||
closeButton.disabled = true;
|
||||
}
|
||||
function enable() {
|
||||
sendMessage.disabled = false;
|
||||
sendButton.disabled = false;
|
||||
closeButton.disabled = false;
|
||||
}
|
||||
|
||||
connectionUrl.disabled = true;
|
||||
connectButton.disabled = true;
|
||||
|
||||
if (!socket) {
|
||||
disable();
|
||||
} else {
|
||||
switch (socket.readyState) {
|
||||
case WebSocket.CLOSED:
|
||||
stateLabel.innerHTML = "Closed";
|
||||
disable();
|
||||
connectionUrl.disabled = false;
|
||||
connectButton.disabled = false;
|
||||
break;
|
||||
case WebSocket.CLOSING:
|
||||
stateLabel.innerHTML = "Closing...";
|
||||
disable();
|
||||
break;
|
||||
case WebSocket.CONNECTING:
|
||||
stateLabel.innerHTML = "Connecting...";
|
||||
disable();
|
||||
break;
|
||||
case WebSocket.OPEN:
|
||||
stateLabel.innerHTML = "Open";
|
||||
enable();
|
||||
break;
|
||||
default:
|
||||
stateLabel.innerHTML = "Unknown WebSocket State: " + htmlEscape(socket.readyState);
|
||||
disable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closeButton.onclick = function () {
|
||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
||||
alert("socket not connected");
|
||||
}
|
||||
socket.close(1000, "Closing from client");
|
||||
};
|
||||
|
||||
sendButton.onclick = function () {
|
||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
||||
alert("socket not connected");
|
||||
}
|
||||
var data = sendMessage.value;
|
||||
socket.send(data);
|
||||
commsLog.innerHTML += '<tr>' +
|
||||
'<td class="commslog-client">Client</td>' +
|
||||
'<td class="commslog-server">Server</td>' +
|
||||
'<td class="commslog-data">' + htmlEscape(data) + '</td></tr>';
|
||||
};
|
||||
|
||||
connectButton.onclick = function() {
|
||||
stateLabel.innerHTML = "Connecting";
|
||||
socket = new WebSocket(connectionUrl.value);
|
||||
socket.onopen = function (event) {
|
||||
updateState();
|
||||
commsLog.innerHTML += '<tr>' +
|
||||
'<td colspan="3" class="commslog-data">Connection opened</td>' +
|
||||
'</tr>';
|
||||
};
|
||||
socket.onclose = function (event) {
|
||||
updateState();
|
||||
commsLog.innerHTML += '<tr>' +
|
||||
'<td colspan="3" class="commslog-data">Connection closed. Code: ' + htmlEscape(event.code) + '. Reason: ' + htmlEscape(event.reason) + '</td>' +
|
||||
'</tr>';
|
||||
};
|
||||
socket.onerror = updateState;
|
||||
socket.onmessage = function (event) {
|
||||
commsLog.innerHTML += '<tr>' +
|
||||
'<td class="commslog-server">Server</td>' +
|
||||
'<td class="commslog-client">Client</td>' +
|
||||
'<td class="commslog-data">' + htmlEscape(event.data) + '</td></tr>';
|
||||
};
|
||||
};
|
||||
|
||||
function htmlEscape(str) {
|
||||
return str.toString()
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue