Detecting async client disconnect in ASP.NET MVC - c#

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)?

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.

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.

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.

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.

Related

Is it possible to add a Cancellation Token to http request into request URL itself? ASP.NET Core

I've a Blazor client which shows a live stream of images coming from a ASP.NET Core (.NET 6) Web Application Server which in turn requests the stream to a camera (multiple cameras actually), which returns a multipart/x-mixed-replace content.
When the user (client side) leaves the page containing the stream of images, I'd like to server to stop requesting the camera live stream. I know I need to use Cancellation Token, as explained here, but it seems not to work if client side I make the stream request like this:
// CameraLive.razor
<img src="CameraSystem/startStreaming">
Server side, request is forwarded to the actual device like this (simplified code)
// CameraSystemController.cs
[ApiController]
[Route("[controller]")]
public class CameraSystemController : ControllerBase
{
private string _contentTypeStreaming = "multipart/x-mixed-replace;boundary=myboundary";
private HttpClient _httpClient;
[HttpGet("startStreaming")]
public async Task<IActionResult>StartCameraStreaming(CancellationToken token)
{
Stream stream = await _httpClient.GetStreamAsync("http://[...]", token);
FileStreamResult result = new FileStreamResult(stream, _contentTypeStreaming) {
EnableRangeProcessing = true
};
return result;
}
}
Currently, every time a client open CameraLive.razor page, the server request a stream to the camera and returns it. If on the same client I move to another page of the web application and later I re-enter CameraLive.razor, I see the previous request is not canceled and a new one is made. Actually, if I look at task manager, I can see the bandwidth usage is doubled.
The requests are canceled only if I close the browser tab.
I'd like the request to stop as soon as the client moves to another page, as it happens if the client was to be designed this way:
// CameraLive.razor
<img src="#_frame">
// CameraLive.razor.cs
private CancellationTokenSource _tokenSource;
private System.IO.Stream _stream;
private void RetrieveLiveStream() {
_stream = await Http.GetStreamAsync("CameraSystem/startStreaming", _tokenSource.Token);
// Every time a new piece of stream comes,
// I need to extract the frame from it and update the web page
_frame = ...
stateHasChanged();
}
public void Dispose()
{
if (_tokenSource != null) {
_tokenSource.Cancel();
_tokenSource.Dispose();
}
}
If I design the client in this second way the cancellation works: as soon as the user moves to another page, the request is canceled (I can see it clearly from the bandwidth usage data)
However I'd have liked to stick with the first solution because
It's simpler, I don't have to handle all the frame-extraction related code, and I suppose the way browser does it is more efficient than the way I would do (I've never done it, I just tried making the request, doing nothing with the result and then leave the page just to make sure that request is actually canceled).
I have the feeling that having the browser auto-update the image is more efficient than having me calling StateHasChanged() continuously.
So, from what I get, in order to have the cancellationToken to work properly, I need to send the token from the client along with the http request. However in the first solution it seems like it is not possible to pass the cancellationToken along with the request URL, so I don't know what I should do.
Any advice?

SignalR Authorize is being called multiple times

I am trying to keep track of connected users to my hub.
The way I tried to do this was by creating a custom Authorize attribute for my hub, and checking for the user that is trying to connect. If the user is already connected then the hub does not authorize the connection
public class SingleHubConnectionPerUserAttribute : Microsoft.AspNet.SignalR.AuthorizeAttribute
{
private static readonly HashSet<UserKey> connections = new HashSet<UserKey>();
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
Type hubType = hubDescriptor.HubType;
string userId = request.User.Identity.GetUserId();
UserKey userKey = new UserKey(hubType, userId);
if (connections.Contains(userKey) || !base.AuthorizeHubConnection(hubDescriptor, request))
{
return false;
}
connections.Add(userKey);
return true;
}
}
This would work fine if the method AuthorizeHubConnection was called only once per connection, but that is not what is happening.
When I load the page that tries to connect with the hub, AuthorizeHubConnection oddly runs multiple times, and the number of times it runs is not always the same, sometimes it's 5, some it's 3, I really have no clue of what could possibly be causing it.
Do you know what could cause AuthorizeHubConnection to get called more than once?
Authorization is invoked each time SignalR server receives an HTTP request before it does anything else (See: https://github.com/SignalR/SignalR/blob/dev/src/Microsoft.AspNet.SignalR.Core/PersistentConnection.cs#L161). While SignalR maintains a logically persistent connection it makes multiple HTTP Requests behind the scenes. When using Websockets transport you will typically see only 3 of these when starting the connection (for the negotiate, connect and start requests) and one for each reconnect. longPolling and serverSentEvents transport create an HTTP request each time to send data (send). In addition longPolling creates a polling HTTP request to receive data (poll). Each of these requests has to be authorized so this is the reason why you see multiple calls to the AuthorizeHubConnection method.

Speeding up http web request in WCF invoked method

Hi I am writing a WCF service not hosted on IIS. It runs on my server as console application.
I have a static method that is invoked by the mentioned service.
In this method I have async web request that sends sms.
Sometimes it happens that sms is never recieved. After some debuging I found out that when I remove async call the web request sometimes throws an exception with message: 'The operation has timed out'. This happens when i try to send many smss in short period of time.
But when i type the address of the web request in browser everything works fine. (the times i press refresh, no matter how fast, that number of times i receive sms) how can this be achived with what i've got.
So far i have
public static bool DoTheRequest(string number, string message)
{
try
{
HttpWebRequest myReq = (HttpWebRequest)WebRequest.Create(string.Format("http://SomeURL?With={0}&something={1}", number, message));
myReq.BeginGetResponse(FinishWebRequest, myReq);
}
catch (Exception ex)
{
throw ex;
}
return true;
}
static void FinishWebRequest(IAsyncResult result)
{
HttpWebResponse response = (result.AsyncState as HttpWebRequest).EndGetResponse(result) as HttpWebResponse;
}
EDIT:
And the service definition:
[OperationContract]
void TestSms(string number);
and implementation:
public void TestSms(string number)
{
Utilities.DoTheRequest(number, "THIS IS A TEST");
}
Please help
Depending on how many times you are calling the send-sms function in a short period of time, I surmise that it's doing it a lot faster than you can refresh in your browser - and you're managing to flood whatever service it is you are using with your SMS calls.
In this scenario, I would suggest that your web-service method actually puts the SMS in to a queue, and some kind of a background worker thread that is designed not to throttle the API that actually sends the SMS, has the job of batching up these SMSs and sending them.
Better yet, to facilitate the issue that machines and software are not perfect, and lightning does indeed strike, I would suggest that you push SMSs in to some kind of back-end data store and leave it at that. A background worker job/thread then has the job of finding all SMSs that are unsent and trying to send them (but sending no more than "x per minute"), each one being marked as "Sent" upon completion.
This has the added advantage that you can throw as many SMSs as you want at your backing data storage, if the machine dies it can pick up where it left off, and if you get some failures, they can be retried.

Async WCF Service

To make this easier to understand: We are using a database that does not have connection pooling built in. We are implementing our own connection pooler.
Ok so the title probably did not give the best description. Let me first Describe what I am trying to do. We have a WCF Service (hosted in a windows service) that needs to be able to take/process multiple requests at once. The WCF service will take the request and try to talk to (say) 10 available database connections. These database connections are all tracked by the WCF service and when processing are set to busy. If a request comes in and the WCF tries to talk to one of the 10 database connections and all of them are set to busy we would like the WCF service to wait for and return the response when it becomes available.
We have tried a few different things. For example we could have while loop (yuck)
[OperationContract(AsyncPattern=true)]
ExecuteProgram(string clientId, string program, string[] args)
{
string requestId = DbManager.RegisterRequest(clientId, program, args);
string response = null;
while(response == null)
{
response = DbManager.GetResponseForRequestId(requestId);
}
return response;
}
Basically the DbManager would track requests and responses. Each request would call the DbManager which would assign a request id. When a database connection is available it would assign (say) Responses[requestId] = [the database reponse]. The request would constantly ask the DbManager if it had a response and when it did the request could return it.
This has problems all over the place. We could possibly have multiple threads stuck in while loops for who knows how long. That would be terrible for performance and CPU usage. (To say the least)
We have also looked into trying this with events / listeners. I don't know how this would be accomplished so the code below is more of how we envisioned it working.
[OperationContract(AsyncPattern=true)]
ExecuteProgram(string clientId, string program, string[] args)
{
// register an event
// listen for that event
// when that event is called return its value
}
We have also looked into the DbManager having a queue or using things like Pulse/Monitor.Wait (which we are unfamiliar with).
So, the question is: How can we have an async WCF Operation that returns when it is able to?
WCF supports the async/await keywords in .net 4.5 http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx. You would need to do a bit of refactoring to make your ExecuteProgram async and make your DbManager request operation awaitable.
If you need your DbManager to manage the completion of these tasks as results become available for given clientIds, you can map each clientId to a TaskCompletionSource. The TaskCompletionSource can be used to create a Task and the DbManager can use the TaskCompletionSource to set the results.
This should work, with a properly-implemented async method to call:
[OperationContract]
string ExecuteProgram(string clientId, string program, string[] args)
{
Task<string> task = DbManager.DoRequestAsync(clientId, program, args);
return task.Result;
}
Are you manually managing the 10 DB connections? It sounds like you've re-implemented database connection pooling. Perhaps you should be using the connection pooling built-in to your DB server or driver.
If you only have a single database server (which I suspect is likely), then just use a BlockingCollection for your pool.

Is there such a RTSP Ping?

I am currently working on a WinForm app to stream videos from IP camera using the RTSP protocol in C#. Everything worked fine. Part of the requirement for the app includes a function to check whether the IP camera is online or not.
So I did a ping function using the System.Net.NetworkInformation.Ping class to ping the IP camera. Say if the RTSP url of the camera is as follows rtsp://[CAMERA IP]:554/Master0-RTSP/1.0, I would only need to extract the [CAMERA IP] part and use the Ping class to see if the camera is online or not by using its IP.
Initially, it works until an issue came, say if one to enter an IP which may not be the intended IP Camera (say an IP of a computer) the ping function would still work if the entered IP of the entered device is online.
I tried to search for something like a RTSP ping but could not find one. Was hoping for any advices or opinions on this matter. Any example in C# are greatly appreciated. Thank you for your kind attention.
OPTIONS can possibly work but the standard specifies the correct way is through using theGET_PARAMETER.
RFC2326 outlines that clearly
http://www.ietf.org/rfc/rfc2326.txt
10.8 GET_PARAMETER
The GET_PARAMETER request retrieves the value of a parameter of a
presentation or stream specified in the URI. The content of the reply
and response is left to the implementation. GET_PARAMETER with no
entity body may be used to test client or server liveness ("ping").
While GET_PARAMETER may not be supported by the server there is no way to tell how that server will react to the OPTIONS request which does not even require a sessionID. Therefor it cannot be guaranteed it will keep your existing session alive.
This is clear from reading the same RFC about the OPTIONS request
10.1 OPTIONS
The behavior is equivalent to that described in [H9.2]. An OPTIONS
request may be issued at any time, e.g., if the client is about to
try a nonstandard request. It does not influence server state.
Example:
C->S: OPTIONS * RTSP/1.0
CSeq: 1
Require: implicit-play
Proxy-Require: gzipped-messages
S->C: RTSP/1.0 200 OK
CSeq: 1
Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE
Note that these are necessarily fictional features (one would hope
that we would not purposefully overlook a truly useful feature just
so that we could have a strong example in this section).
If GET_PARAMETER is not supported then you would issue a PLAY request with the SessionId of the session you want to keep alive.
This should work even if OPTIONS doesn't as PLAY honors the Session ID and if you are already playing there is no adverse effect.
For the C# RtspClient see my project # https://net7mma.codeplex.com/
And the article on CodeProject # http://www.codeproject.com/Articles/507218/Managed-Media-Aggregation-using-Rtsp-and-Rtp
Regarding RTSP in C# see this thread Using RTMP or RTSP protocol in C#
Regarding Ping ... you can implement is as DESCRIBE operation ... but pay attention do not make it too frequently, the device should be affected.
http://www.ietf.org/rfc/rfc2326.txt
Instead of ICMP ping, you might want to keep a helper RTSP session without video/audio RTP streams, checking good standing of socket connection and sending OPTIONS or DESCRIBE command on a regular basis, e.g. once a minute, in order to see if the device is responsive.
Some suggest using GET_PARAMETER instead of options, however this is inferior method. OPTIONS is mandatory, GET_PARAMETER is not. Both serve different purpose. Both have small server side execution expense. OPTIONS is clearly the better of the two.
Some servers may not support setting stream parameters and thus not support GET_PARAMETER and SET_PARAMETER.
You can use RTSPClientSharp and do something like this:
public static async Task TestRTSPConnection(string rtspAddress, string user, string password)
{
var serverUri = new Uri(rtspAddress);
var credentials = new NetworkCredential(user, password);
var connectionParameters = new ConnectionParameters(serverUri, credentials);
var cancellationTokenSource = new CancellationTokenSource();
var connectTask = ConnectAsync(connectionParameters, cancellationTokenSource.Token);
if (await Task.WhenAny(connectTask, Task.Delay(15000 /*timeout*/)) == connectTask)
{
if (!connectTask.Result)
{
logger.Warn("Connection refused - check username and password");
}
logger.Info("Connection test completed");
}
else
{
logger.Warn("Connection timed out - check username and password");
}
}
private static async Task<bool> ConnectAsync(ConnectionParameters connectionParameters, CancellationToken token)
{
try
{
using (var rtspClient = new RtspClient(connectionParameters))
{
rtspClient.FrameReceived +=
(sender, frame) => logger.Info($"New frame {frame.Timestamp}: {frame.GetType().Name}");
while (true)
{
logger.Info("Connecting...");
try
{
await rtspClient.ConnectAsync(token);
}
catch (OperationCanceledException)
{
logger.Info("Finishing test before connection could be established. Check credentials");
return false;
}
catch (RtspClientException e)
{
logger.Error($"{e.Message}: {e.InnerException?.Message}");
return false;
}
logger.Info("Connected - camera is online");
return true;
}
}
}
catch (OperationCanceledException)
{
return false;
}
}
It works for me pretty well if you just care about pinging and if the camera is online or not. Also timeout happens when credentials are incorrect. You get direct failure if port is not exposed or connection is refused.

Categories

Resources