15

Given an async controller:

public class MyController : AsyncController 
{
    [NoAsyncTimeout]
    public void MyActionAsync() { ... }

    public void MyActionCompleted() { ... }
}

Assume MyActionAsync kicks off a process that takes several minutes. If the user now goes to the MyAction action, the browser will wait with the connection open. If the user closes his browser, the connection is closed. Is it possible to detect when that happens on the server (preferably inside the controller)? If so, how? I've tried overriding OnException but that never fires in this scenario.

Note: I do appreciate the helpful answers below, but the key aspect of this question is that I'm using an AsyncController. This means that the HTTP requests are still open (they are long-lived like COMET or BOSH) which means it's a live socket connection. Why can't the server be notified when this live connection is terminated (i.e. "connection reset by peer", the TCP RST packet)?

Kirk Woll
  • 76,112
  • 22
  • 180
  • 195

5 Answers5

29

I realise this question is old, but it turned up frequently in my search for the same answer. The details below only apply to .Net 4.5

HttpContext.Response.ClientDisconnectedToken is what you want. That will give you a CancellationToken you can pass to your async/await calls.

public async Task<ActionResult> Index()
{
    //The Connected Client 'manages' this token. 
    //HttpContext.Response.ClientDisconnectedToken.IsCancellationRequested will be set to true if the client disconnects
    try
    {
        using (var client = new System.Net.Http.HttpClient())
        {
            var url = "http://google.com";
            var html = await client.GetAsync(url,  HttpContext.Response.ClientDisconnectedToken);
        }
    }
    catch (TaskCanceledException e)
    {
        //The Client has gone
        //you can handle this and the request will keep on being processed, but no one is there to see the resonse
    }
    return View();
}

You can test the snippet above by putting a breakpoint at the start of the function then closing your browser window.


And another snippet, not directly related to your question but useful all the same...

You can also put a hard limit on the amount of time an action can execute for by using the AsyncTimeout attribute. To use this use add an additional parameter of type CancellationToken. This token will allow ASP.Net to time-out the request if execution takes too long.

[AsyncTimeout(500)] //500ms
public async Task<ActionResult> Index(CancellationToken cancel)
{
    //ASP.Net manages the cancel token.
    //cancel.IsCancellationRequested will be set to true after 500ms
    try
    {
        using (var client = new System.Net.Http.HttpClient())
        {
            var url = "http://google.com";
            var html = await client.GetAsync(url, cancel);
        }
    }
    catch (TaskCanceledException e)
    {
        //ASP.Net has killed the request
        //Yellow Screen Of Death with System.TimeoutException
        //the return View() below wont render
    }
    return View();
}

You can test this one by putting a breakpoint at the start of the function (thus making the request take more than 500ms when the breakpoint is hit) then letting it run out.

Will D
  • 844
  • 10
  • 12
7

Does not Response.IsClientConnected work fairly well for this? I have just now tried out to in my case cancel large file uploads. By that I mean if a client abort their (in my case Ajax) requests I can see that in my Action. I am not saying it is 100% accurate but my small scale testing shows that the client browser aborts the request, and that the Action gets the correct response from IsClientConnected.

Konstantin
  • 3,626
  • 2
  • 33
  • 45
  • 1
    Yes, this is the best answer here. IsClientConnected will be false in the middle of an async request if the client closes their browser. – eoleary Apr 18 '13 at 00:24
5

It's just as @Darin says. HTTP is a stateless protocol which means that there are no way (by using HTTP) to detect if the client is still there or not. HTTP 1.0 closes the socket after each request, while HTTP/1.1 can keep it open for a while (a keep alive timeout can be set as a header). That a HTTP/1.1 client closes the socket (or the server for that matter) doesn't mean that the client has gone away, just that the socket hasn't been used for a while.

There are something called COMET servers which are used to let client/server continue to "chat" over HTTP. Search for comet here at SO or on the net, there are several implementations available.

