Supporto di WebSocket in ASP.NET Core

L'articolo contiene l'introduzione all'uso di oggetti WebSocket in ASP.NET Core. WebSocket (RFC 6455) è un protocollo che consente canali di comunicazione persistente bidirezionale su connessioni TCP. Viene usato nelle app che sfruttano comunicazioni veloci e in tempo reale, ad esempio le app di chat, i dashboard e le app di gioco.

Visualizzare o scaricare il codice di esempio (come scaricare, come eseguire).

Supporto di WebSocket Http/2

L'uso di WebSocket su HTTP/2 sfrutta le nuove funzionalità, ad esempio:

  • Compressione dell'intestazione.
  • Multiplexing, che riduce il tempo e le risorse necessarie quando si effettuano più richieste al server.

Queste funzionalità supportate sono disponibili in in Kestrel tutte le piattaforme abilitate per HTTP/2. La negoziazione della versione è automatica nei browser e Kestrel, quindi non sono necessarie nuove API.

.NET 7 ha introdotto websocket sul supporto HTTP/2 per Kestrel, il SignalR client JavaScript e SignalR con Blazor WebAssembly.

Nota

I WebSocket HTTP/2 usano le richieste CONNECT anziché GET, quindi le route e i controller personalizzati potrebbero richiedere l'aggiornamento. Per altre informazioni, vedere Aggiungere il supporto webSocket HTTP/2 per i controller esistenti in questo articolo.

Chrome e Edge hanno HTTP/2 WebSocket abilitati per impostazione predefinita ed è possibile abilitarlo in FireFox nella about:config pagina con il network.http.spdy.websockets flag .

I WebSocket sono stati originariamente progettati per HTTP/1.1, ma sono stati adattati per funzionare su HTTP/2. (RFC 8441)

SignalR

ASP.NET Core SignalR è una libreria che semplifica l'aggiunta di funzionalità Web in tempo reale alle app. Laddove possibile, usa oggetti WebSocket.

Per la maggior parte delle applicazioni, è consigliabile SignalR anziché webSocket non elaborati. SignalR:

  • Fornisce il fallback del trasporto per gli ambienti in cui WebSocket non è disponibile.
  • Fornisce un modello di app di chiamata di routine remota di base.
  • Non presenta uno svantaggio significativo delle prestazioni rispetto all'uso di WebSocket non elaborati nella maggior parte degli scenari.

WebSockets su HTTP/2 è supportato per:

  • ASP.NET client JavaScript core SignalR
  • ASP.NET Core SignalR con Blazor WebAssembly

Per alcune app, gRPC in .NET offre un'alternativa ai WebSocket.

Prerequisiti

  • Qualsiasi sistema operativo che supporta ASP.NET Core:
    • Windows 7/Windows Server 2008 o versioni successive
    • Linux
    • macOS
  • Se l'app viene eseguita in Windows con IIS:
    • Windows 8/Windows Server 2012 o versioni successive
    • IIS 8/IIS 8 Express
    • I WebSocket devono essere abilitati. Vedere la sezione supporto di IIS/IIS Express.
  • Se l'app viene eseguita in HTTP.sys:
    • Windows 8/Windows Server 2012 o versioni successive
  • Per i browser supportati, vedere È possibile usare.

Configurare il middleware

Aggiungere il middleware WebSockets in Program.cs:

app.UseWebSockets();

È possibile configurare le impostazioni seguenti:

  • KeepAliveInterval: la frequenza di invio di frame "ping" al client per garantire che i proxy tengano aperta la connessione. Il valore predefinito è due minuti.
  • AllowedOrigins: elenco di valori dell'intestazione Origin consentiti per le richieste WebSocket. Per impostazione predefinita, tutte le origini sono consentite. Per altre informazioni, vedere Restrizione dell'origine WebSocket in questo articolo.
var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

app.UseWebSockets(webSocketOptions);

Accettare le richieste WebSocket

In un secondo momento nel ciclo di vita della richiesta (più avanti in Program.cs o in un metodo di azione, ad esempio) controllare se si tratta di una richiesta WebSocket e accettare la richiesta WebSocket.

L'esempio seguente è riportato più avanti in Program.cs:

app.Use(async (context, next) =>
{
    if (context.Request.Path == "/ws")
    {
        if (context.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            context.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }
    else
    {
        await next(context);
    }

});

Una richiesta WebSocket può arrivare in qualsiasi URL, ma questo codice di esempio accetta solo le richieste per /ws.

Un approccio simile può essere adottato in un metodo controller:

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;
        }
    }

Quando si usa un oggetto WebSocket, è necessario mantenere la pipeline middleware in esecuzione per la durata della connessione. Se si cerca di inviare o ricevere un messaggio WebSocket dopo che la pipeline middleware è terminata, si potrebbe ottenere un'eccezione simile alla seguente:

System.Net.WebSockets.WebSocketException (0x80004005): The remote party closed the WebSocket connection without completing the close handshake. ---> System.ObjectDisposedException: Cannot write to the response body, the response has completed.
Object name: 'HttpResponseStream'.

Se si sta usando un servizio in background per scrivere i dati in un oggetto WebSocket, assicurarsi di mantenere la pipeline middleware in esecuzione. A tale scopo, usare TaskCompletionSource<TResult>. Passare TaskCompletionSource al servizio in background e fare in modo che chiami TrySetResult al termine dell'uso dell'oggetto WebSocket. Quindi assegnare await alla proprietà Task durante la richiesta, come mostrato nell'esempio seguente:

app.Run(async (context) =>
{
    using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
    var socketFinishedTcs = new TaskCompletionSource<object>();

    BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

    await socketFinishedTcs.Task;
});

L'eccezione chiusa WebSocket può verificarsi anche quando si torna troppo presto da un metodo di azione. Quando si accetta un socket in un metodo di azione, attendere il completamento del codice che usa il socket prima di tornare dal metodo di azione.

Per attendere il completamento del socket, non utilizzare mai Task.Wait, Task.Result o chiamate di blocco simili perché ciò può provocare gravi problemi di threading. Usare sempre await.

Aggiungere il supporto WebSocket HTTP/2 per i controller esistenti

.NET 7 ha introdotto websocket sul supporto HTTP/2 per Kestrel, il SignalR client JavaScript e SignalR con Blazor WebAssembly. Http/2 WebSocket usano le richieste CONNECT anziché GET. Se in precedenza è stato usato [HttpGet("/path")] nel metodo di azione del controller per le richieste Websocket, aggiornarlo in modo da usarlo [Route("/path")] .

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;
        }
    }

Compressione

