Does an HttpClient await the Header and the body?

6/10/2024

If you call HttpClient.GetAsync or HttpClient.PostAsync and then await the result, does the await wait only for the headers to be received, or does it wait for the body to be received? Let's find out!

Our simple server

We will create a simple server that sends a response with a header and a body. The body will be sent in chunks so we can see when it is received.

app.MapGet("/", async context =>
    {
        await context.Response.WriteAsync("1");
        await Task.Delay(500);
        await context.Response.WriteAsync("2");
        await Task.Delay(500);
        await context.Response.WriteAsync("3");
        await context.Response.CompleteAsync();
    });

So, the whole request takes at least 1 second to complete. So, let's write a very simple call to that endpoint

The client

using var client = new HttpClient();

// Let's measure the time it takes to get the response
var sw = Stopwatch.StartNew();
await client.GetAsync("https://localhost:5001");
Console.WriteLine($"Response-Time: {sw.ElapsedMilliseconds}ms");

If we execute this code, we will see that the response time is around 1 second. So the await waits for the whole response to be received:

Response-Time: 1088ms

Now that we answered the question, let's see how we can actually not await the body! We can use the HttpCompletionOption.ResponseHeadersRead option:

var sw = Stopwatch.StartNew();
await client.GetAsync("https://localhost:5001", HttpCompletionOption.ResponseHeadersRead);
Console.WriteLine($"Response-Time: {sw.ElapsedMilliseconds}ms");

So we added the HttpCompletionOption.ResponseHeadersRead flag to the GetAsync call. Now the response time is much lower:

Response-Time: 78ms

So the await only waits for the headers to be received. The body is still being received in the background. If you want to wait for the body to be received, you can use the ReadAsStringAsync method:

using var response = await client.GetAsync("https://localhost:5001", HttpCompletionOption.ResponseHeadersRead);
var body = await response.Content.ReadAsStringAsync();

Now very important is that we are using using var response - the object is maybe still holding on to resources, so we should dispose it as soon as possible. If you omit the HttpCompletionOption.ResponseHeadersRead flag, the response object will be disposed when the await is done, so you don't have to worry about it.

All in all, there is a performance and memory benefit when using HttpCompletionOption.ResponseHeadersRead, and you can still read the body if you want to. But you have to be careful with the response object, as it might still hold on to resources and your code becomes more complex. So this shouldn't be your default choice, but only if you really need it.

How to test HttpClient inside API Tests

I am a great fan of API tests. I do like to utilize the WebApplicationFactory for this. I even wrote some articles about this in the past "Introduction to WebApplicationFactory". But what if we need to "mock" HttpClient calls inside our tests?

The state machine in C# with async/await

You often here that the async/await keywords leads to a state machine. But what does that mean? Let's discuss this with a simple example.

Missing Stack trace when eliding the await keyword

You may have heard that when you elide the await keyword in a method that returns a Task or Task<T>, you lose the stack trace. Buy why does that happen? Let's find out!

An error has occurred. This application may no longer respond until reloaded. Reload x