-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Linux HttpClient.SendAsync resets Content Stream position #23438
Comments
cc: @stephentoub |
@rmkerr can you please finish this work for 2.1? Thanks! |
After a discussion we decided to move it to Future. We do not allow |
Not with HttpClient. What about with HttpMessageInvoker? Is it documented somewhere that the same message can't be used multiple times with it? (To be clear, I'm not objecting to pushing this out; I just want to make sure our rationale is correct.) |
@davidsh can you please chime in? |
I don't see anything stating that we can't reuse an |
It is not documented either way. However, in practice our implementation have evolved to be problematic for this. However, this particular issue here is actually not about whether or not a dev can re-use an HttpRequestMessage object for additional SendAsync() calls. Instead, this issue is about a difference in behaviors between the Windows and Linux HTTP stacks. Specifically, the Windows HTTP stack leaves request body context objects (HttpRequestMessage.Content) as-is after being "consumed" by sending. I.e. it doesn't explicitly reset the stream pointer of an StreamContent object after SendAsync() is done. The Linux implementation is rewinding the StreamContent object for some reason after sending. While it is true that the HttpClient stack will rewind an HttpContent (StreamContent) object during re-submits within a single SendAsync() (i.e. to resubmit due to 401, 3xx etc.), it won't do a final rewind of the content object. In general, the design principle in HttpClient/HttpContent objects is that they are "consumed" during send. |
I'd like to add on to this -- what I found was that because of the limitations of CurlHandler and of HttpRequestMessage, it is difficult to make the stream rewind behavior consistent. The longer explanation is that I think we should ideally converge the behavior at some point, but not for 2.1. That is especially true since we consider the HttpContent to have been consumed during send. It's something I'd be interested in working on later, but for now it would be a lot of effort spent on an unsupported behavior. |
I don't understand this explanation. The test you highlighted is passing on Windows. That means WinHttpHandler must be rewinding the stream as well to the starting position, as otherwise it would be failing this test, where the second iteration expects all of the relevant parts of the stream to be sent again, and that can only happen if the stream's position was rewound. What am I missing? |
You're right -- I definitely misunderstood the behavior on windows. In that case it may be worth giving this another look. |
The issue has to do with StreamContent. There are two ways of getting out the stream:
When a StreamContent is created, its ctor stores the stream's Position. SerializeToStream rewinds the stream to that Position; CreateContentReadStreamAsync does not. And WinHttpHandler uses CopyToAsync; CurlHandler uses ReadAsStreamAsync. Hence the difference in behavior here. (HttpContent also caches the stream it gets back from CreateContentReadStreamAsync, so even if CreateContentReadStreamAsync were changed to restore the position, that alone wouldn't help). |
@karelz @stephentoub Does this problem occur with SocketsHttpHandler as well? I think this problem is only with CurlHandler. Since SocketsHttpHandler is the default for .NET Core 2.1 and we plan to deprecate CurlHandler, perhaps we can close this issue? |
It shouldn't; SocketsHttpHandler uses the same CopyToAsync API that WinHttpHandler did, as outlined in https://github.com/dotnet/corefx/issues/23782#issuecomment-375790899.
We should add a test for this (or confirm we already have one), validate that it passes on HttpClientHandler now by default on both Windows and Unix, and then I think it's fine to close this. |
Issue #23782 was opened describing a behavior difference in Linux versus Windows regarding the final position of a stream after being sent. This turned out to be a behavior in CurlHandler that we decided to not fix. This PR adds a test to document the behavior of resending a request body content object (now possible now since we no longer dispose request content after send). This test is only validating the default HttpClientHandler which is SocketsHttpHandler now. It also matches .NET Framework behavior. Closes #23782
Issue #23782 was opened describing a behavior difference in Linux versus Windows regarding the final position of a stream after being sent. This turned out to be a behavior in CurlHandler that we decided to not fix. This PR adds a test to document the behavior of resending a request body content object (now possible now since we no longer dispose request content after send). This test is only validating the default HttpClientHandler which is SocketsHttpHandler now. It also matches .NET Framework behavior. Closes #23782
In the past the stream was disposed after SendAsync call. Now it isn't : dotnet/corefx#19082
However, on Linux the Stream.Position is reset to zero. On windows it remains on the last read position.
The following program, i.e., set the MemoryStream position to last read position 3 in Windows, but to 0 in Linux
OS : Windows 10 vs. Ubuntu16.04
.NetCore 2.0.0
[EDIT] Add C# syntax highlighting by @karelz
The text was updated successfully, but these errors were encountered: