-
Notifications
You must be signed in to change notification settings - Fork 10.4k
[Addresses #9466] API to configure TempDirectory used by HttpBuffering #11569
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
Conversation
|
||
namespace Microsoft.AspNetCore.Http | ||
{ | ||
public interface IFileBufferingStreamFactory |
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 would likely go in the Http.Abstractions package.
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 was thinking on putting IBufferedWriteStream
on WebUtilities but in that case Http.Abstractions would have a dependency on it, is that OK?
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.
Hmm, tradeoffs. Let's not change the dependencies right now. That means IFileBufferingStreamFactory stays in Http and IBufferedWriteStream in WebUtilities.
{ | ||
public interface IFileBufferingStreamFactory | ||
{ | ||
FileBufferingReadStream CreateReadStream(Stream inner, int bufferThreshold, long? bufferLimit = null); |
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.
Why can't this return Stream?
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 consumers are actually expecting a Stream, so it can return it. The only reason was because the consumers of the writer need the FileBufferingWriteStream
explicitly so I kept this one same way. Moving this to return Stream
sounds better.
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 only reason was because the consumers of the writer need the FileBufferingWriteStream explicitly
I expect we could remove that requirement. DrainBufferAsync looks like a fancy CopyToAsync.
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 expect we could remove that requirement. DrainBufferAsync looks like a fancy CopyToAsync.
By default CopyTo
throws NotSupportedException
when a stream cannot be read, like in this case. Could it be confusing if we allow to copy this stream but reading it is not allowed? I don't know, however, if this behavior is usually honored.
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.
You're right, that came up in the design so we added DrainBufferAsync. I'd rather not have the concrete type exposed here. Maybe we can abstract it.
public interface IBufferedWriteStream
{
Stream Buffer { get; }
Task DrainBufferAsync(Stream destination);
}
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.
If I got it right, FileBufferingWriteStream
would be implementing IBufferedWriteStream
and returned from the factory instead of the concrete type.
Regarding of Buffer
property, and also that we need the actual Stream
in the consumers, what is the purpose of the property and how are we getting the Stream
?
I have the feeling that property could be the answer since IBufferedWriteStream
doesn't know it's a Stream
(cannot extend the class), so TL;DR, could be the implementation this one and the way of getting it instead of casting on the consumers?
Stream Buffer => this;
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.
Stream Buffer => this;
Exactly
@@ -21,15 +22,17 @@ public static HttpRequest EnableRewind(this HttpRequest request, int bufferThres | |||
var body = request.Body; | |||
if (!body.CanSeek) | |||
{ | |||
var fileStream = new FileBufferingReadStream(body, bufferThreshold, bufferLimit, AspNetCoreTempDirectory.TempDirectoryFactory); | |||
var httpBufferingOptions = request.HttpContext.RequestServices.GetRequiredService<IOptions<HttpBufferingOptions>>(); | |||
var factory = new HttpFileBufferingStreamFactory(httpBufferingOptions); |
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.
Why isn't the factory being resolved from DI? Yes something would need to register it, probably Hosting with the rest of the default services.
https://github.com/aspnet/AspNetCore/blob/70b478136a702d2b6061ac9b159029189ad230d0/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs#L84
https://github.com/aspnet/AspNetCore/blob/70b478136a702d2b6061ac9b159029189ad230d0/src/Hosting/Hosting/src/WebHostBuilder.cs#L284
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.
Great, I thought about register and resolve the factory but didn't know where to register it. Hosting sounds good, thanks :)
I can register together the IPostConfiguration for the options and the factory, probably with an Extension method, like AddHttpBuffering()?
The scope I believe it should be a singleton, out of curiosity, why the DefaultHttpContextFactory
in the Generic is a Singleton and in the other one is Transient? Should I follow same pattern here?
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 can register together the IPostConfiguration for the options and the factory, probably with an Extension method, like AddHttpBuffering()?
Sounds good.
The scope I believe it should be a singleton, out of curiosity, why the DefaultHttpContextFactory in the Generic is a Singleton and in the other one is Transient? Should I follow same pattern here?
I don't recall, but follow what we do in Generic, it's more recent.
1f3d239
to
6152fc3
Compare
6152fc3
to
b4d76d8
Compare
5468745
to
9831e36
Compare
|
||
namespace Microsoft.AspNetCore.WebUtilities | ||
{ | ||
public interface IBufferedWriteStream : IAsyncDisposable, IDisposable |
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.
Marking this as IDisposable
helps in situations like this one:
And it also would simplify this:
What do you think?
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.
Looks good.
I haven't been following this closely but I'm not a fan of adding more services to hosting to enable this to work. I strongly recommend looking for an alternative design. We've been slowly moving away from adding these "default services" to hosting (see #5944). |
Would it make sense to add the required services with
For these ones, It's also used in Http\src\Internal\BufferingHelper.cs, called from from FormFeature: but exposed publicly via So, in these cases, do you know of another place more specific than hosting that could work? Having a fallback in case is not registered (not sure if this would be clean)? |
No, features exposed directly from HttpContext need to work with pre-registered services. Adding it by default in Hosting should be adequate. |
@davidfowl I'll follow-up with you directly. |
Offline discussion:
@glennc to check if IHostEnvironment vs IWebHostEnvironment makes the most sense. |
Thanks @Tratcher, I like it. This way it's not just the temp dir for HttpBuffering but a way for setting/getting the temp dir for everyone. The consumers still need to create the Buffering Read and Write Stream, in this case if we use the host APIs to configure the TempDirectory, we check if the Directory exists on configuration and everything there, could we resolve the HostEnvironment and do this for the accessor?
Or is still better and safer to pass a Func like this in case there's something in the middle breaking it? Regarding the default path, are we still keeping the default behavior in the Streams for getting the accessor or deprecating/marking obsolete (this is the breaking-change itself)? |
Yeah, reading and validating TempFileDirectory should be lazy because it may not be created until after startup, and could even be deleted later.
Yes, we'll keep any back-compat that's reasonably possible, but may decided to Obsolete(Warning) some of them. |
Makes sense. We could keep the factory then but just in WebUtilities this way: public class FileBufferingStreamFactory : IFileBufferingStreamFactory
{
public FileBufferingStreamFactory(string tempPath)
{
_tempPath = tempPath;
_getTempDirectory = () => _tempDirectory;
}
private string _tempDirectory
{
get
{
if (!Directory.Exists(_tempPath))
{
throw new DirectoryNotFoundException(_tempPath);
}
return _tempPath;
}
}
} And create it when needed resolving
Glenn, could you please take a look to see in which one it suits better? Specially as the former would make it cross-repo. |
@Tratcher I've started with this on a new branch (cjaliaga@750cc03), adding it on Is it better to create a new PR with the new branch linking this one or to reset this one to master and do it here to keep the PR active? |
This got backburnered during the run up to 3.0. We'll try to look at this for 5.0 now! |
Let's close this in favor of #12555 |
Addresses #9466
This is still not ready but before implementing it in all the callers I'd like to check if the logic looks right.
Currently the
HttpBufferingOptions
is the one configured in the DI, andIOptions<HttpBufferingOptions>
resolved in the callers, suchEnableRewind
or the MVC formatters.For the creation of the Streams, a new Factory is added
(HttpFileBufferingFactory
) inMicrosoft.AspNetCore.Http
which gets in the ctor the options. This factory could be resolved instead of being manually created, in that case, what should be the right place to be registered into the Service Collection?Another option would be making the factory more generic, just getting the
TempDirectoryPath
as string in the ctor instead of being tied to theHttpBufferingOptions,
so it could be added to theWebUtilities project
.@Tratcher, @anurse could you please take a look and let me know what you think?