Cannot Interrupt HttpClient.GetStreamAsync by means of CancellationToken in ASP.NET Core - asp.net-core

I'm trying to send an http request to an AXIS Camera in order to receive a stream.
Everything works fine except that I can't get to use CancellationToken to cancel the request when it is no more needed. I've the following architecture:
Blazor client:
// LiveCamera.razor
<img src="CameraSystem/getStream" onerror="[...]" alt="">
ASP.NET Core Server:
// CameraSystemController.cs
[ApiController]
[Route("[controller]")]
public class CameraSystemController : Controller
{
[HttpGet("getStream")]
public async Task<IActionResult> GetStream()
{
Stream stream = await Device_CameraStandard.GetStream();
if (stream != null) {
Response.Headers.Add("Cache-Control", "no-cache");
FileStreamResult result = new FileStreamResult(stream, _contentTypeStreaming) {
EnableRangeProcessing = true
};
return result;
} else {
return new StatusCodeResult((int)HttpStatusCode.ServiceUnavailable);
}
}
}
Class accessing the camera:
// Device_CameraStandard.cs
internal class Device_CameraStandard
{
private HttpClient _httpClient;
private static CancellationTokenSource _tokenSource;
private System.Timers.Timer _keepAliveTimer;
internal Device_CameraStandard() {
_keepAliveTimer = new System.Timers.Timer();
_keepAliveTimer.Interval = 3000;
_keepAliveTimer.Elapsed += KeepAliveTimeout;
_tokenSource = new CancellationTokenSource();
[...]
}
internal async Task<Stream> GetStream()
{
return await _httpClient.GetStreamAsync("http://[...]/axis-cgi/mjpg/video.cgi?&camera=1", _tokenSource.Token);
}
// Invoked periodically by client from LiveCamera.razor.cs, not included here
internal void KeepAlive()
{
LLogger.Debug("KeepAlive!");
_keepAliveTimer.Stop();
_keepAliveTimer.Start();
}
private void KeepAliveTimeout(object sender, ElapsedEventArgs e)
{
LLogger.Debug("Timeout!");
_keepAliveTimer.Stop();
_tokenSource.Cancel();
_tokenSource.Dispose();
_tokenSource = new CancellationTokenSource();
}
}
However, even if all clients leave LiveCamera.razor page and the _keepAliveTimer elapses and the CancellationTokenSource is canceled, the request is not canceled. I can see it from the fact that bandwidth usage does not decreases (the "receiving" bandwitdh, indicating that Server is still receiving data from camera), it only decreases if I close the browser tab.
Could you please help me to understand what am I doing wrong? Thanks
EDIT: In the end, even after following the suggestion of observing the token in all code parts where the returned stream was used, included the controller, I ended up discovering that the tag
// LiveCamera.razor
<img src="CameraSystem/getStream" onerror="[...]" alt="">
was causing the client to never stop sending requests. Thus I had to use a workaround to force client to stop sending requests before leaving LiveCamera.razor page.

Related

How to detect when client has closed stream when writing to Response.Body in asp.net core

