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.
This commit is contained in:
Martin Baulig 2019-06-12 15:17:16 -04:00
Родитель 253170dfe9
Коммит e92a2903ce
1 изменённых файлов: 93 добавлений и 1 удалений

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

@ -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<Stream> 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