I am trying to consume an API endpoint that returns a stream of text. When I open the endpoint directly in my browser address bar, it works fine and it is clear that it is in fact streaming the response and not just returning it all at once.
However, although it works, I can see in the console that I get a ERR_INCOMPLETE_CHUNKED_ENCODING
error.
This error is a likely reason why my client app is unable to consume this endpoint.
However, when I call the endpoint from my Blazor app, I get this error/stacktrace which does not really tell me what the problem is.
Here is my code in my Blazor app that is calling the endpoint:
using var request = new HttpRequestMessage(HttpMethod.Get, path);
request.SetBrowserResponseStreamingEnabled(true);
using var response = await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
using var stream = await response.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream);
var buffer = new char[1024];
int bytesRead;
while ((bytesRead = await reader.ReadBlockAsync(buffer, 0, buffer.Length)) > 0)
{
var text = new string(buffer, 0, bytesRead);
Console.Write(text);
yield return text;
}
The error happens on the while
line above.
When I look in the Network tab, then the preflight (OPTIONS) call is successful and the actual fetch call is also 200 OK but the response seems empty in browsers Developer Tools.
This is what it looks like in the browser console.
I am already doing 100s of API calls from this Blazor app to that API so the difference here is that this particular endpoint returns a stream of text instead of just a normal json response.
- What could be the cause of the error?
and/or
- How can I find the actual error that is happening?
Someone mentioned the issue could be related to the serverside code. That could very well be right as the error is also shown when just inserting the endpoint in the address bar of the browser.
I am including the .NET 6 Azure Function here:
[FunctionName("RespondWithStream")]
public static async Task Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "stream-text")] HttpRequest req,
ILogger log)
{
try
{
List<string> list = new List<string>
{
"text1",
"text2",
"text3"
};
var response = req.HttpContext.Response;
response.StatusCode = 200;
response.ContentType = "application/json";
response.Headers.Add("Access-Control-Allow-Origin", "*");
response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.Headers.Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, photish-tokenkey");
response.Headers.Add("Access-Control-Allow-Credentials", "true");
StringBuilder sb = new();
await using var sw = new StreamWriter(response.Body);
foreach (var item in list)
{
sb.Append(item);
await sw.WriteAsync(item);
await sw.FlushAsync();
Thread.Sleep(2000);
}
}
catch (Exception exc)
{
log.LogError(exc, "Issue when streaming: " + exc.Message);
}
}
Solution
Here is the final code that solved the issue. I believe the key was CompleteAsync
and that was unavailable before I switched to IHttpResponseBodyFeature
[FunctionName("RespondWithStream")]
public static async Task Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "stream-text")] HttpRequest req,
ILogger log)
{
var response = req.HttpContext.Response;
var responseBody = response.HttpContext.Features.Get<IHttpResponseBodyFeature>();
responseBody.DisableBuffering();
response.StatusCode = 200;
response.ContentLength = null;
response.ContentType = "text/plain";
await using var sw = new StreamWriter(response.Body);
for (int i = 0; i < 100; i++)
{
await responseBody.Writer.WriteAsync(Encoding.UTF8.GetBytes(i+"."));
await responseBody.Writer.FlushAsync();
await Task.Delay(100);
}
await responseBody.CompleteAsync();
}