I'm trying to write an infinite length response body and detect when a client disconnects so I can stop writing. I'm used to getting socket exceptions or similar when a client closes the connection but that doesn't seem to be happening when writing directly to Response.Body. I can close the client applications and the server side just keeps on writing. I've included the relevant code below. It's entirely possible there is a better way to do it but this came to mind. Basically I have a live video feed which should go on forever. I'm writing to ResponseBody as chunked content (No content length, flushing after each video frame). The video frames are received via an event callback from elsewhere in the program so I'm subscribing to the events in the controller method and then forcing it to stay open with the await Task.Delay loop so the Response stream isn't closed. The callback for H264PacketReceived is formatting the data as a streaming mp4 file and writing it to the Response Stream. This all seems to work fine, I can play the live stream with ffmpeg or chrome, but when I close the client application I don't get an exception or anything. It just keeps writing to the stream without any errors.
public class LiveController : ControllerBase
{
[HttpGet]
[Route("/live/{cameraId}/{stream}.mp4")]
public async Task GetLiveMP4(Guid cameraId, int stream)
{
try
{
Response.StatusCode = 200;
Response.ContentType = "video/mp4";
Response.Headers.Add("Cache-Control", "no-store");
Response.Headers.Add("Connection", "close");
ms = Response.Body;
lock (TCPVideoReceiver.CameraStreams)
{
TCPVideoReceiver.CameraStreams.TryGetValue(cameraId, out cameraStream);
}
if (this.PacketStream == null)
{
throw new KeyNotFoundException($"Stream {cameraId}_{stream} not found");
}
else
{
connected = true;
this.PacketStream.H264PacketReceived += DefaultStream_H264PacketReceived;
this.PacketStream.StreamClosed += PacketStream_StreamClosed;
}
while(connected)
{
await Task.Delay(1000);
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
finally
{
connected = false;
this.PacketStream.H264PacketReceived -= DefaultStream_H264PacketReceived;
this.PacketStream.StreamClosed -= PacketStream_StreamClosed;
}
}
private bool connected = false;
private PacketStream PacketStream;
private Mp4File mp4File;
private Stream ms;
private async void PacketStream_StreamClosed(PacketStream source)
{
await Task.Run(() =>
{
try
{
Console.WriteLine($"Closing live stream");
connected = false;
ms.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
});
}
private async void DefaultStream_H264PacketReceived(PacketStream source, H264Packet packet)
{
try
{
if (mp4File == null && packet.IsIFrame)
{
mp4File = new Mp4File(null, packet.sps, packet.pps);
var _p = mp4File.WriteHeader(0);
await ms.WriteAsync(mp4File.buffer, 0, _p);
}
if (mp4File != null)
{
var _p = mp4File.WriteFrame(packet, 0);
var start = mp4File._moofScratchIndex - _p;
if (_p > 0)
{
await ms.WriteAsync(mp4File._moofScratch, start, _p);
await ms.FlushAsync();
}
}
return;
}
catch (Exception ex)
{
connected = false;
Console.WriteLine(ex.ToString());
}
}
Answering my own question.
When the client disconnects mvc core sets the cancellation token HttpContext.RequestAborted
By monitoring and/or using that cancellation token you can detect a disconnect and clean everything up.
That said, the entire design can be improved by creating a custom stream which encapsulates the event handling (producer/consumer). Then the controller action can be reduced to.
return File(new MyCustomStream(cameraId, stream), "video/mp4");
The File Method already monitors the cancellation token and everything works as you'd expect.

WebApi 2: Custom HttpResponseMessage after handling OperationCanceledException in Message Handler is not returned to client

I am currently investigating in WebApi 2 Message Handler and how to implement a server side timeout using cancellation tokens. If a cancellation occurs a OperationCanceledException is thrown and handled in my Message Handler. In this case I return a HttpResponseMessage with a adequate HttpStatusCode (HttpStatusCode.RequestTimeout).
I expected that my consuming client (using postman) retrieves this HttpStatusCode, but instead "Could not get any response" is displayed, thus my client aborts without any additional information. Could someone explain to me whats the deal with this behavior? What am I missing?
See following example code:
public class RequestTimeoutHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
TimeSpan timeout = TimeSpan.FromSeconds(10.0);
using (CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(timeout))
using (CancellationTokenSource linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token))
{
try
{
return await base.SendAsync(request, linkedCancellationTokenSource.Token);
}
catch (OperationCanceledException e)
{
return new HttpResponseMessage(HttpStatusCode.RequestTimeout);
}
}
return null;
}
}
My Test Controller Method looks as follows:
[Route("testTimeoutAsyncHandleException"), HttpPost]
public IHttpActionResult TestTimeoutAsynchandle(string hugo, CancellationToken ct)
{
for (int i = 0; i < 500; i++)
{
Thread.Sleep(1000); //sleep 1 sec until exception is thown
if (ct.IsCancellationRequested)
{
ct.ThrowIfCancellationRequested();
}
}
return Ok("yes");
}
turns out, using postman was not my best idea. I created a console application using a httpclient and requested my test method. the httpstatus 408 is returned as expected.

Keep connection alive for streaming in ASP.NET Core

I'm making a small web application which is built on ASP.NET Core. My application is for streaming video from clients to clients through service.
I've followed this post :
http://www.strathweb.com/2013/01/asynchronously-streaming-video-with-asp-net-web-api/
I've implemented the application of tutorial successfully, but, that was for streaming Video from server to clients.
What I wanna do now is :
Clients register to service for streaming. (using video or audio tag)
Service receives client submitted data (submit through POSTMAN)
Service broadcast the data to its every registered clients.
Here is what I've implemented:
(Index.cshtml)
<div>
<video width="480"
height="320"
controls="controls"
autoplay="autoplay">
<source src="/api/video/initiate"
type="video/mp4">
</source>
</video>
</div>
StreamingService
public class StreamingService: IStreamingService
{
public IList<Stream> Connections {get;set;}
public StreamingService()
{
Connections = new List<Stream>();
}
public byte[] AnalyzeStream(Stream stream)
{
long originalPosititon = 0;
if (stream.CanSeek)
{
originalPosititon = stream.Position;
stream.Position = 0;
}
try
{
var readBuffer = new byte[4096];
int bytesReader;
while ((byteRead = stream.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0)
{
totalBytesRead += byteRead;
if (totalBytesRead == readBuffer.Length)
{
var nextByte = stream.ReadByte();
if (nextByte != -1)
{
var temp = new byte[readBuffer * 2];
Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length);
Buffer.SetByte(temp, totalBytesRead, (byte)nextByte);
readBuffer = temp;
totalBytesRead++;
}
}
}
var buffer = readBuffer;
if (readBuffer.Length != totalBytesRead)
{
buffer = new byte[totalBytesRead];
Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead);
}
return buffer;
}
finally
{
if (stream.CanSeek)
stream.Position = originalPosititon;
}
}
}
VideoController
public class VideoController: Controller
{
private readonly IStreamingService _streamingService;
private readonly IHostingEnvironment _hostingEnvironment;
public VideoController(IStreamingService streamingService, IHostingEnvironment hostingEnvironment)
{
_streamingService = streamingService;
_hostingEnvironment = hostingEnvironment;
}
[HttpGet("initiate")]
public IActionResult Initiate()
{
_streamingService.Connections.Add(Response.Body);
}
[HttpPost("broadcast")]
public async Task<IActionResult> Broadcast()
{
// Retrieve data submitted from POSTMAN.
var data = _streamingService.AnalyzeStream(Request.Body);
foreach (var stream in _streamingService.Connections)
{
try
{
await stream.WriteAsync(data, 0, data.Length);
}
catch (Exception exception)
{
stream.Dispose();
_streamingService.Connections.Remove(stream);
}
}
}
}
When I send data from POSTMAN through api/video/broadcast . For loop ran and I got an exception said the stream has been disposed.
My question is:
How can I keep the stream alive for streaming ?
(Stream created in api/video/initiate is kept alive and when a client calls api/video/broadcast , all initiated stream will update its date without having disposed)
Thank you,
Is it an option to keep the stream in cache?
You can read more about it here. The simplest way it to add the cache services to the dependency injection container and the request the concrete implementation of IMemoryCache through constructor injection in your VideoController (as you've done with IStreamingService and IHostingEnvironment).
Just add the stream to the cache and use the cached stream the next time api/video/broadcast is hit.
Be aware though that if you are on a webfarm or hosted in the cloud it is recommended to use Distributed Cache like Redis Cache, or else your cache could disapear unexpected. I use Azure Redis Cache for instance which works great!

Catching an exception from wrapped EAP request to WCF

I have a WCF request in WP8 environment that I wrapped according to this
http://msdn.microsoft.com/en-us/library/hh873178%28v=vs.110%29.aspx#EAP
My call to the WCF service proceeds as follows:
try
{
var result = await mWCFClient.PerformRequestAsync();
}
catch(Exception e)
{
}
where PerformRequestAsync is an extension method. i.e.
public static ResultType PerformRequestAsync(this WCFClient client)
{
// EAP wrapper code
}
What happens is that occasionally something goes wrong on the WCF service and it returns "NotFound". I am not 100% sure why this happens and it seems like a rare occasion. The problem, however, is not the WCF service behavior, but the fact that it breaks in the EndPerformRequestAsync() in the automatically generated WCF code instead of going to my exception handler.
How and where should I be catching this exception as it never reaches my intended handler?!
[Edit]
As per Stephen's request, I've included the wrapper code here:
public static Task<RegistrationResult> RegisterAsync(this StoreServiceReference.StoreServiceClient client, string token, bool dummy)
{
var tcs = new TaskCompletionSource<RegistrationResult>();
EventHandler<RegisterCompletedEventArgs> handler = null;
handler = (_, e) =>
{
client.RegisterCompleted -= handler;
if (e.Error != null)
tcs.TrySetException(e.Error);
else if (e.Cancelled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(e.Result);
};
client.RegisterCompleted += handler;
PerformStoreRequest(client, () => client.RegisterAsync(), token);
return tcs.Task;
}
private static void PerformStoreRequest(StoreServiceClient client, Action action, string token)
{
using (new OperationContextScope(client.InnerChannel))
{
HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty();
requestMessage.Headers[STORE_TOKEN_HTTP_HEADER] = token;
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestMessage;
action.Invoke();
// TODO: Do we need to add handler here?
}
}
Now that I look at it, I think the problem stems from the nature of action invoke. But adding custom headers to WP8 WCF services already is a pain.
The action inside is an async operation, but Invoke as far as I know is not.
What's the proper way to go about it here?

Maximum threads issue

To begin with, I checked the discussions regarding this issue and couldn't find an answer to my problem and that's why I'm opening this question.
I've set up a web service using restlet 2.0.15.The implementation is only for the server. The connections to the server are made through a webpage, and therefore I didn't use ClientResource.
Most of the answers to the exhaustion of the thread pool problem suggested the inclusion of
#exhaust + #release
The process of web service can be described as a single function.Receive GET requests from the webpage, query the database, frame the results in XML and return the final representation. I used a Filter to override the beforeHandle and afterHandle.
The code for component creation code:
Component component = new Component();
component.getServers().add(Protocol.HTTP, 8188);
component.getContext().getParameters().add("maxThreads", "512");
component.getContext().getParameters().add("minThreads", "100");
component.getContext().getParameters().add("lowThreads", "145");
component.getContext().getParameters().add("maxQueued", "100");
component.getContext().getParameters().add("maxTotalConnections", "100");
component.getContext().getParameters().add("maxIoIdleTimeMs", "100");
component.getDefaultHost().attach("/orcamento2013", new ServerApp());
component.start();
The parameters are the result of a discussion present in this forum and modification by my part in an attempt to maximize efficiency.
Coming to the Application, the code is as follows:
#Override
public synchronized Restlet createInboundRoot() {
// Create a router Restlet that routes each call to a
// new instance of HelloWorldResource.
Router router = new Router(getContext());
// Defines only one route
router.attach("/{taxes}", ServerImpl.class);
//router.attach("/acores/{taxes}", ServerImplAcores.class);
System.out.println(router.getRoutes().size());
OriginFilter originFilter = new OriginFilter(getContext());
originFilter.setNext(router);
return originFilter;
}
I used an example Filter found in a discussion here, too. The implementation is as follows:
public OriginFilter(Context context) {
super(context);
}
#Override
protected int beforeHandle(Request request, Response response) {
if (Method.OPTIONS.equals(request.getMethod())) {
Form requestHeaders = (Form) request.getAttributes().get("org.restlet.http.headers");
String origin = requestHeaders.getFirstValue("Origin", true);
Form responseHeaders = (Form) response.getAttributes().get("org.restlet.http.headers");
if (responseHeaders == null) {
responseHeaders = new Form();
response.getAttributes().put("org.restlet.http.headers", responseHeaders);
responseHeaders.add("Access-Control-Allow-Origin", origin);
responseHeaders.add("Access-Control-Allow-Methods", "GET,POST,DELETE");
responseHeaders.add("Access-Control-Allow-Headers", "Content-Type");
responseHeaders.add("Access-Control-Allow-Credentials", "true");
response.setEntity(new EmptyRepresentation());
return SKIP;
}
}
return super.beforeHandle(request, response);
}
#Override
protected void afterHandle(Request request, Response response) {
if (!Method.OPTIONS.equals(request.getMethod())) {
Form requestHeaders = (Form) request.getAttributes().get("org.restlet.http.headers");
String origin = requestHeaders.getFirstValue("Origin", true);
Form responseHeaders = (Form) response.getAttributes().get("org.restlet.http.headers");
if (responseHeaders == null) {
responseHeaders = new Form();
response.getAttributes().put("org.restlet.http.headers", responseHeaders);
responseHeaders.add("Access-Control-Allow-Origin", origin);
responseHeaders.add("Access-Control-Allow-Methods", "GET,POST,DELETE"); //
responseHeaders.add("Access-Control-Allow-Headers", "Content-Type");
responseHeaders.add("Access-Control-Allow-Credentials", "true");
}
}
super.afterHandle(request, response);
Representation requestRepresentation = request.getEntity();
if (requestRepresentation != null) {
try {
requestRepresentation.exhaust();
} catch (IOException e) {
// handle exception
}
requestRepresentation.release();
}
Representation responseRepresentation = response.getEntity();
if(responseRepresentation != null) {
try {
responseRepresentation.exhaust();
} catch (IOException ex) {
Logger.getLogger(OriginFilter.class.getName()).log(Level.SEVERE, null, ex);
} finally {
}
}
}
The responseRepresentation does not have a #release method because it crashes the processes giving the warning WARNING: A response with a 200 (Ok) status should have an entity (...)
The code of the ServerResource implementation is the following:
public class ServerImpl extends ServerResource {
String itemName;
#Override
protected void doInit() throws ResourceException {
this.itemName = (String) getRequest().getAttributes().get("taxes");
}
#Get("xml")
public Representation makeItWork() throws SAXException, IOException {
DomRepresentation representation = new DomRepresentation(MediaType.TEXT_XML);
DAL dal = new DAL();
String ip = getRequest().getCurrent().getClientInfo().getAddress();
System.out.println(itemName);
double tax = Double.parseDouble(itemName);
Document myXML = Auxiliar.getMyXML(tax, dal, ip);
myXML.normalizeDocument();
representation.setDocument(myXML);
return representation;
}
#Override
protected void doRelease() throws ResourceException {
super.doRelease();
}
}
I've tried the solutions provided in other threads but none of them seem to work. Firstly, it does not seem that the thread pool is augmented with the parameters set as the warnings state that the thread pool available is 10. As mentioned before, the increase of the maxThreads value only seems to postpone the result.
Example: INFO: Worker service tasks: 0 queued, 10 active, 17 completed, 27 scheduled.
There could be some error concerning the Restlet version, but I downloaded the stable version to verify this was not the issue.The Web Service is having around 5000 requests per day, which is not much.Note: the insertion of the #release method either in the ServerResource or OriginFilter returns error and the referred warning ("WARNING: A response with a 200 (Ok) status should have an entity (...)")
Please guide.
Thanks!
By reading this site the problem residing in the server-side that I described was resolved by upgrading the Restlet distribution to the 2.1 version.
You will need to alter some code. You should consult the respective migration guide.