Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

Commit 80813f7

Browse files
committed
Use pooled StringBuilder to reduce allocations when adding response cookies
- #561 - new `SetCookieHeaderValue.AppendToStringBuilder()` method; avoids per-call `StringBuilder` allocation - `ResponseCookies` uses `ObjectPool<StringBuilder>` that `ResponseCookiesFeature` provides - `ResponseCookies` works fine if no `ObjectPoolProvider` is available - `IHttpContextFactory` instance is a singleton instantiated from CI - make `HttpContextFactory` `ObjectPoolProvider` and `ResponseCookiesFeature`-aware - apply same pattern to sample `PooledHttpContextFactory` - pool is not currently configurable; defaults are fine for response cookies - if we need (policy) configuration, would add an `IOptions<HttpContextFactorySettings>` nit: Add some doc comments
1 parent 8efc650 commit 80813f7

File tree

11 files changed

+260
-79
lines changed

11 files changed

+260
-79
lines changed

samples/SampleApp/PooledHttpContextFactory.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,48 @@
11
// 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

4+
using System;
45
using System.Collections.Generic;
6+
using System.Text;
57
using Microsoft.AspNetCore.Http;
68
using Microsoft.AspNetCore.Http.Features;
9+
using Microsoft.AspNetCore.Http.Features.Internal;
10+
using Microsoft.Extensions.ObjectPool;
711

