Podpora websocketů v ASP.NET Core

Tom Dykstra a Andrew Stanton-Nurse

Tento článek vysvětluje, jak začít s websockety v ASP.NET Core. WebSocket (RFC 6455)je protokol, který umožňuje dvousestupné trvalé komunikační kanály přes připojení TCP. Používá se v aplikacích, které těží z rychlé komunikace v reálném čase, jako je chat, řídicí panel a herní aplikace.

Zobrazení nebo stažení ukázkového kódu (stažení). Jak spustit .

SignalR

ASP.NET Core SignalR je knihovna, která zjednodušuje přidávání webových funkcí do aplikací v reálném čase. Pokud je to možné, používá webSockety.

Pro většinu aplikací doporučujeme používat SignalR nezpracované webSockety. SignalR poskytuje záložní přenos pro prostředí, ve kterých není k dispozici webSocket. Poskytuje také základní model aplikace vzdáleného volání procedur. A ve většině scénářů SignalR nemá žádná významná nevýhoda výkonu v porovnání s použitím nezpracovaných protokolu WebSocket.

Pro některé aplikace nabízí gRPC v .NET alternativu k webSocketům.

Požadavky

  • Jakýkoli operační systém, který podporuje ASP.NET Core:
    • Windows 7 nebo Windows Server 2008 nebo novější
    • Linux
    • macOS
  • Pokud aplikace běží ve službě Windows službou IIS:
  • Pokud aplikace běží na HTTP.sys:
    • Windows 8 / Windows Server 2012 nebo novější
  • Informace o podporovaných prohlížečích najdete v tématu https://caniuse.com/#feat=websockets .

Konfigurace middlewaru

Přidejte middleware WebSockets Configure do metody třídy Startup :

app.UseWebSockets();

Poznámka

Pokud chcete přijímat požadavky WebSocket v kontroleru, musí k volání dojít app.UseWebSockets před app.UseEndpoints .

Je možné nakonfigurovat následující nastavení:

  • KeepAliveInterval – Jak často se do klienta odesílaly rámce "ping", aby se zajistilo, že se připojení bude udržovat otevřené v rámci proxů. Výchozí hodnota je dvě minuty.

Je možné nakonfigurovat následující nastavení:

  • KeepAliveInterval – Jak často se do klienta odesílaly rámce "ping", aby se zajistilo, že se připojení bude udržovat otevřené v rámci proxů. Výchozí hodnota je dvě minuty.
  • AllowedOrigins – Seznam povolených hodnot hlavičky Origin pro požadavky WebSocket. Ve výchozím nastavení jsou povoleny všechny zdroje. Podrobnosti najdete níže v části Omezení původu WebSocket.
var webSocketOptions = new WebSocketOptions() 
{
    KeepAliveInterval = TimeSpan.FromSeconds(120),
};

app.UseWebSockets(webSocketOptions);

Přijetí požadavků WebSocket

Někde později v životním cyklu požadavku (například později v metodě nebo metodě akce) zkontrolujte, jestli se jedná o požadavek Configure WebSocket, a přijměte požadavek WebSocket.

Následující příklad je z pozdější části Configure metody :

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

});

Požadavek WebSocket může přijít na libovolnou adresu URL, ale tento ukázkový kód přijímá pouze požadavky na /ws .

Podobný přístup lze použít v metodě kontroleru:

public class WebSocketController : ControllerBase
{
    [HttpGet("/ws")]
    public async Task Get()
    {
        if (HttpContext.WebSockets.IsWebSocketRequest)
        {
            using WebSocket webSocket = await 
                               HttpContext.WebSockets.AcceptWebSocketAsync();
            await Echo(HttpContext, webSocket);
        }
        else
        {
            HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
        }
    }

Při použití protokolu WebSocket musíte nechat kanál middlewaru spuštěný po dobu trvání připojení. Pokud se pokusíte odeslat nebo přijmout zprávu WebSocket po ukončení kanálu middlewaru, může se zobrazit výjimka podobná této:

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'.

Pokud k zápisu dat do protokolu WebSocket používáte službu na pozadí, ujistěte se, že je kanál middlewaru spuštěný. Proveďte to pomocí TaskCompletionSource<TResult> . Předejte do služby na pozadí a po dokončení volání volejte TaskCompletionSource TrySetResult webSocket. Potom await vlastnost Task během požadavku, jak je znázorněno v následujícím příkladu:

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

        BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

        await socketFinishedTcs.Task;
    }
});

K uzavřené výjimce webSocket může dojít také v případě, že metoda akce vrátí příliš brzy. Při přijímání soketu v metodě akce počkejte na dokončení kódu, který používá soket, a pak se vraťte z metody akce.

Nikdy nepoužívejte blokovací volání , nebo podobná blokující volání, která čekají na dokončení soketu, protože to může Task.Wait způsobit závažné problémy s Task.Result vlákny. Vždy používejte await .

Komprese

Upozornění

Povolením komprese přes šifrovaná připojení může být aplikace vystavena útokům CRIME/BREACH. Pokud odesíláte citlivé informace, vyhněte se povolení komprese nebo použití WebSocketMessageFlags.DisableCompression při volání WebSocket.SendAsync . To platí pro obě strany rozhraní WebSocket. Všimněte si, že rozhraní WebSockets API v prohlížeči nemá konfiguraci pro zakázání komprese na odeslání.

Pokud je vyžadována komprese zpráv přes webSockety, musí kód pro přijetí určit, že umožňuje kompresi, a to následujícím způsobem:

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

WebSocketAcceptContext.ServerMaxWindowBits a WebSocketAcceptContext.DisableServerContextTakeover jsou pokročilé možnosti, které řídí, jak komprese funguje.

Komprese se vyjedná mezi klientem a serverem při prvním navazování připojení. Další informace o vyjednávání najdete v dokumentu RFC compression extensions for WebSocket.

Poznámka

Pokud server ani klient nepřijímá vyjednávání komprese, připojení se stále na navázat bude. Připojení ale při odesílání a přijímání zpráv nevyujímá kompresi.

Odesílání a příjem zpráv

Metoda AcceptWebSocketAsync upgraduuje připojení TCP na připojení WebSocket a poskytuje objekt WebSocket. K odesílání WebSocket a příjmu zpráv použijte objekt .

Dříve zobrazený kód, který přijímá požadavek WebSocket, předá WebSocket objekt Echo metodě . Kód obdrží zprávu a okamžitě odešle stejnou zprávu zpět. Zprávy se odesílají a přijímou ve smyčce, dokud klient nezadá připojení:

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

Když před zahájením smyčky přijmete připojení WebSocket, kanál middlewaru se ukončí. Po zavření soketu se kanál odvíje. To znamená, že požadavek se zastaví v kanálu po přijetí sady WebSocket. Po dokončení smyčky a uzavření soketu požadavek pokračuje v zálohování kanálu.

Zpracování odpojení klienta

Server není automaticky informován, když se klient odpojí z důvodu ztráty připojení. Server obdrží zprávu o odpojení pouze v případě, že ji klient odešle, což není možné provést, pokud dojde ke ztrátě připojení k internetu. Pokud chcete v takovém případě provést nějakou akci, nastavte časový limit, když se od klienta v určitém časovém období nic neobdrží.

Pokud klient vždy neposílá zprávy a nechcete vypršení časového limitu jenom kvůli nečinnosti připojení, používejte k odeslání zprávy ping každých X sekund časovač. Pokud zpráva na serveru nedošla do 2 X sekund od předchozí zprávy, ukončete připojení a nahlásit, že * se klient odpojil. Počkejte dvakrát v očekávaném časovém intervalu, abyste počkali na prodlevy v síti, které by mohly podržet zprávu ping.

Omezení původu webSocket

