Skip to content

[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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public GenericWebHostBuilder(IHostBuilder builder)
services.TryAddSingleton<IHttpContextFactory, DefaultHttpContextFactory>();
services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>();
services.TryAddSingleton<IApplicationBuilderFactory, ApplicationBuilderFactory>();
services.AddHttpBuffering();

// IMPORTANT: This needs to run *before* direct calls on the builder (like UseStartup)
_hostingStartupWebHostBuilder?.ConfigureServices(webhostContext, services);
Expand Down
1 change: 1 addition & 0 deletions src/Hosting/Hosting/src/WebHostBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ private IServiceCollection BuildCommonServices(out AggregateException hostingSta
services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
services.AddTransient<IHttpContextFactory, DefaultHttpContextFactory>();
services.AddScoped<IMiddlewareFactory, MiddlewareFactory>();
services.AddHttpBuffering();
services.AddOptions();
services.AddLogging();

Expand Down
28 changes: 28 additions & 0 deletions src/Http/Http/ref/Microsoft.AspNetCore.Http.netcoreapp3.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ public void Dispose() { }
void System.Collections.IEnumerator.Reset() { }
}
}
public partial class HttpBufferingOptions
{
public HttpBufferingOptions() { }
public string TempFileDirectory { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
}
public partial class HttpContextAccessor : Microsoft.AspNetCore.Http.IHttpContextAccessor
{
public HttpContextAccessor() { }
Expand All @@ -157,6 +162,13 @@ public HttpContextFactory(Microsoft.Extensions.Options.IOptions<Microsoft.AspNet
public Microsoft.AspNetCore.Http.HttpContext Create(Microsoft.AspNetCore.Http.Features.IFeatureCollection featureCollection) { throw null; }
public void Dispose(Microsoft.AspNetCore.Http.HttpContext httpContext) { }
}
public partial class HttpFileBufferingStreamFactory : Microsoft.AspNetCore.Http.IFileBufferingStreamFactory
{
public HttpFileBufferingStreamFactory(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Http.HttpBufferingOptions> bufferingOptions) { }
public System.IO.Stream CreateReadStream(System.IO.Stream inner, int bufferThreshold, long? bufferLimit = default(long?)) { throw null; }
public Microsoft.AspNetCore.WebUtilities.IBufferedWriteStream CreateWriteStream() { throw null; }
public Microsoft.AspNetCore.WebUtilities.IBufferedWriteStream CreateWriteStream(int memoryThreshold, long? bufferLimit = default(long?)) { throw null; }
}
public static partial class HttpRequestRewindExtensions
{
public static void EnableBuffering(this Microsoft.AspNetCore.Http.HttpRequest request) { }
Expand All @@ -168,12 +180,23 @@ public partial interface IDefaultHttpContextContainer
{
Microsoft.AspNetCore.Http.DefaultHttpContext HttpContext { get; }
}
public partial interface IFileBufferingStreamFactory
{
System.IO.Stream CreateReadStream(System.IO.Stream inner, int bufferThreshold, long? bufferLimit = default(long?));
Microsoft.AspNetCore.WebUtilities.IBufferedWriteStream CreateWriteStream();
Microsoft.AspNetCore.WebUtilities.IBufferedWriteStream CreateWriteStream(int memoryThreshold, long? bufferLimit = default(long?));
}
public partial class MiddlewareFactory : Microsoft.AspNetCore.Http.IMiddlewareFactory
{
public MiddlewareFactory(System.IServiceProvider serviceProvider) { }
public Microsoft.AspNetCore.Http.IMiddleware Create(System.Type middlewareType) { throw null; }
public void Release(Microsoft.AspNetCore.Http.IMiddleware middleware) { }
}
public partial class PostConfigureHttpBufferingOptions : Microsoft.Extensions.Options.IPostConfigureOptions<Microsoft.AspNetCore.Http.HttpBufferingOptions>
{
public PostConfigureHttpBufferingOptions() { }
public void PostConfigure(string name, Microsoft.AspNetCore.Http.HttpBufferingOptions options) { }
}
public partial class QueryCollection : Microsoft.AspNetCore.Http.IQueryCollection, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, Microsoft.Extensions.Primitives.StringValues>>, System.Collections.IEnumerable
{
public static readonly Microsoft.AspNetCore.Http.QueryCollection Empty;
Expand Down Expand Up @@ -353,6 +376,11 @@ public HttpAuthenticationFeature() { }
}
namespace Microsoft.Extensions.DependencyInjection
{
public static partial class HttpBufferingServiceCollectionExtensions
{
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddHttpBuffering(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; }
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddHttpBuffering(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action<Microsoft.AspNetCore.Http.HttpBufferingOptions> configureOptions) { throw null; }
}
public static partial class HttpServiceCollectionExtensions
{
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddHttpContextAccessor(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; }
Expand Down
4 changes: 4 additions & 0 deletions src/Http/Http/src/Features/FormFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;

Expand Down Expand Up @@ -182,8 +184,10 @@ private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancell
var fileSection = new FileMultipartSection(section, contentDisposition);

// Enable buffering for the file if not already done for the full body
var fileBufferingStreamFactory = _request.HttpContext.RequestServices.GetRequiredService<IFileBufferingStreamFactory>();
section.EnableRewind(
_request.HttpContext.Response.RegisterForDispose,
fileBufferingStreamFactory,
_options.MemoryBufferThreshold, _options.MultipartBodyLengthLimit);

// Find the end
Expand Down
10 changes: 10 additions & 0 deletions src/Http/Http/src/HttpBufferingOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.AspNetCore.Http
{
public class HttpBufferingOptions
{
public string TempFileDirectory { get; set; }
}
}
60 changes: 60 additions & 0 deletions src/Http/Http/src/HttpBufferingServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;

namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Extension methods for setting up Http Buffering services in an <see cref="IServiceCollection" />.
/// </summary>
public static class HttpBufferingServiceCollectionExtensions
{
/// <summary>
/// Adds services required to enable Http Bufering to the specified <see cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddHttpBuffering(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}

services.AddOptions();

services.TryAddSingleton<IFileBufferingStreamFactory, HttpFileBufferingStreamFactory>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<HttpBufferingOptions>, PostConfigureHttpBufferingOptions>());

return services;
}

/// <summary>
/// Adds services required to enable Http Bufering to the specified <see cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <param name="configureOptions">An <see cref="Action{HttpBufferingOptions}"/> to configure the provided <see cref="HttpBufferingOptions"/>.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddHttpBuffering(this IServiceCollection services, Action<HttpBufferingOptions> configureOptions)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}

if (configureOptions == null)
{
throw new ArgumentNullException(nameof(configureOptions));
}

services.AddHttpBuffering();
services.Configure(configureOptions);

return services;
}
}
}
56 changes: 56 additions & 0 deletions src/Http/Http/src/HttpFileBufferingStreamFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.IO;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Http
{
public class HttpFileBufferingStreamFactory : IFileBufferingStreamFactory
{
private readonly HttpBufferingOptions _bufferingOptions;
private readonly Func<string> _getTempDirectory;

public HttpFileBufferingStreamFactory(IOptions<HttpBufferingOptions> bufferingOptions)
{
if (bufferingOptions == null)
{
throw new ArgumentNullException(nameof(bufferingOptions));
}

_bufferingOptions = bufferingOptions.Value;
_getTempDirectory = () => _tempDirectory;
}

public Stream CreateReadStream(Stream inner, int bufferThreshold, long? bufferLimit = null)
{
return new FileBufferingReadStream(inner, bufferThreshold, bufferLimit, _getTempDirectory);
}

public IBufferedWriteStream CreateWriteStream()
{
return new FileBufferingWriteStream(tempFileDirectoryAccessor: _getTempDirectory);
}

public IBufferedWriteStream CreateWriteStream(int memoryThreshold, long? bufferLimit = null)
{
return new FileBufferingWriteStream(memoryThreshold, bufferLimit, _getTempDirectory);
}

private string _tempDirectory
{
get
{
// TempFileDirectory defaults to Path.GetTempPath() if it's not modified
if (!Directory.Exists(_bufferingOptions.TempFileDirectory))
{
throw new DirectoryNotFoundException(_bufferingOptions.TempFileDirectory);
}

return _bufferingOptions.TempFileDirectory;
}
}
}
}
15 changes: 15 additions & 0 deletions src/Http/Http/src/IFileBufferingStreamFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.IO;
using Microsoft.AspNetCore.WebUtilities;

namespace Microsoft.AspNetCore.Http
{
public interface IFileBufferingStreamFactory
Copy link
Member

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.

Copy link
Member Author

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?

Copy link
Member

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.

{
Stream CreateReadStream(Stream inner, int bufferThreshold, long? bufferLimit = null);
IBufferedWriteStream CreateWriteStream();
IBufferedWriteStream CreateWriteStream(int memoryThreshold, long? bufferLimit = null);
}
}
13 changes: 10 additions & 3 deletions src/Http/Http/src/Internal/BufferingHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.AspNetCore.Http
{
Expand All @@ -21,15 +22,16 @@ 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 factory = request.HttpContext.RequestServices.GetRequiredService<IFileBufferingStreamFactory>();
var fileStream = factory.CreateReadStream(body, bufferThreshold, bufferLimit);
request.Body = fileStream;
request.HttpContext.Response.RegisterForDispose(fileStream);
}
return request;
}

