This repository was archived by the owner on Dec 18, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 524
Call ProduceEnd() before consuming request body if response has already started (#1530) #1537
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -341,7 +341,7 @@ public async Task SingleErrorResponseSentWhenAppSwallowsBadRequestException() | |
{ | ||
readException = await Assert.ThrowsAsync<BadHttpRequestException>( | ||
async () => await httpContext.Request.Body.ReadAsync(new byte[1], 0, 1)); | ||
}, new TestServiceContext())) | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
|
@@ -370,7 +370,7 @@ public async Task TransferEncodingChunkedSetOnUnknownLengthHttp11Response() | |
{ | ||
await httpContext.Response.WriteAsync("hello, "); | ||
await httpContext.Response.WriteAsync("world"); | ||
}, new TestServiceContext())) | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
|
@@ -404,7 +404,7 @@ public async Task TransferEncodingChunkedNotSetOnNonBodyResponse(int statusCode) | |
{ | ||
httpContext.Response.StatusCode = statusCode; | ||
return TaskCache.CompletedTask; | ||
}, new TestServiceContext())) | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
|
@@ -427,7 +427,7 @@ public async Task TransferEncodingNotSetOnHeadResponse() | |
using (var server = new TestServer(httpContext => | ||
{ | ||
return TaskCache.CompletedTask; | ||
}, new TestServiceContext())) | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
|
@@ -880,7 +880,7 @@ public async Task HeadResponseCanContainContentLengthHeader() | |
{ | ||
httpContext.Response.ContentLength = 42; | ||
return TaskCache.CompletedTask; | ||
}, new TestServiceContext())) | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
|
@@ -908,7 +908,7 @@ public async Task HeadResponseBodyNotWrittenWithAsyncWrite() | |
httpContext.Response.ContentLength = 12; | ||
await httpContext.Response.WriteAsync("hello, world"); | ||
await flushed.WaitAsync(); | ||
}, new TestServiceContext())) | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
|
@@ -939,7 +939,7 @@ public async Task HeadResponseBodyNotWrittenWithSyncWrite() | |
httpContext.Response.Body.Write(Encoding.ASCII.GetBytes("hello, world"), 0, 12); | ||
flushed.Wait(); | ||
return TaskCache.CompletedTask; | ||
}, new TestServiceContext())) | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
|
@@ -970,7 +970,7 @@ public async Task ZeroLengthWritesFlushHeaders() | |
await httpContext.Response.WriteAsync(""); | ||
flushed.Wait(); | ||
await httpContext.Response.WriteAsync("hello, world"); | ||
}, new TestServiceContext())) | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
|
@@ -1013,7 +1013,7 @@ public async Task WriteAfterConnectionCloseNoops() | |
{ | ||
tcs.TrySetException(ex); | ||
} | ||
}, new TestServiceContext())) | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
|
@@ -1053,7 +1053,7 @@ public async Task AppCanWriteOwnBadRequestResponse() | |
await httpContext.Response.WriteAsync(ex.Message); | ||
responseWritten.Release(); | ||
} | ||
}, new TestServiceContext())) | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
|
@@ -1084,7 +1084,7 @@ public async Task ConnectionClosedWhenChunkedIsNotFinalTransferCoding(string res | |
{ | ||
httpContext.Response.Headers["Transfer-Encoding"] = responseTransferEncoding; | ||
await httpContext.Response.WriteAsync("hello, world"); | ||
}, new TestServiceContext())) | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
|
@@ -1131,7 +1131,7 @@ public async Task ConnectionClosedWhenChunkedIsNotFinalTransferCodingEvenIfConne | |
httpContext.Response.Headers["Connection"] = "keep-alive"; | ||
httpContext.Response.Headers["Transfer-Encoding"] = responseTransferEncoding; | ||
await httpContext.Response.WriteAsync("hello, world"); | ||
}, new TestServiceContext())) | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
|
@@ -1177,7 +1177,7 @@ public async Task ConnectionKeptAliveWhenChunkedIsFinalTransferCoding(string res | |
|
||
// App would have to chunk manually, but here we don't care | ||
await httpContext.Response.WriteAsync("hello, world"); | ||
}, new TestServiceContext())) | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
|
@@ -1225,7 +1225,7 @@ public async Task FirstWriteVerifiedAfterOnStarting() | |
// If OnStarting is not run before verifying writes, an error response will be sent. | ||
httpContext.Response.Body.Write(response, 0, response.Length); | ||
return TaskCache.CompletedTask; | ||
}, new TestServiceContext())) | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
|
@@ -1266,7 +1266,7 @@ public async Task SubsequentWriteVerifiedAfterOnStarting() | |
httpContext.Response.Body.Write(response, 0, response.Length / 2); | ||
httpContext.Response.Body.Write(response, response.Length / 2, response.Length - response.Length / 2); | ||
return TaskCache.CompletedTask; | ||
}, new TestServiceContext())) | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
|
@@ -1307,7 +1307,7 @@ public async Task FirstWriteAsyncVerifiedAfterOnStarting() | |
|
||
// If OnStarting is not run before verifying writes, an error response will be sent. | ||
return httpContext.Response.Body.WriteAsync(response, 0, response.Length); | ||
}, new TestServiceContext())) | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
|
@@ -1347,7 +1347,7 @@ public async Task SubsequentWriteAsyncVerifiedAfterOnStarting() | |
// If OnStarting is not run before verifying writes, an error response will be sent. | ||
await httpContext.Response.Body.WriteAsync(response, 0, response.Length / 2); | ||
await httpContext.Response.Body.WriteAsync(response, response.Length / 2, response.Length - response.Length / 2); | ||
}, new TestServiceContext())) | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
|
@@ -1371,6 +1371,162 @@ await connection.Receive( | |
} | ||
} | ||
|
||
[Fact] | ||
public async Task WhenResponseAlreadyStartedResponseEndedBeforeConsumingRequestBody() | ||
{ | ||
using (var server = new TestServer(async httpContext => | ||
{ | ||
await httpContext.Response.WriteAsync("hello, world"); | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
await connection.Send( | ||
"POST / HTTP/1.1", | ||
"Content-Length: 1", | ||
"", | ||
""); | ||
|
||
await connection.Receive( | ||
"HTTP/1.1 200 OK", | ||
$"Date: {server.Context.DateHeaderValue}", | ||
$"Transfer-Encoding: chunked", | ||
"", | ||
"c", | ||
"hello, world", | ||
""); | ||
|
||
// If the expected behavior is regressed, this will hang because the | ||
// server will try to consume the request body before flushing the chunked | ||
// terminator. | ||
await connection.Receive( | ||
"0", | ||
"", | ||
""); | ||
} | ||
} | ||
} | ||
|
||
[Fact] | ||
public async Task WhenResponseNotStartedResponseEndedAfterConsumingRequestBody() | ||
{ | ||
using (var server = new TestServer(httpContext => TaskCache.CompletedTask)) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
await connection.Send( | ||
"POST / HTTP/1.1", | ||
"Transfer-Encoding: chunked", | ||
"", | ||
"gg"); | ||
|
||
// If the expected behavior is regressed, this will receive | ||
// a success response because the server flushed the response | ||
// before reading the malformed chunk header in the request. | ||
await connection.ReceiveForcedEnd( | ||
"HTTP/1.1 400 Bad Request", | ||
"Connection: close", | ||
$"Date: {server.Context.DateHeaderValue}", | ||
"Content-Length: 0", | ||
"", | ||
""); | ||
} | ||
} | ||
} | ||
|
||
[Fact] | ||
public async Task Sending100ContinueDoesNotStartResponse() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we test sending the chunk terminator pre-consume when we send a 100-continue and the app sends a response? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added. |
||
{ | ||
using (var server = new TestServer(httpContext => | ||
{ | ||
return httpContext.Request.Body.ReadAsync(new byte[1], 0, 1); | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
await connection.Send( | ||
"POST / HTTP/1.1", | ||
"Transfer-Encoding: chunked", | ||
"Expect: 100-continue", | ||
"", | ||
""); | ||
|
||
await connection.Receive( | ||
"HTTP/1.1 100 Continue", | ||
"", | ||
""); | ||
|
||
// Let the app finish | ||
await connection.Send( | ||
"1", | ||
"a", | ||
""); | ||
|
||
// This will be consumed by Frame when it attempts to | ||
// consume the request body and will cause an error. | ||
await connection.Send( | ||
"gg"); | ||
|
||
// If 100 Continue sets Frame.HasResponseStarted to true, | ||
// a success response will be produced before the server sees the | ||
// bad chunk header above, making this test fail. | ||
await connection.ReceiveForcedEnd( | ||
"HTTP/1.1 400 Bad Request", | ||
"Connection: close", | ||
$"Date: {server.Context.DateHeaderValue}", | ||
"Content-Length: 0", | ||
"", | ||
""); | ||
} | ||
} | ||
} | ||
|
||
[Fact] | ||
public async Task Sending100ContinueAndResponseSendsChunkTerminatorBeforeConsumingRequestBody() | ||
{ | ||
using (var server = new TestServer(async httpContext => | ||
{ | ||
await httpContext.Request.Body.ReadAsync(new byte[1], 0, 1); | ||
await httpContext.Response.WriteAsync("hello, world"); | ||
})) | ||
{ | ||
using (var connection = server.CreateConnection()) | ||
{ | ||
await connection.Send( | ||
"POST / HTTP/1.1", | ||
"Content-Length: 2", | ||
"Expect: 100-continue", | ||
"", | ||
""); | ||
|
||
await connection.Receive( | ||
"HTTP/1.1 100 Continue", | ||
"", | ||
""); | ||
|
||
await connection.Send( | ||
"a"); | ||
|
||
await connection.Receive( | ||
"HTTP/1.1 200 OK", | ||
$"Date: {server.Context.DateHeaderValue}", | ||
$"Transfer-Encoding: chunked", | ||
"", | ||
"c", | ||
"hello, world", | ||
""); | ||
|
||
// If the expected behavior is regressed, this will hang because the | ||
// server will try to consume the request body before flushing the chunked | ||
// terminator. | ||
await connection.Receive( | ||
"0", | ||
"", | ||
""); | ||
} | ||
} | ||
} | ||
|
||
public static TheoryData<string, StringValues, string> NullHeaderData | ||
{ | ||
get | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you split this into two calls to connection.Receive() just to insert the above comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. It's to make it clear that the first part will be received fine, but if there's a regression the connection will time out waiting for the chunk terminator.