Skip to content

WebSockets

An extension exists to enable the use of Channels over WebSockets. This extension provides support for mapping ASP.NET Core WebSocket endpoints to channel configurations. For non-ASP.NET Core applications, the extension provides a factory to create a channel from a System.Net.WebSockets.WebSocket instance.

Considerations

Unlike TCP or UDP channels, the extension library does not provide a low-level server to handle WebSocket connections. Instead, it provides a factory to create a channel on top of an existing WebSocket connection.

Also unlike the TCP and UDP channels, a WebSockets channel does not deliver raw byte buffers to the input pipeline. Instead, it delivers a WebSocketMessage object, which contains the message type and payload, since WebSockets support both binary and text payloads.

Tip

Messages delivered to the input pipeline are always complete messages (EndOfMessage == true). If a fragmented message is received, the channel will buffer the fragments until it is complete and only then deliver it to the input pipeline.

When writing to the output pipeline, the library provides built-in middleware for sending binary, text or fragmented messages, so either of the following types can be directly written to the output pipeline:

  • byte[] which is sent as a (complete) binary message
  • IByteBuffer also sent as a (complete) binary message
  • string which is sent as a (complete) text message (utf-8 encoded)
  • WebSocketMessage which gives us full control over the message type and content

Usage

To make use of this extension, we first need to add the NuGet package to our project:

dotnet add package Faactory.Channels.WebSockets

The channel pipeline configuration is done in the same way as with the TCP and UDP channels - WebSocket channels will use this same configuration. Nonetheless, additional services are required to set up the WebSockets middleware.

IServiceCollection services = ...;

/*
Configure the channel or channels as usual
*/
services.AddChannels( ... );

/*
register web sockets middleware services
*/
services.AddWebSocketChannels();

With the middleware in place, we now need to bind the WebSockets endpoint to the channels, which is done through route mapping:

WebApplication app = ...;

// required: ASP.NET WebSockets middleware
app.UseWebSockets();

// map WebSocket endpoint to the default channel configuration
app.MapWebSocketChannel( "/ws" );

Multiple Endpoints

By using named channels, it is possible to have multiple WebSocket endpoints with different channel configurations. The following example demonstrates how to set up two named channels with different middleware:

IServiceCollection services = ...;

services.AddChannels()
    .Add( "foo_channel", channel =>
    {
        // set up input pipeline
        channel.AddInputAdapter<ExampleAdapter>()
            .AddInputHandler<ExampleHandler1>();

        // set up output pipeline
        channel.AddOutputAdapter<ExampleAdapter>();
    } )
    .Add( "bar_channel", channel =>
    {
        // set up input pipeline
        channel.AddInputAdapter<ExampleAdapter>()
            .AddInputHandler<ExampleHandler2>();

        // set up output pipeline
        channel.AddOutputAdapter<ExampleAdapter>();
    } );

To bind the named channels to the WebSocket endpoints, we just need to include the channel name in the route mapping:

WebApplication app = ...;

// required: ASP.NET WebSockets middleware
app.UseWebSockets();

// map the WebSocket endpoints
app.MapWebSocketChannel( "/ws/foo", "foo_channel" );
app.MapWebSocketChannel( "/ws/bar", "bar_channel" );

Usage without ASP.NET Core

If not using ASP.NET Core, we can still use the extension library with any HTTP server that can produce a System.Net.WebSockets.WebSocket instance. In this case, we won't be using the MapWebSocketChannel extension method, but instead, we will need to manually create the channel by using the factory.

WebSocket webSocket = ...;
// get the factory from the DI container (usually injected)
IWebSocketChannelFactory factory = ...;
// optional: graceful shutdown if using the WaitAsync method
CancellationToken cancellationToken = ...;

// create named channel using the pre-configured "channel1" pipeline
var channel = factory.CreateChannel( webSocket, "channel1" );

// optional: wait until the channel is closed or cancellation token is triggered
try
{
    await channel.WaitAsync( cts.Token );
}
catch ( OperationCanceledException )
{ }

// recommended: close the channel and release resources
await channel.CloseAsync();