Ochrana poskytovaná CORS se nevztahuje na webSockety. Prohlížeče ne:

  • Proveďte požadavky CORS před letem.
  • Při provádění požadavků Protokolu WebSocket dodržujte omezení určená Access-Control v hlavičkách.

Prohlížeče ale při vydávání požadavků Origin protokolu WebSocket hlavičku odesílat. Aplikace by měly být nakonfigurované tak, aby tyto hlavičky ověřovali, aby se zajistilo, že budou povolené jenom sady WebSocket pocházející z očekávaného původu.

Pokud server hostíte na serveru a hostíte klienta na serveru , přidejte ho do seznamu, který https://server.com https://client.com https://client.com AllowedOrigins webSocket ověří.

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

app.UseWebSockets(webSocketOptions);

Poznámka

Hlavičku řídí klient a stejně jako záhlaví Origin Referer může být napodobenna. Nepoužívejte tyto hlavičky jako mechanismus ověřování.

Podpora iis/IIS Express serveru

Windows Server 2012 nebo novější a Windows 8 nebo novější s IIS/IIS Express 8 nebo novějším podporuje protokol WebSocket.

Poznámka

WebSockety jsou při použití protokolu IIS Express.

Povolení protokolu WebSocket ve službě IIS

Povolení podpory pro protokol WebSocket v Windows Server 2012 nebo novějším:

Poznámka

Tyto kroky nejsou při použití nástroje IIS Express

  1. V nabídce Spravovat použijte průvodce přidáním rolí a funkcí nebo odkaz v Správce serveru.
  2. Vyberte Instalace na základě rolí nebo funkcí. Vyberte Další.
  3. Vyberte příslušný server (ve výchozím nastavení je vybraný místní server). Vyberte Další.
  4. Rozbalte Webový server (IIS) ve stromu Role, rozbalte Webový server a pak rozbalte Vývoj aplikací.
  5. Vyberte Protokol WebSocket. Vyberte Další.
  6. Pokud další funkce nejsou potřeba, vyberte Další.
  7. Vyberte Nainstalovat.
  8. Po dokončení instalace ukončete průvodce výběrem možnosti Zavřít.

Povolení podpory pro protokol WebSocket v Windows 8 nebo novějším:

Poznámka

Tyto kroky nejsou při použití nástroje IIS Express

  1. Přejděte na Ovládací panely Programy a funkce Windows zapněte nebo vypněte > > > (levá strana obrazovky).
  2. Otevřete následující uzly: Internetová informační služba > World Wide Web Services Application Development > Features.
  3. Vyberte funkci protokolu WebSocket. Vyberte OK.

Při použití protokolu WebSocket socket.io v Node.js

Pokud používáte podporu protokolu WebSocket v socket.io na Node.js, zakažte výchozí modul IIS WebSocket pomocí elementu v webSocket souboru web.config nebo applicationHost.config. Pokud tento krok není proveden, modul IIS WebSocket se pokusí zpracovat komunikaci protokolu WebSocket místo Node.js a aplikace.

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

Ukázková aplikace

Ukázková aplikace, která doprovází tento článek, je aplikace odezvy. Obsahuje webovou stránku, která provádí připojení protokolu WebSocket, a server znovu předá všechny zprávy, které obdrží zpět klientovi. Ukázková aplikace není nakonfigurovaná tak, aby Visual Studio s IIS Express, proto ji spusťte v příkazovém prostředí pomocí příkazu a v prohlížeči dotnet run přejděte na http://localhost:5000 . Na webové stránce se zobrazí stav připojení:

Počáteční stav webové stránky před připojením WebSocket

Vyberte Připojení a odešlete požadavek WebSocket na zobrazenou adresu URL. Zadejte testovací zprávu a vyberte Odeslat. Až to bude hotové, vyberte Zavřít soket. Oddíl Protokol komunikace hlásí každou akci otevření, odeslání a zavření, jakmile k ní dojde.

Konečný stav webové stránky po odeslání a přijetí testovacích zpráv a připojení protokolu WebSocket