PushStreamContent and client disconnection

Topics: ASP.NET Web API
Jun 10, 2012 at 3:19 PM
Edited Jun 10, 2012 at 6:35 PM

I've been looking at the example here with great interest and I'm curious to know if there's a way of reliably detecting client disconnection from the server. Henriks' sample code catches exceptions on writing to the client stream in the timer callback however in my tests I've found that a client can disconnect and no exception is thrown / caught. I've also noticed that TransportContext in the OnStreamAvailable callback is always null.

After some thought I've come to the conclusion that it probably isn't possible to determine whether a client is connected or not until the stream is written to and flushed however I can't for the life of me come up with a way to determine this. Perhaps what I need is a some sort of callback after the Stream is written..

In short I'm trying to come up with a way of managing a list of connected client streams efficiently and purely on the server side. I hope this makes sense!

Jun 11, 2012 at 6:21 PM

I should clarify that my application is a server sent event ApiController which means I need to maintain and manage connections to clients over long periods of time. This is a valid use case so I'm sure there has to be a way to manage the collection of connected clients on the server, pruning dead connections when necessary. Any ideas anyone? Pretty please?

Here's my class looking remarkably similar to Henriks' code:

public class EventController : ApiController
{
    static Timer _timer = default(Timer);
    static readonly ConcurrentQueue<StreamWriter> ConnectedClients = new ConcurrentQueue<StreamWriter>();

    /// <summary>
    /// Initializes a new instance of the <see cref="EventController"/> class.
    /// </summary>
    public EventController()
    {
        _timer = _timer ?? new Timer(OnTimerEvent, null, 0, 1000);
    }

    /// <summary>
    /// Gets the specified request.
    /// This is a client subscription to the service
    /// </summary>
    /// <param name="request">The request.</param>
    /// <returns></returns>
    public HttpResponseMessage Get(HttpRequestMessage request)
    {
        HttpResponseMessage response = request.CreateResponse();
        response.Content = new PushStreamContent(OnStreamAvailable, "text/event-stream");
        return response;
    }

    /// <summary>
    /// Called when [stream available].
    /// This is the callback from PushStreamContent
    /// </summary>
    /// <param name="stream">The stream.</param>
    /// <param name="headers">The headers.</param>
    /// <param name="context">The context.</param>
    public static void OnStreamAvailable(Stream stream, HttpContentHeaders headers, TransportContext context)
    {
        var clientStream = new StreamWriter(stream);
        ConnectedClients.Enqueue(clientStream);
    }
        
    /// <summary>
    /// Called on [timer event].
    /// Stream available data out to connected clients on a regular interval
    /// </summary>
    /// <param name="state">The state.</param>
    static void OnTimerEvent(object state)
    {
        _timer.Change(Timeout.Infinite, Timeout.Infinite);            
        try
        {
            //TODO: get the event data from an external source
            var outboundMessage = new EventData { Timestamp = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss"), Data = "test" };

            foreach (var clientStream in ConnectedClients)
            {
                try
                {                            
                    clientStream.WriteLine("data:" + JsonConvert.SerializeObject(outboundMessage) + "\n");
                    clientStream.Flush();
                }
                catch
                {
                    //never occurs
                }
            }
        }
        finally
        {
            _timer.Change(1000, 1000);
        }                
    }
}

Jun 14, 2012 at 3:01 PM

Detecting that the TCP connection has been reset is something that the Host (ASP, WCF, etc.) monitors but in .NET 4 neither ASP nor WCF tells us (the Web API layer) about it. This means that the only reliable manner to detect a broken connection is to actually write data to it. This is why we have the try/catch around the write operation in the sample. That is, responses will get cleaned up when they fail and not before.

In .NET 4.5 there is a mechanism for detecting client disconnect but I haven't tried it out.

Hope this helps,

Henrik

Jun 14, 2012 at 6:04 PM

Hi Henrik

Yes that helps a lot thank you. What you've said confirms how I can implement connection management and it also confirms that the lower level framework could theoretically notify the Web API about a socket disconnect but this just isn't implemented at the moment.

Thanks again,

Joe

Oct 19, 2012 at 6:31 PM

Try checking System.Web.HttpContext.Current.Response.IsClientConnected.