Avviso

L'abilitazione della compressione sulle connessioni crittografate può rendere un'app soggetta a CRIME/BREACH attacchi. Se si inviano informazioni riservate, evitare di abilitare la compressione o usare WebSocketMessageFlags.DisableCompression quando si chiama WebSocket.SendAsync. Questo vale per entrambi i lati del WebSocket. Si noti che l'API WebSocket nel browser non ha la configurazione per disabilitare la compressione per invio.

Se si desidera la compressione dei messaggi su WebSocket, il codice accept deve specificare che consente la compressione come indicato di seguito:

using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(
    new WebSocketAcceptContext { DangerousEnableCompression = true }))
{

}

WebSocketAcceptContext.ServerMaxWindowBits e WebSocketAcceptContext.DisableServerContextTakeover sono opzioni avanzate che controllano il funzionamento della compressione.

La compressione viene negoziata tra il client e il server quando viene stabilita per la prima volta una connessione. Per altre informazioni sulla negoziazione, vedere Compression Extensions for WebSocket RFC (Estensioni di compressione per RFC WebSocket).

Nota

Se la negoziazione di compressione non viene accettata dal server o dal client, la connessione viene ancora stabilita. Tuttavia, la connessione non usa la compressione durante l'invio e la ricezione di messaggi.

Inviare e ricevere messaggi

Il AcceptWebSocketAsync metodo aggiorna la connessione TCP a una connessione WebSocket e fornisce un WebSocket oggetto . Usare l'oggetto WebSocket per inviare e ricevere messaggi.

Il codice illustrato in precedenza che accetta la richiesta WebSocket passa l'oggetto WebSocket a un metodo Echo. Il codice riceve un messaggio e lo reinvia immediatamente. I messaggi vengono inviati e ricevuti in un ciclo, fino a quando il client non chiude la connessione:

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);
}

Quando si accetta la connessione WebSocket prima che inizi il ciclo, la pipeline del middleware termina. Dopo la chiusura del socket, la pipeline si arresta, In altri termini, quando viene accettato il WebSocket, lo spostamento in avanti della richiesta nella pipeline si interrompe. Quando il ciclo viene completato e il socket viene chiuso, la richiesta torna ad avanzare nella pipeline.

Gestire le disconnessioni del client

Il server non viene informato automaticamente quando il client si disconnette a causa della perdita di connettività. Il server riceve un messaggio di disconnessione solo se inviato dal client, ma questo non è possibile in caso di interruzione della connessione Internet. Se si vuole intervenire quando si verifica una situazione di questo tipo, impostare un timeout per segnalare che non sono stati ricevuti messaggi dal client entro un determinato intervallo di tempo.

Se il client non invia sempre messaggi e non si vuole scadere solo perché la connessione diventa inattiva, fare in modo che il client usi un timer per inviare un messaggio ping ogni X secondi. Nel server, se un messaggio non è arrivato entro 2*X secondi dopo quello precedente, terminare la connessione e segnalare che il client è disconnesso. Attendere il doppio del tempo previsto per tenere conto di eventuali ritardi della rete che potrebbero trattenere il messaggio ping.

Restrizione per le origini WebSocket

La protezione fornita da CORS non si applica agli oggetti WebSocket. I browser non:

  • Eseguono richieste CORS preventive.
  • Rispettano le restrizioni specificate nelle intestazioni Access-Control quando eseguono richieste WebSocket.

I browser, tuttavia, inviano l'intestazione Origin quando rilasciano richieste WebSocket. Le applicazioni devono essere configurate per la convalida di queste intestazioni per assicurarsi che siano consentiti solo WebSocket provenienti dalle origini previste.

Se si ospita il server in "https://server.com" e ospitare il client in "https://client.com", aggiungere "https://client.com" all'elenco AllowedOrigins per WebSocket da verificare.

var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

webSocketOptions.AllowedOrigins.Add("https://client.com");
webSocketOptions.AllowedOrigins.Add("https://www.client.com");

app.UseWebSockets(webSocketOptions);

Nota

L'intestazione Origin viene controllata dal client e, come l'intestazione Referer, può essere falsificata. Non usare queste intestazioni come meccanismo di autenticazione.

Supporto di IIS/IIS Express

Windows Server 2012 o versioni successive e Windows 8 o versioni successive con IIS/IIS Express 8 o versioni successive include il supporto per il protocollo WebSocket, ma non per WebSockets su HTTP/2.

Nota

Gli oggetti WebSocket sono sempre abilitati quando si usa IIS Express.

Abilitazione di oggetti WebSocket in IIS

Per abilitare il supporto per il protocollo WebSocket in Windows Server 2012 o versioni successive:

Nota

Questi passaggi non sono necessari quando si usa IIS Express

  1. Usare la procedura guidata Aggiungi ruoli e funzionalità accessibile tramite il menu Gestisci o il collegamento in Server Manager.
  2. Selezionare Installazione basata su ruoli o basata su funzionalità. Selezionare Avanti.
  3. Selezionare il server appropriato (il server locale è selezionato per impostazione predefinita). Selezionare Avanti.
  4. Espandere Server Web (IIS) nella struttura Ruoli, espandere Server Web, quindi Sviluppo applicazioni.
  5. Selezionare Protocollo WebSocket. Selezionare Avanti.
  6. Se non sono necessarie le funzionalità aggiuntive, selezionare Avanti.
  7. Selezionare Installa.
  8. Al termine dell'installazione, selezionare Chiudi per chiudere la procedura guidata.

Per abilitare il supporto per il protocollo WebSocket in Windows 8 o versioni successive:

Nota

Questi passaggi non sono necessari quando si usa IIS Express

  1. Passare a Pannello di controllo>Programmi>Programmi e funzionalità>Disattivare o attivare le funzionalità di Windows (a sinistra dello schermo).
  2. Espandere i nodi seguenti: Internet Information Services>Servizi Web>Funzionalità per lo sviluppo di applicazioni.
  3. Selezionare la funzionalità Protocollo WebSocket. Seleziona OK.

Disabilitare WebSocket quando si usa socket.io su node.js

Se si usa il supporto WebSocket in socket.io in Node.js, disabilitare il modulo WebSocket IIS predefinito usando l'elemento webSocket in web.config o applicationHost.config. Se questo passaggio non viene eseguito, il modulo WebSocket IIS tenta di gestire la comunicazione WebSocket anziché Node.js e l'app.

<system.webServer>
  <webSocket enabled="false" />
</system.webServer>

Esempio di app