812
namespace SampleApp
913
{
1014
public class PooledHttpContextFactory : IHttpContextFactory
1115
{
16+
private readonly ObjectPool<StringBuilder> _builderPool;
1217
private readonly IHttpContextAccessor _httpContextAccessor;
1318
private readonly Stack<PooledHttpContext> _pool = new Stack<PooledHttpContext>();
1419

15-
public PooledHttpContextFactory(IHttpContextAccessor httpContextAccessor)
20+
public PooledHttpContextFactory(ObjectPoolProvider poolProvider)
21+
: this(poolProvider, httpContextAccessor: null)
1622
{
23+
}
24+
25+
public PooledHttpContextFactory(ObjectPoolProvider poolProvider, IHttpContextAccessor httpContextAccessor)
26+
{
27+
if (poolProvider == null)
28+
{
29+
throw new ArgumentNullException(nameof(poolProvider));
30+
}
31+
32+
_builderPool = poolProvider.CreateStringBuilderPool();
1733
_httpContextAccessor = httpContextAccessor;
1834
}
1935

2036
public HttpContext Create(IFeatureCollection featureCollection)
2137
{
38+
if (featureCollection == null)
39+
{
40+
throw new ArgumentNullException(nameof(featureCollection));
41+
}
42+
43+
var responseCookiesFeature = new ResponseCookiesFeature(featureCollection, _builderPool);
44+
featureCollection.Set<IResponseCookiesFeature>(responseCookiesFeature);
45+
2246
PooledHttpContext httpContext = null;
2347
lock (_pool)
2448
{

src/Microsoft.AspNetCore.Http.Features/IResponseCookies.cs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,39 @@
44
namespace Microsoft.AspNetCore.Http
55
{
66
/// <summary>
7-
/// A wrapper for the response Set-Cookie header
7+
/// A wrapper for the response Set-Cookie header.
88
/// </summary>
99
public interface IResponseCookies
1010
{
1111
/// <summary>
12-
/// Add a new cookie and value
12+
/// Add a new cookie and value.
1313
/// </summary>
14-
/// <param name="key"></param>
15-
/// <param name="value"></param>
14+
/// <param name="key">Name of the new cookie.</param>
15+
/// <param name="value">Value of the new cookie.</param>
1616
void Append(string key, string value);
1717

1818
/// <summary>
19-
/// Add a new cookie
19+
/// Add a new cookie.
2020
/// </summary>
21-
/// <param name="key"></param>
22-
/// <param name="value"></param>
23-
/// <param name="options"></param>
21+
/// <param name="key">Name of the new cookie.</param>
22+
/// <param name="value">Value of the new cookie.</param>
23+
/// <param name="options"><see cref="CookieOptions"/> included in the new cookie setting.</param>
2424
void Append(string key, string value, CookieOptions options);
2525

2626
/// <summary>
27-
/// Sets an expired cookie
27+
/// Sets an expired cookie.
2828
/// </summary>
29-
/// <param name="key"></param>
29+
/// <param name="key">Name of the cookie to expire.</param>
3030
void Delete(string key);
3131

3232
/// <summary>
33-
/// Sets an expired cookie
33+
/// Sets an expired cookie.
3434
/// </summary>
35-
/// <param name="key"></param>
36-
/// <param name="options"></param>
35+
/// <param name="key">Name of the cookie to expire.</param>
36+
/// <param name="options">
37+
/// <see cref="CookieOptions"/> used to discriminate the particular cookie to expire. The
38+
/// <see cref="CookieOptions.Domain"/> and <see cref="CookieOptions.Path"/> values are especially important.
39+
/// </param>
3740
void Delete(string key, CookieOptions options);
3841
}
3942
}

src/Microsoft.AspNetCore.Http.Features/IResponseCookiesFeature.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@
33

44
namespace Microsoft.AspNetCore.Http.Features
55
{
6+
/// <summary>
7+
/// A helper for creating the response Set-Cookie header.
8+
/// </summary>
69
public interface IResponseCookiesFeature
710
{
11+
/// <summary>
12+
/// Gets the wrapper for the response Set-Cookie header.
13+
/// </summary>
814
IResponseCookies Cookies { get; }
915
}
1016
}

src/Microsoft.AspNetCore.Http/Features/ResponseCookiesFeature.cs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,68 @@
11
// 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

4+
using System;
5+
using System.Text;
46
using Microsoft.AspNetCore.Http.Internal;
7+
using Microsoft.Extensions.ObjectPool;
58

69
namespace Microsoft.AspNetCore.Http.Features.Internal
710
{
11+
/// <summary>
12+
/// Default implementation of <see cref="IResponseCookiesFeature"/>.
13+
/// </summary>
814
public class ResponseCookiesFeature : IResponseCookiesFeature
915
{
16+
// Object pool will be null only in test scenarios e.g. if code news up a DefaultHttpContext.
17+
private readonly ObjectPool<StringBuilder> _builderPool;
18+
1019
private FeatureReferences<IHttpResponseFeature> _features;
1120
private IResponseCookies _cookiesCollection;
1221

22+
/// <summary>
23+
/// Initializes a new <see cref="ResponseCookiesFeature"/> instance.
24+
/// </summary>
25+
/// <param name="features">
26+
/// <see cref="IFeatureCollection"/> containing all defined features, including this
27+
/// <see cref="IResponseCookiesFeature"/> and the <see cref="IHttpResponseFeature"/>.
28+
/// </param>
1329
public ResponseCookiesFeature(IFeatureCollection features)
30+
: this(features, builderPool: null)
31+
{
32+
}
33+
34+
/// <summary>
35+
/// Initializes a new <see cref="ResponseCookiesFeature"/> instance.
36+
/// </summary>
37+
/// <param name="features">
38+
/// <see cref="IFeatureCollection"/> containing all defined features, including this
39+
/// <see cref="IResponseCookiesFeature"/> and the <see cref="IHttpResponseFeature"/>.
40+
/// </param>
41+
/// <param name="builderPool">The <see cref="ObjectPool{T}"/>, if available.</param>
42+
public ResponseCookiesFeature(IFeatureCollection features, ObjectPool<StringBuilder> builderPool)
1443
{
44+
if (features == null)
45+
{
46+
throw new ArgumentNullException(nameof(features));
47+
}
48+
1549
_features = new FeatureReferences<IHttpResponseFeature>(features);
50+
_builderPool = builderPool;
1651
}
1752

18-
private IHttpResponseFeature HttpResponseFeature =>
19-
_features.Fetch(ref _features.Cache, f => null);
53+
private IHttpResponseFeature HttpResponseFeature => _features.Fetch(ref _features.Cache, f => null);
2054

55+
/// <inheritdoc />
2156
public IResponseCookies Cookies
2257
{
2358
get
2459
{
2560
if (_cookiesCollection == null)
2661
{
2762
var headers = HttpResponseFeature.Headers;
28-
_cookiesCollection = new ResponseCookies(headers);
63+
_cookiesCollection = new ResponseCookies(headers, _builderPool);
2964
}
65+
3066
return _cookiesCollection;
3167
}
3268
}

src/Microsoft.AspNetCore.Http/HttpContextFactory.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,51 @@
11
// 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

4+
using System;
5+
using System.Text;
46
using Microsoft.AspNetCore.Http.Features;
7+
using Microsoft.AspNetCore.Http.Features.Internal;
8+
using Microsoft.Extensions.ObjectPool;
59

610
namespace Microsoft.AspNetCore.Http.Internal
711
{
812
public class HttpContextFactory : IHttpContextFactory
913
{
10-
private IHttpContextAccessor _httpContextAccessor;
14+
private readonly ObjectPool<StringBuilder> _builderPool;
15+
private readonly IHttpContextAccessor _httpContextAccessor;
1116

12-
public HttpContextFactory() : this(httpContextAccessor: null)
17+
public HttpContextFactory(ObjectPoolProvider poolProvider)
18+
: this(poolProvider, httpContextAccessor: null)
1319
{
1420
}
1521

16-
public HttpContextFactory(IHttpContextAccessor httpContextAccessor)
22+
public HttpContextFactory(ObjectPoolProvider poolProvider, IHttpContextAccessor httpContextAccessor)
1723
{
24+
if (poolProvider == null)
25+
{
26+
throw new ArgumentNullException(nameof(poolProvider));
27+
}
28+
29+
_builderPool = poolProvider.CreateStringBuilderPool();
1830
_httpContextAccessor = httpContextAccessor;
1931
}
2032

2133
public HttpContext Create(IFeatureCollection featureCollection)
2234
{
35+
if (featureCollection == null)
36+
{
37+
throw new ArgumentNullException(nameof(featureCollection));
38+
}
39+
40+
var responseCookiesFeature = new ResponseCookiesFeature(featureCollection, _builderPool);
41+
featureCollection.Set<IResponseCookiesFeature>(responseCookiesFeature);
42+
2343
var httpContext = new DefaultHttpContext(featureCollection);
2444
if (_httpContextAccessor != null)
2545
{
2646
_httpContextAccessor.HttpContext = httpContext;
2747
}
48+
2849
return httpContext;
2950
}
3051

0 commit comments

Comments
 (0)