Skip to content

Conversation

@Rick-Anderson
Copy link
Contributor

@Rick-Anderson Rick-Anderson commented Apr 22, 2025

fixes #35288

@martincostello please review. The other questions are answered in the doc.


Internal previews

📄 File 🔗 Preview link
aspnetcore/fundamentals/middleware/request-response.md aspnetcore/fundamentals/middleware/request-response

When writing directly to `HttpResponse.BodyWriter`, call `PipeWriter.FlushAsync()` manually to ensure the data is flushed to the underlying response stream. Here's why:

* `HttpResponse.BodyWriter` is a `PipeWriter` that buffers data until a flush operation is triggered.
* Calling `FlushAsync` writes the buffered data to the underlying Stream (`HttpResponse.Body`).
Copy link

@verdie-g verdie-g Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we specify how frequently the FlushAsync should be called? Once? Once per Advance? Every X bytes advanced?

Copy link
Contributor Author

@Rick-Anderson Rick-Anderson Apr 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we specify how frequently the FlushAsync should be called? Once? Once per Advance? Every X bytes advanced?

My guess is it's hard to give a simple answer.

@gfoidl @martincostello ???

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My guess is it's hard to give a simple answer.

True. A generic rule when to call Flush is hard to impossible to give.

Very general:

  • call Flush to make the data available to the PipeReader (here: the response body), so that the response can be transmitted (chunked) over the wire to the client
  • call Flush when someting is written that the PipeReader should have access to (before completing the pipe)

But I don't know how to defer a rule out of the points.
Honestly, I'd just omit something like this, as at the end of the article there's a link to System.IO.Pipelines in .NET which has further infos.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we say something like "It's up to the developer to decide when to call FlushAsync, balancing factors such as buffer size, network write overhead, and whether the data should be sent in discrete chunks."

gfoidl

This comment was marked as resolved.

@verdie-g
Copy link

@Rick-Anderson could you also cover what I mentioned in #35288 (comment)

* `HttpResponse.WriteAsync`

Streams aren't being removed from the framework. Streams continue to be used throughout .NET, and many stream types don't have pipe equivalents, such as `FileStreams` and `ResponseCompression`.
Streams aren't being removed from the framework. Streams continue to be used throughout .NET, and many stream types don't have pipe equivalents, such as `FileStreams` and `ResponseCompression`, and straightforward adding compression to the stream.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that it is not possible to write a custom compression middleware with PipeWriter. Is that correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Rick-Anderson could you also cover what I mentioned in #35288 (comment)

That's what I tried to do here (line 28 change).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that it is not possible to write a custom compression middleware with PipeWriter. Is that correct?

@gfoidl @martincostello ???

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not possible to write a custom compression middleware with PipeWriter

It's possible, but very awkward compared to Streams. W/ Streams it's quite easy, as the decorator pattern can be used to wrap a stream, which wraps a streams, etc.

W/ PipeWriter more custom code would be needed. E.g. an intermediate Pipeline that reads what the user's PipeWriter writes (and flushes), then perfom the compression and write (and flush) it the response body's PipeReader.
Also for compression .NET provides no easy to use infrastructure for this use case.

So as said it's possible, but w/ quite an effort. So I see no reason why one should use PipeWriter for this, when it's quite easy to accomplish with Streams.

Note: The Stream in the response is quite a simple adapter for the PipeWriter (at least in Kestrel), so there shouldn't be any perf-difference, especially when the data is sent over the network. So use either Body or BodyWriter depending on the use case and on how it can be done more easily.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Stream in the response is quite a simple adapter for the PipeWriter

I think this is the piece that I was missing. The pipe and stream are different APIs for the same channel of data? For example, if my endpoint middleware writes to pipe and my compression middleware wraps the stream, my data will be compressed?

Could we explicit this part in this document 🙏

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pipe and stream are different APIs for the same channel of data?

Yep.
Underlying is the network socket, from which data is written / read. In oder to make these operations easier to use and abstracted away (because instead of network socket it could also be named pipes, unix domain sockets, etc. -- effort was done in ASP.NET Core with so called "project bedrock") there's pipes and streams, which allow "unified" access higher up in the stack.

if my endpoint middleware writes to pipe and my compression middleware wraps the stream, my data will be compressed?

W/o knowing your code I'm not able to say yes or no.
Interleaving PipeWriter and BodyStream is dangerous, as none of these is thread-safe. They're not intended to be used concurrent or mixed in any way.
So it really depends when you write / flush the pipe and what you'll do with the stream.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's say my endpoint handler is just writing a protobuf object to the pipe

MyProtoObject.WriteTo(HttpContext.Response.BodyWriter)

and I use the standard response compression middleware

app.UseResponseCompression();

I'm expecting the compression middleware to decorate the stream and not touch the pipe. I'm wondering if my response will be compressed.

@dotnet dotnet deleted a comment from martincostello Apr 23, 2025
Copy link
Member

@gfoidl gfoidl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides the little suggestion LGTM.

@Rick-Anderson Rick-Anderson enabled auto-merge (squash) April 24, 2025 16:44
@Rick-Anderson Rick-Anderson merged commit f568987 into main Apr 24, 2025
3 checks passed
@Rick-Anderson Rick-Anderson deleted the Rick-Anderson-patch-2 branch April 24, 2025 16:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Guidelines about HttpResponse.BodyWriter

5 participants