Skip to content

Commit 439f4af

Browse files
authored
Cache _absoluteRequestTarget when reusing strings (#18547)
1 parent f3e2b4d commit 439f4af

File tree

3 files changed

+49
-8
lines changed

3 files changed

+49
-8
lines changed

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ internal partial class Http1Connection : HttpProtocol, IRequestProcessor
3434
private HttpRequestTarget _requestTargetForm = HttpRequestTarget.Unknown;
3535
private Uri _absoluteRequestTarget;
3636

37+
// The _parsed fields cache the Path, QueryString, RawTarget, and/or _absoluteRequestTarget
38+
// from the previous request when DisableStringReuse is false.
39+
private string _parsedPath = null;
40+
private string _parsedQueryString = null;
41+
private string _parsedRawTarget = null;
42+
private Uri _parsedAbsoluteRequestTarget;
43+
3744
private int _remainingRequestHeadersBytesAllowed;
3845

3946
public Http1Connection(HttpConnectionContext context)
@@ -337,6 +344,7 @@ private void OnOriginFormTarget(bool pathEncoded, Span<byte> target, Span<byte>
337344
// Clear parsedData as we won't check it if we come via this path again,
338345
// an setting to null is fast as it doesn't need to use a GC write barrier.
339346
_parsedRawTarget = _parsedPath = _parsedQueryString = null;
347+
_parsedAbsoluteRequestTarget = null;
340348
return;
341349
}
342350

@@ -389,6 +397,10 @@ private void OnOriginFormTarget(bool pathEncoded, Span<byte> target, Span<byte>
389397
Path = _parsedPath;
390398
QueryString = _parsedQueryString;
391399
}
400+
401+
// Clear parsedData for absolute target as we won't check it if we come via this path again,
402+
// an setting to null is fast as it doesn't need to use a GC write barrier.
403+
_parsedAbsoluteRequestTarget = null;
392404
}
393405
catch (InvalidOperationException)
394406
{
@@ -441,9 +453,10 @@ private void OnAuthorityFormTarget(HttpMethod method, Span<byte> target)
441453

442454
Path = string.Empty;
443455
QueryString = string.Empty;
444-
// Clear parsedData for path and queryString as we won't check it if we come via this path again,
456+
// Clear parsedData for path, queryString and absolute target as we won't check it if we come via this path again,
445457
// an setting to null is fast as it doesn't need to use a GC write barrier.
446458
_parsedPath = _parsedQueryString = null;
459+
_parsedAbsoluteRequestTarget = null;
447460
}
448461

449462
private void OnAsteriskFormTarget(HttpMethod method)
@@ -463,6 +476,7 @@ private void OnAsteriskFormTarget(HttpMethod method)
463476
// Clear parsedData as we won't check it if we come via this path again,
464477
// an setting to null is fast as it doesn't need to use a GC write barrier.
465478
_parsedRawTarget = _parsedPath = _parsedQueryString = null;
479+
_parsedAbsoluteRequestTarget = null;
466480
}
467481

468482
private void OnAbsoluteFormTarget(Span<byte> target, Span<byte> query)
@@ -497,7 +511,7 @@ private void OnAbsoluteFormTarget(Span<byte> target, Span<byte> query)
497511
ThrowRequestTargetRejected(target);
498512
}
499513

500-
_absoluteRequestTarget = uri;
514+
_absoluteRequestTarget = _parsedAbsoluteRequestTarget = uri;
501515
Path = _parsedPath = uri.LocalPath;
502516
// don't use uri.Query because we need the unescaped version
503517
previousValue = _parsedQueryString;
@@ -520,6 +534,7 @@ private void OnAbsoluteFormTarget(Span<byte> target, Span<byte> query)
520534
RawTarget = _parsedRawTarget;
521535
Path = _parsedPath;
522536
QueryString = _parsedQueryString;
537+
_absoluteRequestTarget = _parsedAbsoluteRequestTarget;
523538
}
524539
}
525540

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,8 @@ public string TraceIdentifier
134134
public HttpMethod Method { get; set; }
135135
public string PathBase { get; set; }
136136

137-
protected string _parsedPath = null;
138137
public string Path { get; set; }
139-
140-
protected string _parsedQueryString = null;
141138
public string QueryString { get; set; }
142-
143-
protected string _parsedRawTarget = null;
144139
public string RawTarget { get; set; }
145140

146141
public string HttpVersion

src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,38 @@ await connection.Receive($"HTTP/1.1 200 OK",
253253
}
254254
}
255255

256+
[Fact]
257+
public async Task CanHandleTwoAbsoluteFormRequestsInARow()
258+
{
259+
// Regression test for https://github.com/dotnet/aspnetcore/issues/18438
260+
var testContext = new TestServiceContext(LoggerFactory);
261+
262+
await using (var server = new TestServer(TestApp.EchoAppChunked, testContext))
263+
{
264+
using (var connection = server.CreateConnection())
265+
{
266+
await connection.Send(
267+
"GET http://localhost/ HTTP/1.1",
268+
"Host: localhost",
269+
"",
270+
"GET http://localhost/ HTTP/1.1",
271+
"Host: localhost",
272+
"",
273+
"");
274+
await connection.Receive(
275+
"HTTP/1.1 200 OK",
276+
$"Date: {testContext.DateHeaderValue}",
277+
"Content-Length: 0",
278+
"",
279+
"HTTP/1.1 200 OK",
280+
$"Date: {testContext.DateHeaderValue}",
281+
"Content-Length: 0",
282+
"",
283+
"");
284+
}
285+
}
286+
}
287+
256288
[Fact]
257289
public async Task AppCanSetTraceIdentifier()
258290
{
@@ -358,7 +390,6 @@ await connection.ReceiveEnd(
358390
}
359391
}
360392

361-
362393
[Fact]
363394
public async Task Http10NotKeptAliveByDefault()
364395
{

0 commit comments

Comments
 (0)