1

I've an ASP.NET Core server that, upon client request, start fetching a stream from an AXIS camera and returns it to the client for displaying. If the http request to server is made directly by the browser by means of img src attribute, it works fine. But if I use HttpClient (which I need to do since I need CancellationToken), the httpClient.GetStreamAsync instruction get stuck and I cannot parse the returned data.

Server side Controller (take from here):

[ApiController]
[Route("[controller]")]
public class CameraSystemController : ControllerBase
{
    private string _contentTypeStreaming = "multipart/x-mixed-replace;boundary=myboundary";
    private HttpClient _httpClient = new HttpClient(new HttpClientHandler { Credentials = ...});

    [HttpGet("getStream")]
    public async Task<IActionResult> GetStream(CancellationToken token)
    {
        Stream stream = await _httpClient.GetStreamAsync("http://.../axis-cgi/mjpg/video.cgi?resolution=800x600&compression=50", token);
        if (stream != null) {
            FileStreamResult result = new FileStreamResult(stream, _contentTypeStreaming) {
                EnableRangeProcessing = true
            };
            return result;
        } else {
            return new StatusCodeResult((int)HttpStatusCode.ServiceUnavailable);
        }
    }
}

Now, as I said, as long as I make the browser perform the http request by means of

// LiveCamera.razor
<img src="CameraSystem/getStream" onerror="@OnImgLoadingError" alt="">

Data acquired in live mode by the camera is correctly displayed in browser. If instead I make the request in the client this way:

//LiveCamera.razor.cs
[Inject] public HttpClient Http { get; private set; }
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();

private async void StartStreamingRequest(object sender, ElapsedEventArgs e) {
    Console.WriteLine("I'm about to request live stream");
    Stream responseStream = await Http.GetStreamAsync("CameraSystem/getStream", _cancellationTokenSource.Token);
    Console.WriteLine("Header found!");
    string boundary = "myboundary";
    for (MultipartReader streamReader = new MultipartReader(boundary, responseStream); ;) {
        MultipartSection nextFrame = await streamReader.ReadNextSectionAsync(_cancellationTokenSource.Token);
        DisplayOneFrameCallback(nextFrame.Body);
    }
}

private void DisplayOneFrameCallback(Stream body)
{
    StreamReader reader = new StreamReader(body);
    string frameData = reader.ReadToEnd();
    _imgSrc = "data:image/jpeg;base64," + frameData;
    StateHasChanged();
}

In this seconds case the request is performed correctly (server-side I can see the code executed and from task manager I can see the bandwidth usage increasing) but the client got stuck on await instruction, and subsequent code is never executed. Now, in Microsoft documentation under GetStreamAsync it is possible to read

This operation will not block. The returned Task object will complete after the response headers are read. This method does not read nor buffer the response body.

so I would expect it not to block. Thus I suppose there could be a problem in the header I'm producing server-side, even though browser request works just fine.

Just out of curiosity, I've captured with Wireshark the data between the camera and the server. The header and the initial part of the body are like this:

..-...HTTP/1.0 200 OK..Cache-Control: no-cache..Pragma: no-cache..Expires: Thu, 01 Dec 1994 16:00:00 GMT..Connection: close..Content-Type: multipart/x-mixed-replace;boundary=myboundary....--myboundary..Content-Type: image/jpeg..Content-Length: 30146......

I've double-checked with browser developer tools and I can confirm that sending the request from browser or from httpClient yield the same exact request. Furthermore right-clicking on the request url and issueing "open in new tab" opens a tab where I can see the camera live stream in both cases.

Could you please help me in this?

  • You are using a controller which waits for the end of data before deserializing. See following : https://learn.microsoft.com/en-us/previous-versions/aspnet/ee728598(v=vs.100)?force_isolation=true – jdweng Oct 04 '21 at 11:03
  • If that was the case, shouldn't the browser be blocked as well when making http requests to server? – Alessandro Martinelli Oct 04 '21 at 13:19
  • The request is probably much smaller and can be sent in one chunk. – jdweng Oct 04 '21 at 13:42
  • Sorry but I still don't get it. The resource you've pointed me is about ASP.NET MVC 2 and the AsyncController class. There is no such class in ASP.NET Core. However, if I look at the task manager I can see from the bandwitdh that data is returned, so I'm pretty sure controller is returning data. In fact, if I take the request from browser developer tools and I click "open in new tab", in the new tab the request is answered and the live video is returned – Alessandro Martinelli Oct 05 '21 at 10:08
  • Rather, looking with Postman at the header returned by my server response and by the cameras response, I can see that camera response header contains "Connection: close" attribute. Maybe that could be the problem. It's just I don't know how to add such header attribute to FileStreamResult... actually I think it is not possible. – Alessandro Martinelli Oct 05 '21 at 10:16
  • You are getting a response with many MIME attachments. See msdn for an example : https://learn.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/aa563375(v=exchg.140)?force_isolation=true – jdweng Oct 05 '21 at 10:26
  • I got to add some headers to the response with Response.Headers.Add("Connection", "close"); Response.Headers.Add("Cache-Control", "no-cache"); and so on, however this was not enought to solve the problem: while if I make a request with Postman the headers I've added are actually in the response, when I make the request with Blazor, ASP.NET Core console output says that kestrel is removing headers like Connection because they are invalid for HTTP/2 and HTTP/3, and request blocks (until timeout) as before. – Alessandro Martinelli Oct 05 '21 at 11:20
  • You can change the HTTP Request Protocol version to either 1.0 (stream mode) or 1.1 (chunk mode). See following : https://github.com/dotnet/runtime/issues/23251?force_isolation=true – jdweng Oct 05 '21 at 12:27

0 Answers0