Skip to content

Commit ea344bf

Browse files
authored
Don't allocate the FormFeature eagerly (#6511)
- Expose FormOptions on DefaultHttpContext - Use those options on DefaultHttpContext when the FormFeature is initialized
1 parent b6bdffe commit ea344bf

File tree

8 files changed

+31
-12
lines changed

8 files changed

+31
-12
lines changed

src/Http/Http/src/DefaultHttpContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ public void Uninitialize()
6262
_websockets?.Uninitialize();
6363
}
6464

65+
public FormOptions FormOptions { get; set; }
66+
6567
private IItemsFeature ItemsFeature =>
6668
_features.Fetch(ref _features.Cache.Items, _newItemsFeature);
6769

src/Http/Http/src/Features/FormFeature.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ namespace Microsoft.AspNetCore.Http.Features
1515
{
1616
public class FormFeature : IFormFeature
1717
{
18-
private static readonly FormOptions DefaultFormOptions = new FormOptions();
19-
2018
private readonly HttpRequest _request;
2119
private readonly FormOptions _options;
2220
private Task<IFormCollection> _parsedFormTask;
@@ -32,7 +30,7 @@ public FormFeature(IFormCollection form)
3230
Form = form;
3331
}
3432
public FormFeature(HttpRequest request)
35-
: this(request, DefaultFormOptions)
33+
: this(request, FormOptions.Default)
3634
{
3735
}
3836

src/Http/Http/src/Features/FormOptions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System.IO;
@@ -8,6 +8,8 @@ namespace Microsoft.AspNetCore.Http.Features
88
{
99
public class FormOptions
1010
{
11+
internal static readonly FormOptions Default = new FormOptions();
12+
1113
public const int DefaultMemoryBufferThreshold = 1024 * 64;
1214
public const int DefaultBufferBodyLengthLimit = 1024 * 1024 * 128;
1315
public const int DefaultMultipartBoundaryLengthLimit = 128;

src/Http/Http/src/HttpContextFactory.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,12 @@ public HttpContext Create(IFeatureCollection featureCollection)
4242
_httpContextAccessor.HttpContext = httpContext;
4343
}
4444

45-
var formFeature = new FormFeature(httpContext.Request, _formOptions);
46-
featureCollection.Set<IFormFeature>(formFeature);
45+
httpContext.FormOptions = _formOptions;
4746

4847
return httpContext;
4948
}
5049

51-
private static HttpContext CreateHttpContext(IFeatureCollection featureCollection)
50+
private static DefaultHttpContext CreateHttpContext(IFeatureCollection featureCollection)
5251
{
5352
if (featureCollection is IHttpContextContainer container)
5453
{

src/Http/Http/src/IHttpContextContainer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ namespace Microsoft.AspNetCore.Http
66
{
77
public interface IHttpContextContainer
88
{
9-
HttpContext HttpContext { get; }
9+
DefaultHttpContext HttpContext { get; }
1010
}
1111
}

src/Http/Http/src/Internal/DefaultHttpRequest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public sealed class DefaultHttpRequest : HttpRequest
1717
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
1818
private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;
1919
private readonly static Func<IFeatureCollection, IQueryFeature> _newQueryFeature = f => new QueryFeature(f);
20-
private readonly static Func<HttpRequest, IFormFeature> _newFormFeature = r => new FormFeature(r);
20+
private readonly static Func<DefaultHttpRequest, IFormFeature> _newFormFeature = r => new FormFeature(r, r._context.FormOptions ?? FormOptions.Default);
2121
private readonly static Func<IFeatureCollection, IRequestCookiesFeature> _newRequestCookiesFeature = f => new RequestCookiesFeature(f);
2222
private readonly static Func<IFeatureCollection, IRouteValuesFeature> _newRouteValuesFeature = f => new RouteValuesFeature();
2323
private readonly static Func<HttpContext, IRequestBodyPipeFeature> _newRequestBodyPipeFeature = context => new RequestBodyPipeFeature(context);

src/Http/Http/test/Features/FormFeatureTests.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,24 @@ public async Task ReadFormAsync_0ContentLength_ReturnsEmptyForm()
2929
Assert.Same(FormCollection.Empty, formCollection);
3030
}
3131

32+
[Fact]
33+
public async Task FormFeatureReadsOptionsFromDefaultHttpContext()
34+
{
35+
var context = new DefaultHttpContext();
36+
context.Request.ContentType = "application/x-www-form-urlencoded; charset=utf-8";
37+
context.FormOptions = new FormOptions
38+
{
39+
ValueCountLimit = 1
40+
};
41+
42+
var formContent = Encoding.UTF8.GetBytes("foo=bar&baz=2");
43+
context.Request.Body = new NonSeekableReadStream(formContent);
44+
45+
var exception = await Assert.ThrowsAsync<InvalidDataException>(() => context.Request.ReadFormAsync());
46+
47+
Assert.Equal("Form value count limit 1 exceeded.", exception.Message);
48+
}
49+
3250
[Theory]
3351
[InlineData(true)]
3452
[InlineData(false)]
@@ -391,7 +409,7 @@ public async Task ReadFormAsync_ValueCountLimitExceeded_Throw(bool bufferRequest
391409
IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest, ValueCountLimit = 2 });
392410
context.Features.Set<IFormFeature>(formFeature);
393411

394-
var exception = await Assert.ThrowsAsync<InvalidDataException> (() => context.Request.ReadFormAsync());
412+
var exception = await Assert.ThrowsAsync<InvalidDataException>(() => context.Request.ReadFormAsync());
395413
Assert.Equal("Form value count limit 2 exceeded.", exception.Message);
396414
}
397415

@@ -416,7 +434,7 @@ public async Task ReadFormAsync_ValueCountLimitExceededWithFiles_Throw(bool buff
416434
IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest, ValueCountLimit = 2 });
417435
context.Features.Set<IFormFeature>(formFeature);
418436

419-
var exception = await Assert.ThrowsAsync<InvalidDataException> (() => context.Request.ReadFormAsync());
437+
var exception = await Assert.ThrowsAsync<InvalidDataException>(() => context.Request.ReadFormAsync());
420438
Assert.Equal("Form value count limit 2 exceeded.", exception.Message);
421439
}
422440

src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ public CancellationToken RequestAborted
277277

278278
protected HttpResponseHeaders HttpResponseHeaders { get; } = new HttpResponseHeaders();
279279

280-
HttpContext IHttpContextContainer.HttpContext
280+
DefaultHttpContext IHttpContextContainer.HttpContext
281281
{
282282
get
283283
{

0 commit comments

Comments
 (0)