Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API Proposal]: Event when a HttpResponseMessage is complete #86502

Open
JamesNK opened this issue May 19, 2023 · 7 comments
Open

[API Proposal]: Event when a HttpResponseMessage is complete #86502

JamesNK opened this issue May 19, 2023 · 7 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Net.Http
Milestone

Comments

@JamesNK
Copy link
Member

JamesNK commented May 19, 2023

Background and motivation

HttpClient and its middleware friends are based around passing the request message down through a nested HttpMessageHandler.SendAsync pipeline and returning the response message back.

The response being returned from SendAsync has historically been used to indicate that a request is complete. However, it only indicates the response header headers have been returned (apart from HttpClient and its option to read the response to completion before returning). This means that code that cares about the request being finished, such as counters, activities, and diagnostic source events, are reporting a HTTP request is finished even though the response body is still being read.

This becomes obvious in longer-running HTTP requests, such as gRPC streaming, where the response headers are returned almost immediately. Still, the response body periodically returns messages written by the server.

There should be an event that libraries and apps can subscribe to that indicates when the request is complete.

Potential users:

API Proposal

An event on HttpResponseMessage that reports when the overall request is considered complete.

namespace System.Net.Http;

public class HttpResponseMessage
{
    // Option 1: Cancellation token
    public CancellationToken Completed { get; }

    // Option 2: Register callback
    public void OnCompleted(Action<HttpResponseMessage> callback);
}

API options:

  • CancellationToken might not be the right thing. I can't think of a scenario where someone would use it with an async method call that takes a token. I suggest it because it is similar to ASP.NET Core's HttpContext.RequestAborted token.
  • HttpResponseMessage.OnCompleted(Action callback) may be preferable. It's like ASP.NET Core's HttpResponse.OnCompleted.

When these APIs are used, the registered callback is invoked if the response is complete.

The interesting discussion point here is: when is the overall request considered complete?

I think a request should be considered complete when both of these events are true (or the HttpResponseMessage is explicitly disposed):

  1. The HttpResponseMessage.Content has been read to the end (or no content was returned at all),
  2. And the HTTP request middleware pipeline is finished. That means the response has been returned from HttpClient.SendAsync or HttpMessageInvoker.SendAsync.

Edit: Potential problem with the idea above. The HttpResponseMessage returned for a request doesn't have to exit pipeline. A handler could decide to create a new one or retry the HTTP request which returns a new response. I added explicitly calling HttpResponseMessage.Dispose as another way to indicate the response is complete, but that doesn't seem like a guaranteed fix because someone might not dispose the original HttpResponseMessage.

API Usage

public void MyDelegatingHandler : DelegatingHandler
{
    public long CompletedRequests { get; private set; }

    public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
    {
        var response = await SendAsync(request);
        response.Completed.Register(() => CompletedRequests++);
        return response;
    }
}

Alternative Designs

No response

Risks

No response

@JamesNK JamesNK added api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Net.Http labels May 19, 2023
@ghost
Copy link

ghost commented May 19, 2023

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and motivation

HttpClient and its middleware friends are based around passing the request message down through a nested HttpMessageHandler.SendAsync pipeline and returning the response message back.

The response being returned from SendAsync has historically been used to indicate that a request is complete. However, it only indicates the response header headers have been returned (apart from HttpClient and its option to read the response to completion before returning). This means that code that cares about the request being finished, such as counters, activities, and diagnostic source events, are reporting a HTTP request is finished even though the response body is still being read.

This becomes obvious in longer-running HTTP requests, such as gRPC streaming, where the response headers are returned almost immediately. Still, the response body periodically returns messages written by the server.

There should be an event that libraries and apps can subscribe to that indicates when the request is complete.

Potential users:

API Proposal

An event on HttpResponseMessage that reports when the overall request is considered complete.

namespace System.Net.Http;

public class HttpResponseMessage
{
    // Option 1: Cancellation token
    public CancellationToken Completed { get; }

    // Option 2: Register callback
    public OnCompleted(Action<HttpResponseMessage> callback);
}

If the response is already complete when these APIs are used, then the registered callback is immediately invoked.

The interesting discussion point here is: when is the overall request considered complete?

I think a request should be considered complete when both of these events are true:

  1. The HttpResponseMessage.Content has been read to the end (or no content was returned at all),
  2. And the HTTP request middleware pipeline is finished. That means the response has been returned from HttpClient.SendAsync or HttpMessageInvoker.SendAsync.

API Usage

public void MyDelegatingHandler : DelegatingHandler
{
    public long CompletedRequests { get; private set; }

    public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
    {
        var response = await SendAsync(request);
        response.Completed.Register(() => CompletedRequests++);
        return response;
    }
}

Alternative Designs

No response

Risks

No response

Author: JamesNK
Assignees: -
Labels:

api-suggestion, area-System.Net.Http

Milestone: -

@ghost ghost added the untriaged New issue has not been triaged by the area owner label May 19, 2023
@JamesNK
Copy link
Member Author

JamesNK commented May 19, 2023

FYI @antonfirsov @noahfalk

@CarnaViire
Copy link
Member

Just on a side note, there's another alternative approach used in System.Net.Quic -- a Task property: QuicStream.ReadsClosed and QuicStream.WritesClosed, and you can register continuations instead of subscribing to an event

@gfoidl
Copy link
Member

gfoidl commented May 19, 2023

Task has the downside that it's an allocatoin (IIRC 72 bytes). Should be ValueTask backed by IValueTaskSource (to avoid these allocations).

@antonfirsov
Copy link
Member

antonfirsov commented May 20, 2023

  1. The HttpResponseMessage.Content has been read to the end (or no content was returned at all),

My main concern with the proposal is with the code that would be responsible to watch this citeria. There are two options implementation-wise: to add a layer of indirection that wraps the response's HttpContent and Stream in a way similar to the current workaround or to code the EOF detection into all response stream implementations of individual handlers.

There are several concerns with this:

  • The implementing handler is responsible for the creation of the response HttpContent and Stream, meaning that this feature won't work OOB for arbitrary external handlers (eg. WinHttpHandler), only for ones which explicitly implement support. Are we OK with this?
  • Unless we want other teams to be involved, for mobile and wasm we would most likely implement this by adding an indirection. Are we OK paying the runtime costs on those platforms?
  • For SocketsHttpHandler we can implement this by hooking EOF detection into all response stream implementations. This is the primary reason why I'm calling option (3) expensive/complex in Add request metrics to System.Net.Http #85447 (comment). Do we think the feature is worth the added complexity?

@antonfirsov
Copy link
Member

antonfirsov commented May 20, 2023

Another, even more worrying concern:

or no content was returned at all

We don't have an abstraction for "no content". There is HttpContent being returned containing an empty stream. I don't see a way to detect this case from outside of the implementing handler. We could in theory require empty streams to return Stream.Length == 0, but that would throw for non-empty streams that don't have a length.

@MihaZupan MihaZupan added this to the 8.0.0 milestone May 22, 2023
@MihaZupan MihaZupan removed the untriaged New issue has not been triaged by the area owner label May 22, 2023
@karelz
Copy link
Member

karelz commented Jun 13, 2023

Triage: Not needed for Metrics anymore. Might be useful in general - moving to Future.

@karelz karelz modified the milestones: 8.0.0, Future Jun 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Net.Http
Projects
None yet
Development

No branches or pull requests

6 participants