diff --git a/src/Flurl.Http/Flurl.Http.csproj b/src/Flurl.Http/Flurl.Http.csproj index d130829f..38ab7bc4 100644 --- a/src/Flurl.Http/Flurl.Http.csproj +++ b/src/Flurl.Http/Flurl.Http.csproj @@ -3,7 +3,7 @@ netstandard2.0;net461;net472 True Flurl.Http - 3.2.4 + 3.2.5 Todd Menier A fluent, portable, testable HTTP client library. https://flurl.dev diff --git a/src/Flurl.Http/FlurlRequest.cs b/src/Flurl.Http/FlurlRequest.cs index a340114c..e54e67d2 100644 --- a/src/Flurl.Http/FlurlRequest.cs +++ b/src/Flurl.Http/FlurlRequest.cs @@ -180,13 +180,14 @@ public async Task SendAsync(HttpMethod verb, HttpContent content var ct = GetCancellationTokenWithTimeout(cancellationToken, out var cts); try { + var cloneContent = await content.CloneAsync().ConfigureAwait(false); var response = await Client.HttpClient.SendAsync(request, completionOption, ct).ConfigureAwait(false); call.HttpResponseMessage = response; call.HttpResponseMessage.RequestMessage = request; call.Response = new FlurlResponse(call.HttpResponseMessage, CookieJar); if (call.Succeeded) { - var redirResponse = await ProcessRedirectAsync(call, cancellationToken, completionOption).ConfigureAwait(false); + var redirResponse = await ProcessRedirectAsync(call, cloneContent, cancellationToken, completionOption).ConfigureAwait(false); return redirResponse ?? call.Response; } else @@ -231,7 +232,7 @@ private CancellationToken GetCancellationTokenWithTimeout(CancellationToken orig } } - private async Task ProcessRedirectAsync(FlurlCall call, CancellationToken cancellationToken, HttpCompletionOption completionOption) { + private async Task ProcessRedirectAsync(FlurlCall call, HttpContent content, CancellationToken cancellationToken, HttpCompletionOption completionOption) { if (Settings.Redirects.Enabled) call.Redirect = GetRedirect(call); @@ -266,7 +267,7 @@ private async Task ProcessRedirectAsync(FlurlCall call, Cancella try { return await redir.SendAsync( changeToGet ? HttpMethod.Get : call.HttpRequestMessage.Method, - changeToGet ? null : call.HttpRequestMessage.Content, + changeToGet ? null : content, ct, completionOption).ConfigureAwait(false); } diff --git a/src/Flurl.Http/HttpContentExtensions.cs b/src/Flurl.Http/HttpContentExtensions.cs new file mode 100644 index 00000000..7a530f9a --- /dev/null +++ b/src/Flurl.Http/HttpContentExtensions.cs @@ -0,0 +1,38 @@ +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Flurl.Http +{ + /// + /// Extension methods of HttpContent + /// + public static class HttpContentExtensions + { + /// Get a copy of the request content. + /// The content to copy. + /// The cancellation token. + /// Note that cloning content isn't possible after it's dispatched, because the stream is automatically disposed after the request. + internal static async Task CloneAsync(this HttpContent content, CancellationToken cancellationToken = default) { + if (content == null) + return null; + + Stream stream = new MemoryStream(); + await content + .CopyToAsync(stream +#if NET5_0_OR_GREATER + , cancellationToken +#endif + ) + .ConfigureAwait(false); + stream.Position = 0; + + StreamContent clone = new StreamContent(stream); + foreach (var header in content.Headers) + clone.Headers.Add(header.Key, header.Value); + + return clone; + } + } +}