jgauffin
  • 99,844
  • 45
  • 235
  • 372
  • 3
    @jgauffin, thanks for the response. It's interesting you mention COMET because that is essentially waht I am trying to use. When using an `AsyncController`, you are engaging in long-lived HTTP connections -- *just like COMET*. Therefore, if the user closes the browser **while the connection is still open**, why can't the server know about it and inform me? – Kirk Woll Jan 23 '11 at 15:33
  • @Kirk, when using an AsyncController the client is not engaging in any long lived connections. It's just the same as a normal Controller in terms of the HTTP protocol. The only difference is that the ASP.NET will begin the request on one worker thread and could finish it on another and in-between it could use IOCP if your API supports it. But looking at the protocol level exactly the same stuff is exchange if you weren't using an async controller. – Darin Dimitrov Jan 23 '11 at 15:37
  • 2
    @Darin, I believe you are mistaken. In firebug or even just the browser directly, you can see that the connection is open. You can hit the escape key and it will terminate the connection. It's exactly like a request that is just taking a very long time to process. (or to put it another way, you are *correct* -- it's exactly like a normal controller *that is taking a very long time* to service an action -- which is indeed a long-lived HTTP connection.) – Kirk Woll Jan 23 '11 at 15:38
  • @Kirk, yes that's what I am saying, it is just as a normal controller which is taking long time to process. Absolutely no difference in terms of the HTTP protocol. So whether you are using a normal or an async controller you still cannot be notified that the client closed his browser simply because the client sends no notification to the server in this event. Also don't forget that closing the browser doesn't necessary mean that the client has closed the socket. – Darin Dimitrov Jan 23 '11 at 15:39
  • 1
    @Darin, What about the TCP RST packet? I know it's not guaranteed, but I thought most well behaving socket implementations will send that over in this scenario. – Kirk Woll Jan 23 '11 at 15:45
  • IIS knows that a client has disconnected. Asp.NET knows it too. But you will hardly be told about it. You should not need to know about it, even if you create a COMET server. What you should do in your application is to create a `Timer` which invoked a timeout if the javascript haven't triggered a request in let's say two minutes. – jgauffin Jan 23 '11 at 15:52
  • I can't stress enough that you do not handle socket closing. Your implementation will fail utterly if you do. Since a closed socket do not mean anything when using HTTP. You should use a timer and chunked results (and not async controllers) to create a well working implementation. – jgauffin Jan 23 '11 at 15:54
  • 1
    @jgauffin -- I think I see what I was missing now. You and Darin are obviously correct that after the request is finished the client can't send *any* packet, RST or otherwise. However, I do disagree with your conention that polling is better than long-lived HTTP connections. (i.e. AsyncController) BOSH and COMET use what are essentially use this technique to great effect. Anyway, I see why what I want is impossible. Bummer. Will mark this as correct. – Kirk Woll Jan 23 '11 at 15:58
  • You missunderstand me. I'm suggesting a combination of the both. Lookup chunked responses. They are used to send like different responses in the same HTTP response. They are great when trying to keep the connection open. By timer I do not mean a javascript timer but a Timer server side. It's needed to detect that I client haven't sent something for a while (unless you like to poll the DB) – jgauffin Jan 23 '11 at 16:47
  • @jgauffin, yep, I misunderstood you. And thanks for the advice on chunked transfer encoding, looks interesting, and I see what you're saying there with a timer. I appreciate you taking the time! – Kirk Woll Jan 23 '11 at 17:55
0

In a Controller in ASP .Net Core 7.0 I successfully used:

this.HttpContext.RequestAborted

to get a CancellationToken that can be passed on to the long running method and used according to common patterns and best practices

Johan Franzén
  • 2,355
  • 1
  • 17
  • 15
-2

For obvious reasons the server cannot be notified that the client has closed his browser. Or that he went to the toilet :-) What you could do is have the client continuously poll the server with AJAX requests at regular interval (window.setInterval) and if the server detects that it is no longer polled it means the client is no longer there.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 1
    I appreciate the response, but I do realize that HTTP does not allow me to know when a user closes the browser *after the response is delivered*. However, that is not what I asked. Part and parcel of an `AsyncController` is that the request *is not finished* -- it remains open. In principle, it seems possible to know that there is no client to send the repsonse to. – Kirk Woll Jan 23 '11 at 15:32
  • 1
    @Kirk, in terms of the HTTP protocol an `AsyncController` is not any different than a normal `Controller`. The protocol always stays stateless. – Darin Dimitrov Jan 23 '11 at 15:35
  • 1
    it's stateless, and it's request/response. But that transaction can *still* take a very long time. This is exactly how COMET and BOSH works (or frankly, even Gmail when you have chat enabled), and I don't understand why the termination of that live connection (it *is* a live connection, that's why you can send a response back at the end) isn't recognizable. – Kirk Woll Jan 23 '11 at 15:41
  • @Kirk, the server receives a request from the client and starts executing the request. During this execution the client closes his browser. Absolutely no notification is sent to the server in this case. The server continues his job until he finishes. Once he finishes he sends the response but there is nothing on the other end to receive it. Of course this happens much after any of the controllers that might have serviced this request have been destroyed and the ASP.NET pipeline finished executing the request and handled the response to IIS. – Darin Dimitrov Jan 23 '11 at 15:51
  • 3
    -1 HTTP sits on top of TCP and when the user closes the browser, the TCP connection is closed. The 'statelessness' of HTTP is between subsequent request/response pairs, it does not effect whether the connection has state or other states within serving the response ( e.g. reading from the tcp pipe, anything up to a double CR LF are headers - there's a state machine right bang in the middle of the HTTP protocol for you). – Pete Kirkham Sep 26 '13 at 10:32
  • 1
    As long as you have TCP KeepAlive, or the server attempts to start writing a response, or the client’s OS cleanly closes the connection, IIS knows that there is no longer a client. Other answers have actually described the ASP.NET API’s exposure of this knowledge. – binki Jan 30 '15 at 20:34