L'app di esempio inclusa in questo articolo è un'app echo. Dispone di una pagina Web che effettua connessioni WebSocket e il server invia nuovamente tutti i messaggi ricevuti al client. L'app di esempio supporta WebSocket su HTTP/2 quando si usa un framework di destinazione di .NET 7 o versione successiva.

Eseguire l'app:

  • Per eseguire l'app in Visual Studio: aprire il progetto di esempio in Visual Studio e premere CTRL+F5 per l'esecuzione senza il debugger.
  • Per eseguire l'app in una shell dei comandi: eseguire il comando dotnet run e passare a un browser a http://localhost:<port>.

La pagina Web mostra lo stato della connessione:

Stato iniziale della pagina Web prima della connessione WebSocket

Selezionare Connect (Connetti) per inviare una richiesta WebSocket per l'URL indicato. Immettere un messaggio di prova e selezionare Send (Invia). Al termine dell'operazione, selezionare Close Socket (Chiudi socket). La sezione Comminication Log (Registrazione comunicazione) riporta tutte le azioni di apertura, invio e chiusura nel momento in cui vengono eseguite.

Stato finale della pagina Web dopo l'invio e la ricezione di messaggi di test e connessione WebSocket

L'articolo contiene l'introduzione all'uso di oggetti WebSocket in ASP.NET Core. WebSocket (RFC 6455) è un protocollo che consente canali di comunicazione persistente bidirezionale su connessioni TCP. Viene usato nelle app che sfruttano comunicazioni veloci e in tempo reale, ad esempio le app di chat, i dashboard e le app di gioco.

Visualizzare o scaricare il codice di esempio (come scaricare, come eseguire).

Supporto di WebSocket Http/2

L'uso di WebSocket su HTTP/2 sfrutta le nuove funzionalità, ad esempio:

  • Compressione dell'intestazione.
  • Multiplexing, che riduce il tempo e le risorse necessarie quando si effettuano più richieste al server.

Queste funzionalità supportate sono disponibili in in Kestrel tutte le piattaforme abilitate per HTTP/2. La negoziazione della versione è automatica nei browser e Kestrel, quindi non sono necessarie nuove API.

.NET 7 ha introdotto websocket sul supporto HTTP/2 per Kestrel, il SignalR client JavaScript e SignalR con Blazor WebAssembly.

Nota

I WebSocket HTTP/2 usano le richieste CONNECT anziché GET, quindi le route e i controller personalizzati potrebbero richiedere l'aggiornamento. Per altre informazioni, vedere Aggiungere il supporto webSocket HTTP/2 per i controller esistenti in questo articolo.

Chrome e Edge hanno HTTP/2 WebSocket abilitati per impostazione predefinita ed è possibile abilitarlo in FireFox nella about:config pagina con il network.http.spdy.websockets flag .

I WebSocket sono stati originariamente progettati per HTTP/1.1, ma sono stati adattati per funzionare su HTTP/2. (RFC 8441)

SignalR

ASP.NET Core SignalR è una libreria che semplifica l'aggiunta di funzionalità Web in tempo reale alle app. Laddove possibile, usa oggetti WebSocket.

Per la maggior parte delle applicazioni, è consigliabile SignalR anziché webSocket non elaborati. SignalR:

  • Fornisce il fallback del trasporto per gli ambienti in cui WebSocket non è disponibile.
  • Fornisce un modello di app di chiamata di routine remota di base.
  • Non presenta uno svantaggio significativo delle prestazioni rispetto all'uso di WebSocket non elaborati nella maggior parte degli scenari.

WebSockets su HTTP/2 è supportato per:

  • ASP.NET client JavaScript core SignalR
  • ASP.NET Core SignalR con Blazor WebAssembly

Per alcune app, gRPC in .NET offre un'alternativa ai WebSocket.

Prerequisiti

  • Qualsiasi sistema operativo che supporta ASP.NET Core:
    • Windows 7/Windows Server 2008 o versioni successive
    • Linux
    • macOS
  • Se l'app viene eseguita in Windows con IIS:
    • Windows 8/Windows Server 2012 o versioni successive
    • IIS 8/IIS 8 Express
    • I WebSocket devono essere abilitati. Vedere la sezione supporto di IIS/IIS Express.
  • Se l'app viene eseguita in HTTP.sys:
    • Windows 8/Windows Server 2012 o versioni successive
  • Per i browser supportati, vedere È possibile usare.

Configurare il middleware

Aggiungere il middleware WebSockets in Program.cs:

app.UseWebSockets();

È possibile configurare le impostazioni seguenti:

  • KeepAliveInterval: la frequenza di invio di frame "ping" al client per garantire che i proxy tengano aperta la connessione. Il valore predefinito è due minuti.
  • AllowedOrigins: elenco di valori dell'intestazione Origin consentiti per le richieste WebSocket. Per impostazione predefinita, tutte le origini sono consentite. Per altre informazioni, vedere Restrizione dell'origine WebSocket in questo articolo.
var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

app.UseWebSockets(webSocketOptions);

Accettare le richieste WebSocket

In un secondo momento nel ciclo di vita della richiesta (più avanti in Program.cs o in un metodo di azione, ad esempio) controllare se si tratta di una richiesta WebSocket e accettare la richiesta WebSocket.

L'esempio seguente è riportato più avanti in Program.cs:

app.Use(async (context, next) =>
{
    if (context.Request.Path == "/ws")
    {
        if (context.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            context.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }
    else
    {
        await next(context);
    }

});

Una richiesta WebSocket può arrivare in qualsiasi URL, ma questo codice di esempio accetta solo le richieste per /ws.

Un approccio simile può essere adottato in un metodo controller:

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;
        }
    }

Quando si usa un oggetto WebSocket, è necessario mantenere la pipeline middleware in esecuzione per la durata della connessione. Se si cerca di inviare o ricevere un messaggio WebSocket dopo che la pipeline middleware è terminata, si potrebbe ottenere un'eccezione simile alla seguente:

System.Net.WebSockets.WebSocketException (0x80004005): The remote party closed the WebSocket connection without completing the close handshake. ---> System.ObjectDisposedException: Cannot write to the response body, the response has completed.
Object name: 'HttpResponseStream'.

Se si sta usando un servizio in background per scrivere i dati in un oggetto WebSocket, assicurarsi di mantenere la pipeline middleware in esecuzione. A tale scopo, usare TaskCompletionSource<TResult>. Passare TaskCompletionSource al servizio in background e fare in modo che chiami TrySetResult al termine dell'uso dell'oggetto WebSocket. Quindi assegnare await alla proprietà Task durante la richiesta, come mostrato nell'esempio seguente:

