-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
#2039 Buffer request body and copy the body to downstreams during multiplexing #2050
#2039 Buffer request body and copy the body to downstreams during multiplexing #2050
Conversation
|
||
protected virtual async Task<Stream> CopyBufferToTargetRequestAsync(HttpContext source) | ||
{ | ||
source.Request.EnableBuffering(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This EnableBuffering()
extension method comes from the AspNet framework:
This seems to be the recommended way to enable native capabilities:
Feel free to discuss this solution here, I think it's pretty elegant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're aware of this fantastic ASP.NET feature 😄. However, as a team, we've opted not to use HttpClient
and instead utilize the new HttpMessageInvoker
pooling mechanism to enhance our heavy streaming capabilities.
For multiplexing, we should employ local buffering and replicate the body multiple times into downstreams.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🆗 After a quick reading docs I realized we need to consider more flexible version of the EnableBuffering
extension 👉 EnableBuffering(HttpRequest, Int32, Int64)
with params (see link) instead of most common EnableBuffering(HttpRequest)
without params.
My proposal is intro new options to apply them as args for this version. That will give extra flexibility for end users to have settings for buffering in Aggregation scenarios only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@raman-m thanks for the input, it is enabled on ASP.NET Request, not on HttpClient.
Where would you put the settings for this buffering? I thought it would be preferable to let it by default at first because this would not be enabled for requests that are not multiplexed, thus being a bit confusing.
Also buffering was not part of the story which is why I did the strict minimum buffering. Considering the remark on the previous PR on overstepping the boundaries of the bug, I think it would be best to postpone this (interesting) addition/customization to a more global buffering story.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also buffering was not part of the story which is why I did the strict minimum buffering.
Okay, it makes sense not to deliver this feature at this time.
Let's wait for @ggnaegi ...
Paul, thanks for re-creation the PR!
Benchmarks testing project is here: Ocelot.Benchmarks |
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My suggestions below 👇
I'll submit a couple of commits with tests improvements.
|
||
protected virtual async Task<Stream> CopyBufferToTargetRequestAsync(HttpContext source) | ||
{ | ||
source.Request.EnableBuffering(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🆗 After a quick reading docs I realized we need to consider more flexible version of the EnableBuffering
extension 👉 EnableBuffering(HttpRequest, Int32, Int64)
with params (see link) instead of most common EnableBuffering(HttpRequest)
without params.
My proposal is intro new options to apply them as args for this version. That will give extra flexibility for end users to have settings for buffering in Aggregation scenarios only.
Yes, I tested locally first. Also the acceptance test can show quite efficiently the resolution (disabling the copy fails the test with the correct exception). |
5ac5eeb
to
d3ff90b
Compare
cdb9807
to
5ed97d0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's necessary to introduce additional buffering options in the middleware.
Please consider my suggestions listed below. 👇
protected virtual async Task<Stream> CloneRequestBodyAsync(HttpRequest request, CancellationToken aborted) | ||
{ | ||
request.EnableBuffering(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Next goal
Let's add MultiplexingMiddleware
options.
Given options for buffering from FileAggregateRoute
class:
public struct BufferingOptions
{
public int BufferThreshold;
public long BufferLimit;
}
Then, let's apply the options:
protected virtual async Task<Stream> CloneRequestBodyAsync(HttpRequest request, CancellationToken aborted) | |
{ | |
request.EnableBuffering(); | |
protected virtual async Task<Stream> CloneRequestBodyAsync(HttpRequest request, CancellationToken aborted, BufferingOptions options) | |
{ | |
request.EnableBuffering(options.BufferThreshold, options.BufferLimit); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
4665d40
to
31e72b6
Compare
da1065e
to
11d7deb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGFM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just 1-2 suggestions but fine otherwise, thanks
} | ||
|
||
return targetBuffer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@PaulARoy
Regarding the management of the Stream
object's lifetime, it appears we are relying solely on Garbage Collector to handle it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@PaulARoy Regarding the management of the
Stream
object's lifetime, it appears we are relying solely on Garbage Collector to handle it.
Yes, I assume it's disposed once the request itself is disposed by aspnet
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @raman-m, it might be disposed... But since it's a clone of the original HttpContext, I'm not so sure anymore. Maybe we should try to dispose the cloned http contexts afterwards?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The principle of the IDisposable pattern is straightforward: if a class generates IDisposable
objects (and since MemoryStream
→ Stream
is IDisposable
), then the class should either inherit the IDisposable
interface or manage the object's lifetime manually.
- Calling
Dispose
manually is possible, but it's not the best idea. - Inheriting the
IDisposable
interface is unusual for middleware, but it is a viable solution. - Utilizing the
RegisterForDispose(IDisposable)
method (see link) is another option, although it still necessitates implementingIDisposable
.
I find the third option to be an elegant design choice. While this issue is not critical because of GC management and the PR could be merged as is, it would be ideal to address it today or simply proceed with PR merge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue has been resolved in the commit 897c348 ✔️
I was wrong! The RegisterForDispose(IDisposable)
method doesn't require implementation of IDisposable
interface in the middleware class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ready for delivery❗
…s during multiplexing (ThreeMammals#2050) * feat: buffer the request body during multiplexing multiple routes * style: rename clone request body method to be more explicit * Code review by @raman-m * feat: refactor clone request method, add acceptance test for form-based requests * fix: add content-length log, refactor tests from @raman-m commit * Update requestaggregation.rst * style: reverse return condition * Register `Stream` objects for disposing by downstream `HttpResponse` --------- Co-authored-by: Paul Roy <[email protected]> Co-authored-by: Raman Maksimchuk <[email protected]>
Closes #2039
Buffers the body of the request when multiplexing multiple routes and copy the buffered body to the downstreams.
I took consideration of all your feedbacks from the previous PR, don't hesitate to tell me if something's off, either in the code or in the process.
I looked at the benchmark tests but I'm not sure on how to integrate this correctly.
Proposed Changes
MemoryStream
CreateThreadContext
name and parameter changesCloneRequestBodyAsync
(called n times when n > 1, not called for 1)