public static MultipartSection EnableRewind(this MultipartSection section, Action<IDisposable> registerForDispose,
int bufferThreshold = DefaultBufferThreshold, long? bufferLimit = null)
IFileBufferingStreamFactory fileBufferingStreamFactory, int bufferThreshold = DefaultBufferThreshold, long? bufferLimit = null)
{
if (section == null)
{
Expand All @@ -40,10 +42,15 @@ public static MultipartSection EnableRewind(this MultipartSection section, Actio
throw new ArgumentNullException(nameof(registerForDispose));
}

if (fileBufferingStreamFactory == null)
{
throw new ArgumentNullException(nameof(fileBufferingStreamFactory));
}

var body = section.Body;
if (!body.CanSeek)
{
var fileStream = new FileBufferingReadStream(body, bufferThreshold, bufferLimit, AspNetCoreTempDirectory.TempDirectoryFactory);
var fileStream = fileBufferingStreamFactory.CreateReadStream(body, bufferThreshold, bufferLimit);
section.Body = fileStream;
registerForDispose(fileStream);
}
Expand Down
23 changes: 23 additions & 0 deletions src/Http/Http/src/PostConfigureHttpBufferingOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.IO;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Http
{
public class PostConfigureHttpBufferingOptions : IPostConfigureOptions<HttpBufferingOptions>
{
public void PostConfigure(string name, HttpBufferingOptions options)
{
if (string.IsNullOrEmpty(options.TempFileDirectory))
{
// Look for folders in the following order.
options.TempFileDirectory =
Environment.GetEnvironmentVariable("ASPNETCORE_TEMP") ?? // ASPNETCORE_TEMP - User set temporary location.
Path.GetTempPath(); // Fall back.
}
}
}
}
Loading