app.Run(async (context) =>
{
    using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
    var socketFinishedTcs = new TaskCompletionSource<object>();

    BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

    await socketFinishedTcs.Task;
});

L'eccezione chiusa WebSocket può verificarsi anche quando si torna troppo presto da un metodo di azione. Quando si accetta un socket in un metodo di azione, attendere il completamento del codice che usa il socket prima di tornare dal metodo di azione.

Per attendere il completamento del socket, non utilizzare mai Task.Wait, Task.Result o chiamate di blocco simili perché ciò può provocare gravi problemi di threading. Usare sempre await.

Aggiungere il supporto WebSocket HTTP/2 per i controller esistenti

.NET 7 ha introdotto websocket sul supporto HTTP/2 per Kestrel, il SignalR client JavaScript e SignalR con Blazor WebAssembly. Http/2 WebSocket usano le richieste CONNECT anziché GET. Se in precedenza è stato usato [HttpGet("/path")] nel metodo di azione del controller per le richieste Websocket, aggiornarlo in modo da usarlo [Route("/path")] .

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;
        }
    }

Compressione

Avviso

L'abilitazione della compressione sulle connessioni crittografate può rendere un'app soggetta a CRIME/BREACH attacchi. Se si inviano informazioni riservate, evitare di abilitare la compressione o usare WebSocketMessageFlags.DisableCompression quando si chiama WebSocket.SendAsync. Questo vale per entrambi i lati del WebSocket. Si noti che l'API WebSocket nel browser non ha la configurazione per disabilitare la compressione per invio.

Se si desidera la compressione dei messaggi su WebSocket, il codice accept deve specificare che consente la compressione come indicato di seguito:

using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(
    new WebSocketAcceptContext { DangerousEnableCompression = true }))
{

}

WebSocketAcceptContext.ServerMaxWindowBits e WebSocketAcceptContext.DisableServerContextTakeover sono opzioni avanzate che controllano il funzionamento della compressione.

La compressione viene negoziata tra il client e il server quando viene stabilita per la prima volta una connessione. Per altre informazioni sulla negoziazione, vedere Compression Extensions for WebSocket RFC (Estensioni di compressione per RFC WebSocket).

Nota

Se la negoziazione di compressione non viene accettata dal server o dal client, la connessione viene ancora stabilita. Tuttavia, la connessione non usa la compressione durante l'invio e la ricezione di messaggi.

Inviare e ricevere messaggi

Il AcceptWebSocketAsync metodo aggiorna la connessione TCP a una connessione WebSocket e fornisce un WebSocket oggetto . Usare l'oggetto WebSocket per inviare e ricevere messaggi.

Il codice illustrato in precedenza che accetta la richiesta WebSocket passa l'oggetto WebSocket a un metodo Echo. Il codice riceve un messaggio e lo reinvia immediatamente. I messaggi vengono inviati e ricevuti in un ciclo, fino a quando il client non chiude la connessione:

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);
}

Quando si accetta la connessione WebSocket prima che inizi il ciclo, la pipeline del middleware termina. Dopo la chiusura del socket, la pipeline si arresta, In altri termini, quando viene accettato il WebSocket, lo spostamento in avanti della richiesta nella pipeline si interrompe. Quando il ciclo viene completato e il socket viene chiuso, la richiesta torna ad avanzare nella pipeline.

Gestire le disconnessioni del client

Il server non viene informato automaticamente quando il client si disconnette a causa della perdita di connettività. Il server riceve un messaggio di disconnessione solo se inviato dal client, ma questo non è possibile in caso di interruzione della connessione Internet. Se si vuole intervenire quando si verifica una situazione di questo tipo, impostare un timeout per segnalare che non sono stati ricevuti messaggi dal client entro un determinato intervallo di tempo.

Se il client non invia sempre messaggi e non si vuole scadere solo perché la connessione diventa inattiva, fare in modo che il client usi un timer per inviare un messaggio ping ogni X secondi. Nel server, se un messaggio non è arrivato entro 2*X secondi dopo quello precedente, terminare la connessione e segnalare che il client è disconnesso. Attendere il doppio del tempo previsto per tenere conto di eventuali ritardi della rete che potrebbero trattenere il messaggio ping.

Restrizione per le origini WebSocket

La protezione fornita da CORS non si applica agli oggetti WebSocket. I browser non:

  • Eseguono richieste CORS preventive.
  • Rispettano le restrizioni specificate nelle intestazioni Access-Control quando eseguono richieste WebSocket.

I browser, tuttavia, inviano l'intestazione Origin quando rilasciano richieste WebSocket. Le applicazioni devono essere configurate per la convalida di queste intestazioni per assicurarsi che siano consentiti solo WebSocket provenienti dalle origini previste.

Se si ospita il server in "https://server.com" e ospitare il client in "https://client.com", aggiungere "https://client.com" all'elenco AllowedOrigins per WebSocket da verificare.

var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

webSocketOptions.AllowedOrigins.Add("https://client.com");
webSocketOptions.AllowedOrigins.Add("https://www.client.com");

app.UseWebSockets(webSocketOptions);

Nota

L'intestazione Origin viene controllata dal client e, come l'intestazione Referer, può essere falsificata. Non usare queste intestazioni come meccanismo di autenticazione.

Supporto di IIS/IIS Express

Windows Server 2012 o versioni successive e Windows 8 o versioni successive con IIS/IIS Express 8 o versioni successive include il supporto per il protocollo WebSocket, ma non per WebSockets su HTTP/2.

Nota

Gli oggetti WebSocket sono sempre abilitati quando si usa IIS Express.

Abilitazione di oggetti WebSocket in IIS

Per abilitare il supporto per il protocollo WebSocket in Windows Server 2012 o versioni successive:

Nota

Questi passaggi non sono necessari quando si usa IIS Express

  1. Usare la procedura guidata Aggiungi ruoli e funzionalità accessibile tramite il menu Gestisci o il collegamento in Server Manager.
  2. Selezionare Installazione basata su ruoli o basata su funzionalità. Selezionare Avanti.
  3. Selezionare il server appropriato (il server locale è selezionato per impostazione predefinita). Selezionare Avanti.
  4. Espandere Server Web (IIS) nella struttura Ruoli, espandere Server Web, quindi Sviluppo applicazioni.
  5. Selezionare Protocollo WebSocket. Selezionare Avanti.
  6. Se non sono necessarie le funzionalità aggiuntive, selezionare Avanti.
  7. Selezionare Installa.
  8. Al termine dell'installazione, selezionare Chiudi per chiudere la procedura guidata.

