@@ -16,6 +16,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
16
16
{
17
17
internal sealed partial class HttpRequestHeaders : HttpHeaders
18
18
{
19
+ private EnumeratorCache ? _enumeratorCache ;
19
20
private long _previousBits ;
20
21
21
22
public bool ReuseHeaderValues { get ; set ; }
@@ -65,6 +66,7 @@ protected override void ClearFast()
65
66
// Clear ContentLength and any unknown headers as we will never reuse them
66
67
_contentLength = null ;
67
68
MaybeUnknown ? . Clear ( ) ;
69
+ _enumeratorCache ? . Reset ( ) ;
68
70
}
69
71
70
72
private static long ParseContentLength ( string value )
@@ -148,7 +150,73 @@ public Enumerator GetEnumerator()
148
150
149
151
protected override IEnumerator < KeyValuePair < string , StringValues > > GetEnumeratorFast ( )
150
152
{
151
- return GetEnumerator ( ) ;
153
+ // Get or create the cache.
154
+ var cache = _enumeratorCache ??= new ( ) ;
155
+
156
+ EnumeratorBox enumerator ;
157
+ if ( cache . CachedEnumerator is not null )
158
+ {
159
+ // Previous enumerator, reuse that one.
160
+ enumerator = cache . InUseEnumerator = cache . CachedEnumerator ;
161
+ // Set previous to null so if there is a second enumerator call
162
+ // during the same request it doesn't get the same one.
163
+ cache . CachedEnumerator = null ;
164
+ }
165
+ else
166
+ {
167
+ // Create new enumerator box and store as in use.
168
+ enumerator = cache . InUseEnumerator = new ( ) ;
169
+ }
170
+
171
+ // Set the underlying struct enumerator to a new one.
172
+ enumerator . Enumerator = new Enumerator ( this ) ;
173
+ return enumerator ;
174
+ }
175
+
176
+ private class EnumeratorCache
177
+ {
178
+ /// <summary>
179
+ /// Enumerator created from previous request
180
+ /// </summary>
181
+ public EnumeratorBox ? CachedEnumerator { get ; set ; }
182
+ /// <summary>
183
+ /// Enumerator used on this request
184
+ /// </summary>
185
+ public EnumeratorBox ? InUseEnumerator { get ; set ; }
186
+
187
+ /// <summary>
188
+ /// Moves InUseEnumerator to CachedEnumerator
189
+ /// </summary>
190
+ public void Reset ( )
191
+ {
192
+ var enumerator = InUseEnumerator ;
193
+ if ( enumerator is not null )
194
+ {
195
+ InUseEnumerator = null ;
196
+ enumerator . Enumerator = default ;
197
+ CachedEnumerator = enumerator ;
198
+ }
199
+ }
200
+ }
201
+
202
+ /// <summary>
203
+ /// Strong box enumerator for the IEnumerator interface to cache and amortizate the
204
+ /// IEnumerator allocations across requests if the header collection is commonly
205
+ /// enumerated for forwarding in a reverse-proxy type situation.
206
+ /// </summary>
207
+ private class EnumeratorBox : IEnumerator < KeyValuePair < string , StringValues > >
208
+ {
209
+ public Enumerator Enumerator ;
210
+
211
+ public KeyValuePair < string , StringValues > Current => Enumerator . Current ;
212
+
213
+ public bool MoveNext ( ) => Enumerator . MoveNext ( ) ;
214
+
215
+ object IEnumerator . Current => Current ;
216
+
217
+ public void Dispose ( ) { }
218
+
219
+ public void Reset ( ) => throw new NotSupportedException ( ) ;
152
220
}
153
221
154
222
public partial struct Enumerator : IEnumerator < KeyValuePair < string , StringValues > >
0 commit comments