From e92a2903ce665b640398cbc6b23f4e756b205142 Mon Sep 17 00:00:00 2001 From: Martin Baulig Date: Wed, 12 Jun 2019 15:17:16 -0400 Subject: [PATCH] Make NSUrlSessionHandler work with both HttpClient implementations. This is not a perfect solution, but the most robust and risk-free approach. The `NSUrlSessionHandler` implementation depends on Mono-specific behavior, which makes `SerializeToStreamAsync()` cancellable. Unfortunately, the CoreFX implementation of HttpClient does not support this. By copying Mono's old implementation here, we ensure that we're compatible with both HttpClient implementations, so when we eventually adopt the CoreFX version in all of Mono's profiles, we don't regress here. --- src/Foundation/NSUrlSessionHandler.cs | 94 ++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/src/Foundation/NSUrlSessionHandler.cs b/src/Foundation/NSUrlSessionHandler.cs index 23b06c95da..0bd4b37ad9 100644 --- a/src/Foundation/NSUrlSessionHandler.cs +++ b/src/Foundation/NSUrlSessionHandler.cs @@ -841,7 +841,7 @@ namespace Foundation { // Needed since we strip during linking since we're inside a product assembly. [Preserve (AllMembers = true)] - class NSUrlSessionDataTaskStreamContent : StreamContent + class NSUrlSessionDataTaskStreamContent : MonoStreamContent { Action disposed; @@ -859,6 +859,98 @@ namespace Foundation { } } + // + // Copied from https://github.com/mono/mono/blob/2019-02/mcs/class/System.Net.Http/System.Net.Http/StreamContent.cs. + // + // This is not a perfect solution, but the most robust and risk-free approach. + // + // The implementation depends on Mono-specific behavior, which makes SerializeToStreamAsync() cancellable. + // Unfortunately, the CoreFX implementation of HttpClient does not support this. + // + // By copying Mono's old implementation here, we ensure that we're compatible with both HttpClient implementations, + // so when we eventually adopt the CoreFX version in all of Mono's profiles, we don't regress here. + // + class MonoStreamContent : HttpContent + { + readonly Stream content; + readonly int bufferSize; + readonly CancellationToken cancellationToken; + readonly long startPosition; + bool contentCopied; + + public MonoStreamContent (Stream content) + : this (content, 16 * 1024) + { + } + + public MonoStreamContent (Stream content, int bufferSize) + { + if (content == null) + throw new ArgumentNullException ("content"); + + if (bufferSize <= 0) + throw new ArgumentOutOfRangeException ("bufferSize"); + + this.content = content; + this.bufferSize = bufferSize; + + if (content.CanSeek) { + startPosition = content.Position; + } + } + + // + // Workarounds for poor .NET API + // Instead of having SerializeToStreamAsync with CancellationToken as public API. Only LoadIntoBufferAsync + // called internally from the send worker can be cancelled and user cannot see/do it + // + internal MonoStreamContent (Stream content, CancellationToken cancellationToken) + : this (content) + { + // We don't own the token so don't worry about disposing it + this.cancellationToken = cancellationToken; + } + + protected override Task CreateContentReadStreamAsync () + { + return Task.FromResult (content); + } + + protected override void Dispose (bool disposing) + { + if (disposing) { + content.Dispose (); + } + + base.Dispose (disposing); + } + + protected internal override Task SerializeToStreamAsync (Stream stream, TransportContext context) + { + if (contentCopied) { + if (!content.CanSeek) { + throw new InvalidOperationException ("The stream was already consumed. It cannot be read again."); + } + + content.Seek (startPosition, SeekOrigin.Begin); + } else { + contentCopied = true; + } + + return content.CopyToAsync (stream, bufferSize, cancellationToken); + } + + protected internal override bool TryComputeLength (out long length) + { + if (!content.CanSeek) { + length = 0; + return false; + } + length = content.Length - startPosition; + return true; + } + } + // Needed since we strip during linking since we're inside a product assembly. [Preserve (AllMembers = true)] class NSUrlSessionDataTaskStream : Stream