Per abilitare il supporto per il protocollo WebSocket in Windows 8 o versioni successive:

Nota

Questi passaggi non sono necessari quando si usa IIS Express

  1. Passare a Pannello di controllo>Programmi>Programmi e funzionalità>Disattivare o attivare le funzionalità di Windows (a sinistra dello schermo).
  2. Espandere i nodi seguenti: Internet Information Services>Servizi Web>Funzionalità per lo sviluppo di applicazioni.
  3. Selezionare la funzionalità Protocollo WebSocket. Seleziona OK.

Disabilitare WebSocket quando si usa socket.io su node.js

Se si usa il supporto WebSocket in socket.io in Node.js, disabilitare il modulo WebSocket IIS predefinito usando l'elemento webSocket in web.config o applicationHost.config. Se questo passaggio non viene eseguito, il modulo WebSocket IIS tenta di gestire la comunicazione WebSocket anziché Node.js e l'app.

<system.webServer>
  <webSocket enabled="false" />
</system.webServer>

Esempio di app

L'app di esempio inclusa in questo articolo è un'app echo. Dispone di una pagina Web che effettua connessioni WebSocket e il server invia nuovamente tutti i messaggi ricevuti al client. L'app di esempio supporta WebSocket su HTTP/2 quando si usa un framework di destinazione di .NET 7 o versione successiva.

Eseguire l'app:

  • Per eseguire l'app in Visual Studio: aprire il progetto di esempio in Visual Studio e premere CTRL+F5 per l'esecuzione senza il debugger.
  • Per eseguire l'app in una shell dei comandi: eseguire il comando dotnet run e passare a un browser a http://localhost:<port>.

La pagina Web mostra lo stato della connessione:

Stato iniziale della pagina Web prima della connessione WebSocket

Selezionare Connect (Connetti) per inviare una richiesta WebSocket per l'URL indicato. Immettere un messaggio di prova e selezionare Send (Invia). Al termine dell'operazione, selezionare Close Socket (Chiudi socket). La sezione Comminication Log (Registrazione comunicazione) riporta tutte le azioni di apertura, invio e chiusura nel momento in cui vengono eseguite.

Stato finale della pagina Web dopo l'invio e la ricezione di messaggi di test e connessione WebSocket

L'articolo contiene l'introduzione all'uso di oggetti WebSocket in ASP.NET Core. WebSocket (RFC 6455) è un protocollo che consente canali di comunicazione persistente bidirezionale su connessioni TCP. Viene usato nelle app che sfruttano comunicazioni veloci e in tempo reale, ad esempio le app di chat, i dashboard e le app di gioco.

Visualizzare o scaricare il codice di esempio (come scaricare, come eseguire).

SignalR

ASP.NET Core SignalR è una libreria che semplifica l'aggiunta di funzionalità Web in tempo reale alle app. Laddove possibile, usa oggetti WebSocket.

Per la maggior parte delle applicazioni, è consigliabile SignalR usare WebSocket non elaborati. SignalR fornisce il fallback del trasporto per gli ambienti in cui WebSocket non è disponibile. Fornisce anche un modello di app di chiamata di procedura remota di base. Nella maggior parte degli scenari, inoltre, SignalR non presenta uno svantaggio significativo delle prestazioni rispetto all'uso di WebSocket non elaborati.

Per alcune app, gRPC in .NET offre un'alternativa ai WebSocket.

Prerequisiti

  • Qualsiasi sistema operativo che supporta ASP.NET Core:
    • Windows 7/Windows Server 2008 o versioni successive
    • Linux
    • macOS
  • Se l'app viene eseguita in Windows con IIS:
    • Windows 8/Windows Server 2012 o versioni successive
    • IIS 8/IIS 8 Express
    • I WebSocket devono essere abilitati. Vedere la sezione supporto di IIS/IIS Express.
  • Se l'app viene eseguita in HTTP.sys:
    • Windows 8/Windows Server 2012 o versioni successive
  • Per i browser supportati, vedere È possibile usare.

Configurare il middleware

Aggiungere il middleware WebSockets in Program.cs:

app.UseWebSockets();

È possibile configurare le impostazioni seguenti:

  • KeepAliveInterval: la frequenza di invio di frame "ping" al client per garantire che i proxy tengano aperta la connessione. Il valore predefinito è due minuti.
  • AllowedOrigins: elenco di valori dell'intestazione Origin consentiti per le richieste WebSocket. Per impostazione predefinita, tutte le origini sono consentite. Per altre informazioni, vedere Restrizione dell'origine WebSocket in questo articolo.
var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

app.UseWebSockets(webSocketOptions);

Accettare le richieste WebSocket

In un secondo momento nel ciclo di vita della richiesta (più avanti in Program.cs o in un metodo di azione, ad esempio) controllare se si tratta di una richiesta WebSocket e accettare la richiesta WebSocket.

L'esempio seguente è riportato più avanti in Program.cs:

