NSUrlSessionHandler: Rewind request content to the start before sending (#16341)

Fixes #16339

---------

Co-authored-by: Manuel de la Pena <mandel@microsoft.com>
Co-authored-by: Rolf Bjarne Kvinge <rolf@xamarin.com>
This commit is contained in:
Yauheni Pakala 2024-02-28 18:12:06 +01:00 коммит произвёл GitHub
Родитель ca562147e8
Коммит 6ef7d9c0f9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
2 изменённых файлов: 79 добавлений и 0 удалений

Просмотреть файл

@ -491,6 +491,11 @@ namespace Foundation {
};
if (stream != Stream.Null) {
// Rewind the stream to the beginning in case the HttpContent implementation
// will be accessed again (e.g. for retry/redirect) and it keeps its stream open behind the scenes.
if (stream.CanSeek)
stream.Seek (0, SeekOrigin.Begin);
// HttpContent.TryComputeLength is `protected internal` :-( but it's indirectly called by headers
var length = request.Content?.Headers?.ContentLength;
if (length.HasValue && (length <= MaxInputInMemory))

Просмотреть файл

@ -758,5 +758,79 @@ namespace MonoTests.System.Net.Http {
Assert.AreEqual (HttpStatusCode.Unauthorized, httpStatus, "Second status not ok");
}
}
class TestDelegateHandler : DelegatingHandler {
public int Iterations;
public HttpResponseMessage [] Responses;
public TestDelegateHandler (int iterations)
{
Responses = new HttpResponseMessage [iterations];
Iterations = iterations;
}
public bool IsCompleted (int iteration)
{
return Responses [iteration] is not null;
}
protected override async Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
{
// test that we can perform a retry with the same request
for (var i = 0; i < Iterations; i++)
Responses [i] = await base.SendAsync (request, cancellationToken);
return Responses.Last ();
}
}
[TestCase]
public void GHIssue16339 ()
{
// test that we can perform two diff requests with the same managed HttpRequestMessage
var json = "{this:\"\", is:\"a\", test:2}";
var iterations = 2;
var bodies = new string [iterations];
var request = new HttpRequestMessage {
Method = HttpMethod.Post,
RequestUri = new (NetworkResources.Httpbin.PostUrl),
Content = new StringContent (json, Encoding.UTF8, "application/json")
};
using var delegatingHandler = new TestDelegateHandler (iterations) {
InnerHandler = new NSUrlSessionHandler (),
};
var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => {
using var client = new HttpClient (delegatingHandler);
var _ = await client.SendAsync (request);
for (var i = 0; i < iterations; i++) {
if (delegatingHandler.IsCompleted (i))
bodies [i] = await delegatingHandler.Responses [i].Content.ReadAsStringAsync ();
}
}, out var ex);
if (!done) { // timeouts happen in the bots due to dns issues, connection issues etc.. we do not want to fail
Assert.Inconclusive ("Request timedout.");
} else {
Assert.IsNull (ex, "Exception");
for (var i = 0; i < iterations; i++) {
var rsp = delegatingHandler.Responses [i];
Assert.IsTrue (delegatingHandler.IsCompleted (i), $"Completed #{i}");
Assert.IsTrue (rsp.IsSuccessStatusCode, $"IsSuccessStatusCode #{i}");
Assert.AreEqual ("OK", rsp.ReasonPhrase, $"ReasonPhrase #{i}");
Assert.AreEqual (HttpStatusCode.OK, rsp.StatusCode, $"StatusCode #{i}");
var body = bodies [i];
// Poor-man's json parser
var data = body.Split ('\n', '\r').Single (v => v.Contains ("\"data\": \""));
data = data.Trim ().Replace ("\"data\": \"", "").TrimEnd ('"', ',');
data = data.Replace ("\\\"", "\"");
Assert.AreEqual (json, data, $"Post data #{i}");
}
}
}
}
}