asp.netasp.net-coresignalrasp.net-core-signalr

Get SignalR Core received message size


There is a setting to limit maximum size of received signalr core messages:

services.AddSignalR(options =>
{
    options.MaximumReceiveMessageSize = 32768;
});

I need to track actual size of incoming signalr core messages, but didn't find the way to get it. Is there any way to do this? It would be nice if it's possible inside IHubFilter implementation.


Solution

  • TL; DR

    It's not possible with current connection handler implementation. Getting the actual size of raw message without re-serialization would interfere with underlying PipeReader breaking the SignalR's message reading logic.

    Full explanation

    SignalR is using a ConnectionContext to hold ASP.NET Core's individual connection data. That in turn uses System.IO.Pipelines to allow transporting data on the connection, since pipelines are dedicated to read and write streamable data. You can get access to the underlying connection context through feature collection when inside a hub filter:

    public class MessageSizeFilter : IHubFilter
    {
        public async Task InvokeMethodAsync(InvokeMethodAsync invocationContext, Func<InvokeMethodAsync, Task> next)
        {
            var connectionContext = invocationContext.Context.Features
                .Select(x => x.Value)
                .OfType<ConnectionContext>()
                .FirstOrDefault();
            var pipeReader = connectionContext.Transport.Input;
    
            await next(invocationContext);
        }
    }
    

    Getting the length of a single message in itself is not that difficult. You'd normally use reading capabilities from the PipeReader:

    var result = await pipeReader.ReadAsync();
    var messageLength = result.Buffer.Length;
    

    But reading from a pipe that already is in process of reading started from different place is difficult. It is not how pipes were designed to be used. If the pipe is already in the reading state, it will throw an exception when you try to start a new reading process. You'd have to cancel or complete the original process, thus interfering with the reading logic on SignalR side. You can see on your own how the reading process looks like in the SignalR's connection handler. Notice the big while loop that keeps things running and deals with incoming messages as they come, reads them and tries to dispatch to the correct consumer.

    All right, then maybe start reading before the SignalR's connection handler starts its work? Well... good luck with that. You'd have to re-implement the connection handler as there is no fitting method to override, so you could simply throw in only pieces of your own code.

    IMO, it will be easier to create an issue on GitHub, so folk from MS can at least give you some guidance. I haven't been able to get past the interference issue of PipeReader throwing an InvalidOperationException: Reading is already in progress. You may try diving into that topic on your own. Here's some good discussion on PipeReader on GitHub.

    Alternatively, try requesting a missing functionality.

    Alternative solution with serialization

    If you have luxury of knowing the way of communication used and can assume that is not going to change depending on clients, say it is going to always be JSON, then you can try serialization and to the length obtained from JSON bytes add few more for the message envelope, which is not huge.