app.Use(async (context, next) =>
{
    if (context.Request.Path == "/ws")
    {
        if (context.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            context.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }
    else
    {
        await next(context);
    }

});

Una richiesta WebSocket può arrivare in qualsiasi URL, ma questo codice di esempio accetta solo le richieste per /ws.

Un approccio simile può essere adottato in un metodo controller:

public class WebSocketController : ControllerBase
{
    [HttpGet("/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;
        }
    }

Quando si usa un oggetto WebSocket, è necessario mantenere la pipeline middleware in esecuzione per la durata della connessione. Se si cerca di inviare o ricevere un messaggio WebSocket dopo che la pipeline middleware è terminata, si potrebbe ottenere un'eccezione simile alla seguente:

System.Net.WebSockets.WebSocketException (0x80004005): The remote party closed the WebSocket connection without completing the close handshake. ---> System.ObjectDisposedException: Cannot write to the response body, the response has completed.
Object name: 'HttpResponseStream'.

Se si sta usando un servizio in background per scrivere i dati in un oggetto WebSocket, assicurarsi di mantenere la pipeline middleware in esecuzione. A tale scopo, usare TaskCompletionSource<TResult>. Passare TaskCompletionSource al servizio in background e fare in modo che chiami TrySetResult al termine dell'uso dell'oggetto WebSocket. Quindi assegnare await alla proprietà Task durante la richiesta, come mostrato nell'esempio seguente:

app.Run(async (context) =>
{
    using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
    var socketFinishedTcs = new TaskCompletionSource<object>();

    BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

    await socketFinishedTcs.Task;
});

L'eccezione chiusa WebSocket può verificarsi anche quando si torna troppo presto da un metodo di azione. Quando si accetta un socket in un metodo di azione, attendere il completamento del codice che usa il socket prima di tornare dal metodo di azione.

Per attendere il completamento del socket, non utilizzare mai Task.Wait, Task.Result o chiamate di blocco simili perché ciò può provocare gravi problemi di threading. Usare sempre await.

Compressione

Avviso

L'abilitazione della compressione sulle connessioni crittografate può rendere un'app soggetta a CRIME/BREACH attacchi. Se si inviano informazioni riservate, evitare di abilitare la compressione o usare WebSocketMessageFlags.DisableCompression quando si chiama WebSocket.SendAsync. Questo vale per entrambi i lati del WebSocket. Si noti che l'API WebSocket nel browser non ha la configurazione per disabilitare la compressione per invio.

Se si desidera la compressione dei messaggi su WebSocket, il codice accept deve specificare che consente la compressione come indicato di seguito:

using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(
    new WebSocketAcceptContext { DangerousEnableCompression = true }))
{

}

WebSocketAcceptContext.ServerMaxWindowBits e WebSocketAcceptContext.DisableServerContextTakeover sono opzioni avanzate che controllano il funzionamento della compressione.

La compressione viene negoziata tra il client e il server quando viene stabilita per la prima volta una connessione. Per altre informazioni sulla negoziazione, vedere Compression Extensions for WebSocket RFC (Estensioni di compressione per RFC WebSocket).

Nota

Se la negoziazione di compressione non viene accettata dal server o dal client, la connessione viene ancora stabilita. Tuttavia, la connessione non usa la compressione durante l'invio e la ricezione di messaggi.

Inviare e ricevere messaggi

Il AcceptWebSocketAsync metodo aggiorna la connessione TCP a una connessione WebSocket e fornisce un WebSocket oggetto . Usare l'oggetto WebSocket per inviare e ricevere messaggi.

Il codice illustrato in precedenza che accetta la richiesta WebSocket passa l'oggetto WebSocket a un metodo Echo. Il codice riceve un messaggio e lo reinvia immediatamente. I messaggi vengono inviati e ricevuti in un ciclo, fino a quando il client non chiude la connessione:

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);
}

Quando si accetta la connessione WebSocket prima che inizi il ciclo, la pipeline del middleware termina. Dopo la chiusura del socket, la pipeline si arresta, In altri termini, quando viene accettato il WebSocket, lo spostamento in avanti della richiesta nella pipeline si interrompe. Quando il ciclo viene completato e il socket viene chiuso, la richiesta torna ad avanzare nella pipeline.

Gestire le disconnessioni del client

Il server non viene informato automaticamente quando il client si disconnette a causa della perdita di connettività. Il server riceve un messaggio di disconnessione solo se inviato dal client, ma questo non è possibile in caso di interruzione della connessione Internet. Se si vuole intervenire quando si verifica una situazione di questo tipo, impostare un timeout per segnalare che non sono stati ricevuti messaggi dal client entro un determinato intervallo di tempo.

Se il client non invia sempre messaggi e non si vuole scadere solo perché la connessione diventa inattiva, fare in modo che il client usi un timer per inviare un messaggio ping ogni X secondi. Nel server, se un messaggio non è arrivato entro 2*X secondi dopo quello precedente, terminare la connessione e segnalare che il client è disconnesso. Attendere il doppio del tempo previsto per tenere conto di eventuali ritardi della rete che potrebbero trattenere il messaggio ping.

Restrizione per le origini WebSocket

La protezione fornita da CORS non si applica agli oggetti WebSocket. I browser non:

  • Eseguono richieste CORS preventive.
  • Rispettano le restrizioni specificate nelle intestazioni Access-Control quando eseguono richieste WebSocket.

I browser, tuttavia, inviano l'intestazione Origin quando rilasciano richieste WebSocket. Le applicazioni devono essere configurate per la convalida di queste intestazioni per assicurarsi che siano consentiti solo WebSocket provenienti dalle origini previste.

Se si ospita il server in "https://server.com" e ospitare il client in "https://client.com", aggiungere "https://client.com" all'elenco AllowedOrigins per WebSocket da verificare.

var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

webSocketOptions.AllowedOrigins.Add("https://client.com");
webSocketOptions.AllowedOrigins.Add("https://www.client.com");

app.UseWebSockets(webSocketOptions);

Nota

L'intestazione Origin viene controllata dal client e, come l'intestazione Referer, può essere falsificata. Non usare queste intestazioni come meccanismo di autenticazione.

Supporto di IIS/IIS Express

Windows Server 2012 o versioni successive e Windows 8 o versioni successive con IIS/IIS Express 8 o versioni successive includono il supporto per il protocollo WebSocket.

Nota

Gli oggetti WebSocket sono sempre abilitati quando si usa IIS Express.

Abilitazione di oggetti WebSocket in IIS

Per abilitare il supporto per il protocollo WebSocket in Windows Server 2012 o versioni successive:

Nota

Questi passaggi non sono necessari quando si usa IIS Express

  1. Usare la procedura guidata Aggiungi ruoli e funzionalità accessibile tramite il menu Gestisci o il collegamento in Server Manager.
  2. Selezionare Installazione basata su ruoli o basata su funzionalità. Selezionare Avanti.
  3. Selezionare il server appropriato (il server locale è selezionato per impostazione predefinita). Selezionare Avanti.
  4. Espandere Server Web (IIS) nella struttura Ruoli, espandere Server Web, quindi Sviluppo applicazioni.
  5. Selezionare Protocollo WebSocket. Selezionare Avanti.
  6. Se non sono necessarie le funzionalità aggiuntive, selezionare Avanti.
  7. Selezionare Installa.
  8. Al termine dell'installazione, selezionare Chiudi per chiudere la procedura guidata.

Per abilitare il supporto per il protocollo WebSocket in Windows 8 o versioni successive:

Nota

Questi passaggi non sono necessari quando si usa IIS Express

  1. Passare a Pannello di controllo>Programmi>Programmi e funzionalità>Disattivare o attivare le funzionalità di Windows (a sinistra dello schermo).
  2. Espandere i nodi seguenti: Internet Information Services>Servizi Web>Funzionalità per lo sviluppo di applicazioni.
  3. Selezionare la funzionalità Protocollo WebSocket. Seleziona OK.

Disabilitare WebSocket quando si usa socket.io su node.js

Se si usa il supporto WebSocket in socket.io in Node.js, disabilitare il modulo WebSocket IIS predefinito usando l'elemento webSocket in web.config o applicationHost.config. Se questo passaggio non viene eseguito, il modulo WebSocket IIS tenta di gestire la comunicazione WebSocket anziché Node.js e l'app.

<system.webServer>
  <webSocket enabled="false" />
</system.webServer>

Esempio di app

L'app di esempio inclusa in questo articolo è un'app echo. Dispone di una pagina Web che effettua connessioni WebSocket e il server invia nuovamente tutti i messaggi ricevuti al client. L'app di esempio non è configurata per l'esecuzione da Visual Studio con IIS Express, quindi eseguire l'app in una shell dei comandi con dotnet run e passare a un browser a http://localhost:<port>. La pagina Web mostra lo stato della connessione:

Stato iniziale della pagina Web prima della connessione WebSocket

Selezionare Connect (Connetti) per inviare una richiesta WebSocket per l'URL indicato. Immettere un messaggio di prova e selezionare Send (Invia). Al termine dell'operazione, selezionare Close Socket (Chiudi socket). La sezione Comminication Log (Registrazione comunicazione) riporta tutte le azioni di apertura, invio e chiusura nel momento in cui vengono eseguite.

Stato finale della pagina Web dopo l'invio e la ricezione di messaggi di test e connessione WebSocket

L'articolo contiene l'introduzione all'uso di oggetti WebSocket in ASP.NET Core. WebSocket (RFC 6455) è un protocollo che consente canali di comunicazione persistente bidirezionale su connessioni TCP. Viene usato nelle app che sfruttano comunicazioni veloci e in tempo reale, ad esempio le app di chat, i dashboard e le app di gioco.

Visualizzare o scaricare il codice di esempio (procedura per il download). Come eseguire.

SignalR

ASP.NET Core SignalR è una libreria che semplifica l'aggiunta di funzionalità Web in tempo reale alle app. Laddove possibile, usa oggetti WebSocket.

Per la maggior parte delle applicazioni, è consigliabile SignalR usare WebSocket non elaborati. SignalR fornisce il fallback del trasporto per gli ambienti in cui WebSocket non è disponibile. Fornisce anche un modello di app di chiamata di procedura remota di base. Nella maggior parte degli scenari, inoltre, SignalR non presenta uno svantaggio significativo delle prestazioni rispetto all'uso di WebSocket non elaborati.

Per alcune app, gRPC in .NET offre un'alternativa ai WebSocket.

Prerequisiti

  • Qualsiasi sistema operativo che supporta ASP.NET Core:
    • Windows 7/Windows Server 2008 o versioni successive
    • Linux
    • macOS
  • Se l'app viene eseguita in Windows con IIS:
    • Windows 8/Windows Server 2012 o versioni successive
    • IIS 8/IIS 8 Express
    • I WebSocket devono essere abilitati. Vedere la sezione supporto di IIS/IIS Express.
  • Se l'app viene eseguita in HTTP.sys:
    • Windows 8/Windows Server 2012 o versioni successive
  • Per i browser supportati, vedere È possibile usare.

Configurare il middleware

Aggiungere il middleware degli oggetti WebSocket nel metodo Configure della classe Startup:

app.UseWebSockets();

Nota

Se si desidera accettare richieste WebSocket in un controller, la chiamata a app.UseWebSockets deve essere eseguita prima app.UseEndpointsdi .

È possibile configurare le impostazioni seguenti:

  • KeepAliveInterval: la frequenza di invio di frame "ping" al client per garantire che i proxy tengano aperta la connessione. Il valore predefinito è due minuti.
  • AllowedOrigins: elenco di valori dell'intestazione Origin consentiti per le richieste WebSocket. Per impostazione predefinita, tutte le origini sono consentite. Per informazioni dettagliate, vedere più avanti "Restrizione per le origini WebSocket".
var webSocketOptions = new WebSocketOptions()
{
    KeepAliveInterval = TimeSpan.FromSeconds(120),
};

app.UseWebSockets(webSocketOptions);

Accettare le richieste WebSocket

In un momento successivo nel ciclo di vita della richiesta, più avanti nel metodo Configure o in un metodo di azione, ad esempio, verificare che si tratti di una richiesta WebSocket e accettarla.

L'esempio seguente è tratto da un punto successivo nel metodo Configure:

app.Use(async (context, next) =>
{
    if (context.Request.Path == "/ws")
    {
        if (context.WebSockets.IsWebSocketRequest)
        {
            using (WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync())
            {
                await Echo(context, webSocket);
            }
        }
        else
        {
            context.Response.StatusCode = (int) HttpStatusCode.BadRequest;
        }
    }
    else
    {
        await next();
    }

});

Una richiesta WebSocket può arrivare in qualsiasi URL, ma questo codice di esempio accetta solo le richieste per /ws.

Un approccio simile può essere adottato in un metodo controller:

public class WebSocketController : ControllerBase
{
    [HttpGet("/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;
        }
    }

Quando si usa un oggetto WebSocket, è necessario mantenere la pipeline middleware in esecuzione per la durata della connessione. Se si cerca di inviare o ricevere un messaggio WebSocket dopo che la pipeline middleware è terminata, si potrebbe ottenere un'eccezione simile alla seguente:

System.Net.WebSockets.WebSocketException (0x80004005): The remote party closed the WebSocket connection without completing the close handshake. ---> System.ObjectDisposedException: Cannot write to the response body, the response has completed.
Object name: 'HttpResponseStream'.

Se si sta usando un servizio in background per scrivere i dati in un oggetto WebSocket, assicurarsi di mantenere la pipeline middleware in esecuzione. A tale scopo, usare TaskCompletionSource<TResult>. Passare TaskCompletionSource al servizio in background e fare in modo che chiami TrySetResult al termine dell'uso dell'oggetto WebSocket. Quindi assegnare await alla proprietà Task durante la richiesta, come mostrato nell'esempio seguente:

app.Use(async (context, next) =>
{
    using (WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync())
    {
        var socketFinishedTcs = new TaskCompletionSource<object>();

        BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

        await socketFinishedTcs.Task;
    }
});

L'eccezione chiusa WebSocket può verificarsi anche quando si torna troppo presto da un metodo di azione. Quando si accetta un socket in un metodo di azione, attendere il completamento del codice che usa il socket prima di tornare dal metodo di azione.

Per attendere il completamento del socket, non utilizzare mai Task.Wait, Task.Result o chiamate di blocco simili perché ciò può provocare gravi problemi di threading. Usare sempre await.

Inviare e ricevere messaggi

Il AcceptWebSocketAsync metodo aggiorna la connessione TCP a una connessione WebSocket e fornisce un WebSocket oggetto . Usare l'oggetto WebSocket per inviare e ricevere messaggi.

Il codice illustrato in precedenza che accetta la richiesta WebSocket passa l'oggetto WebSocket a un metodo Echo. Il codice riceve un messaggio e lo reinvia immediatamente. I messaggi vengono inviati e ricevuti in un ciclo, fino a quando il client non chiude la connessione:

private async Task Echo(HttpContext context, WebSocket webSocket)
{
    var buffer = new byte[1024 * 4];
    WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
    while (!result.CloseStatus.HasValue)
    {
        await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);

        result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
    }
    await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}

Quando si accetta la connessione WebSocket prima che inizi il ciclo, la pipeline del middleware termina. Dopo la chiusura del socket, la pipeline si arresta, In altri termini, quando viene accettato il WebSocket, lo spostamento in avanti della richiesta nella pipeline si interrompe. Quando il ciclo viene completato e il socket viene chiuso, la richiesta torna ad avanzare nella pipeline.

Gestire le disconnessioni del client

Il server non viene informato automaticamente quando il client si disconnette a causa della perdita di connettività. Il server riceve un messaggio di disconnessione solo se inviato dal client, ma questo non è possibile in caso di interruzione della connessione Internet. Se si vuole intervenire quando si verifica una situazione di questo tipo, impostare un timeout per segnalare che non sono stati ricevuti messaggi dal client entro un determinato intervallo di tempo.

Se il client non invia sempre messaggi e non si vuole scadere solo perché la connessione diventa inattiva, fare in modo che il client usi un timer per inviare un messaggio ping ogni X secondi. Nel server, se un messaggio non è arrivato entro 2*X secondi dopo quello precedente, terminare la connessione e segnalare che il client è disconnesso. Attendere il doppio del tempo previsto per tenere conto di eventuali ritardi della rete che potrebbero trattenere il messaggio ping.

Nota

L'interno ManagedWebSocket gestisce i fotogrammi Ping/Pong in modo implicito per mantenere attiva la connessione se l'opzione KeepAliveInterval è maggiore di zero, che per impostazione predefinita è 30 secondi (TimeSpan.FromSeconds(30)).

Restrizione per le origini WebSocket

La protezione fornita da CORS non si applica agli oggetti WebSocket. I browser non:

  • Eseguono richieste CORS preventive.
  • Rispettano le restrizioni specificate nelle intestazioni Access-Control quando eseguono richieste WebSocket.

I browser, tuttavia, inviano l'intestazione Origin quando rilasciano richieste WebSocket. Le applicazioni devono essere configurate per la convalida di queste intestazioni per assicurarsi che siano consentiti solo WebSocket provenienti dalle origini previste.

Se si ospita il server in "https://server.com" e ospitare il client in "https://client.com", aggiungere "https://client.com" all'elenco AllowedOrigins per WebSocket da verificare.

var webSocketOptions = new WebSocketOptions()
{
    KeepAliveInterval = TimeSpan.FromSeconds(120),
};
webSocketOptions.AllowedOrigins.Add("https://client.com");
webSocketOptions.AllowedOrigins.Add("https://www.client.com");

app.UseWebSockets(webSocketOptions);

Nota

L'intestazione Origin viene controllata dal client e, come l'intestazione Referer, può essere falsificata. Non usare queste intestazioni come meccanismo di autenticazione.

Supporto di IIS/IIS Express

Windows Server 2012 o versioni successive e Windows 8 o versioni successive con IIS/IIS Express 8 o versioni successive includono il supporto per il protocollo WebSocket.

Nota

Gli oggetti WebSocket sono sempre abilitati quando si usa IIS Express.

Abilitazione di oggetti WebSocket in IIS

Per abilitare il supporto per il protocollo WebSocket in Windows Server 2012 o versioni successive:

Nota

Questi passaggi non sono necessari quando si usa IIS Express

  1. Usare la procedura guidata Aggiungi ruoli e funzionalità accessibile tramite il menu Gestisci o il collegamento in Server Manager.
  2. Selezionare Installazione basata su ruoli o basata su funzionalità. Selezionare Avanti.
  3. Selezionare il server appropriato (il server locale è selezionato per impostazione predefinita). Selezionare Avanti.
  4. Espandere Server Web (IIS) nella struttura Ruoli, espandere Server Web, quindi Sviluppo applicazioni.
  5. Selezionare Protocollo WebSocket. Selezionare Avanti.
  6. Se non sono necessarie le funzionalità aggiuntive, selezionare Avanti.
  7. Selezionare Installa.
  8. Al termine dell'installazione, selezionare Chiudi per chiudere la procedura guidata.

Per abilitare il supporto per il protocollo WebSocket in Windows 8 o versioni successive:

Nota

Questi passaggi non sono necessari quando si usa IIS Express

  1. Passare a Pannello di controllo>Programmi>Programmi e funzionalità>Disattivare o attivare le funzionalità di Windows (a sinistra dello schermo).
  2. Espandere i nodi seguenti: Internet Information Services>Servizi Web>Funzionalità per lo sviluppo di applicazioni.
  3. Selezionare la funzionalità Protocollo WebSocket. Seleziona OK.

Disabilitare WebSocket quando si usa socket.io su node.js

Se si usa il supporto WebSocket in socket.io in Node.js, disabilitare il modulo WebSocket IIS predefinito usando l'elemento webSocket in web.config o applicationHost.config. Se questo passaggio non viene eseguito, il modulo WebSocket IIS tenta di gestire la comunicazione WebSocket anziché Node.js e l'app.

<system.webServer>
  <webSocket enabled="false" />
</system.webServer>

Esempio di app

L'app di esempio inclusa in questo articolo è un'app echo. Dispone di una pagina Web che effettua connessioni WebSocket e il server invia nuovamente tutti i messaggi ricevuti al client. L'app di esempio non è configurata per l'esecuzione da Visual Studio con IIS Express, quindi eseguire l'app in una shell dei comandi con dotnet run e passare a un browser a http://localhost:5000. La pagina Web mostra lo stato della connessione:

Stato iniziale della pagina Web prima della connessione WebSocket

Selezionare Connect (Connetti) per inviare una richiesta WebSocket per l'URL indicato. Immettere un messaggio di prova e selezionare Send (Invia). Al termine dell'operazione, selezionare Close Socket (Chiudi socket). La sezione Comminication Log (Registrazione comunicazione) riporta tutte le azioni di apertura, invio e chiusura nel momento in cui vengono eseguite.

Stato finale della pagina Web dopo l'invio e la ricezione di messaggi di test e connessione WebSocket