Skip to content
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

Implement HttpWebRequest AllowWriteStreamBuffering property #95001

Merged
merged 58 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
cf19c74
Implement HttpWebRequest WriteStreamBuffering
liveans Nov 20, 2023
6167c96
Fix tests
liveans Dec 4, 2023
f1e9a29
Fix tests
liveans Dec 4, 2023
5015280
Review feedback
liveans Dec 4, 2023
b7c62d1
Fix timeouts
liveans Dec 4, 2023
9ae4383
fix test build
liveans Dec 4, 2023
3114962
Hang test
liveans Dec 5, 2023
266b610
Merge branch 'main' of github.com:dotnet/runtime into http-write-stre…
liveans Dec 10, 2023
8e20ffa
Fix build
liveans Dec 10, 2023
98152a2
fix tests
liveans Dec 15, 2023
ff5e2bd
delete unnecessary line
liveans Dec 15, 2023
32832f7
fix tests
liveans Dec 15, 2023
69f3da7
refactor test
liveans Dec 15, 2023
dd0b860
Dispose http client
liveans Dec 15, 2023
31ef8a6
Fix buffer write in RequestStream.Flush() method
liveans Jan 2, 2024
c5efd40
Fix same length bug in FlushAsync
liveans Jan 2, 2024
9fb2978
Update ContentLength in HttpWebRequestTest.cs
liveans Jan 2, 2024
ea38e0e
Fix flushing and ending request stream
liveans Jan 3, 2024
a5511ea
Merge branch 'main' into http-write-stream-buffering-impl
liveans Jan 5, 2024
03455dc
Fix flushing and ending request stream
liveans Jan 5, 2024
2318eb5
Fix FlushAsync method to handle cancellation
liveans Jan 5, 2024
ce2f240
Merge branch 'main' into http-write-stream-buffering-impl
liveans Jan 9, 2024
251f6dd
Merge branch 'main' into http-write-stream-buffering-impl
liveans Jan 24, 2024
01e7cca
Update src/libraries/System.Net.Requests/src/System/Net/HttpClientCon…
liveans Jan 26, 2024
f6c3ad0
Review feedback
liveans Jan 31, 2024
cc8c351
Bound streamBuffer lifecycle to HttpClientContentStream
liveans Feb 1, 2024
a2b2b84
Merge branch 'main' into http-write-stream-buffering-impl
liveans Feb 1, 2024
7836a8f
Review feedback
liveans Feb 3, 2024
62067e7
Review feedback
liveans Feb 3, 2024
964d4c6
Change ??= to =
liveans Feb 4, 2024
cc28b79
change delay on test
liveans Feb 4, 2024
607991c
Merge branch 'main' into http-write-stream-buffering-impl
liveans Feb 5, 2024
7d4de22
Apply suggestions from code review
liveans Feb 6, 2024
864fbb7
Fix build
liveans Feb 6, 2024
3b0287a
Review feedback
liveans Feb 7, 2024
149a843
Apply suggestions from code review
liveans Feb 10, 2024
37f990b
Review feedback
liveans Feb 12, 2024
eb494f3
Apply suggestions from code review
liveans Feb 16, 2024
55b5016
Add ProtocolViolationException if we're not buffering and we didn't s…
liveans Feb 16, 2024
a93a60c
Merge branch 'http-write-stream-buffering-impl' of github.com:liveans…
liveans Feb 16, 2024
957ee2c
Review feedback
liveans Feb 16, 2024
6650652
Add test for not buffering and sending the content before we call `Ge…
liveans Feb 16, 2024
454752c
Update src/libraries/System.Net.Requests/src/System/Net/HttpWebReques…
liveans Feb 16, 2024
928eaa2
Remove exception
liveans Feb 16, 2024
4f76467
Merge branch 'http-write-stream-buffering-impl' of github.com:liveans…
liveans Feb 16, 2024
8ebaa78
Review feedback
liveans Feb 16, 2024
de7f101
Fix string resources
liveans Feb 19, 2024
6f2ba90
Add RequestStreamContent class for handling request stream serialization
liveans Feb 20, 2024
dd1bdde
Seperate Buffering and Non-Buffering Streams and Connect non-bufferin…
liveans Feb 23, 2024
c60f443
Remove unnecessary code in HttpWebRequestTest
liveans Feb 23, 2024
a699449
Merge branch 'main' into http-write-stream-buffering-impl
liveans Feb 23, 2024
dcac125
Use random data for testing
liveans Feb 26, 2024
c01da77
Update src/libraries/System.Net.Requests/src/System/Net/RequestBuffer…
liveans Feb 26, 2024
1b51e50
Merge branch 'main' into http-write-stream-buffering-impl
liveans Feb 26, 2024
38656d5
Remove default flushes and add flush on test
liveans Feb 27, 2024
796f1e9
Merge branch 'main' into http-write-stream-buffering-impl
liveans Mar 1, 2024
da86238
Review feedback
liveans Mar 4, 2024
804b80c
Removing unused code
liveans Mar 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<Compile Include="System\Net\IWebRequestCreate.cs" />
<Compile Include="System\Net\ProtocolViolationException.cs" />
<Compile Include="System\Net\RequestStream.cs" />
<Compile Include="System\Net\RequestBufferingStream.cs" />
<Compile Include="System\Net\TaskExtensions.cs" />
<Compile Include="System\Net\WebException.cs" />
<Compile Include="System\Net\WebExceptionStatus.cs" />
Expand All @@ -48,7 +49,6 @@
<Compile Include="System\Net\NetRes.cs" />
<Compile Include="System\Net\NetworkStreamWrapper.cs" />
<Compile Include="System\Net\TimerThread.cs" />
<Compile Include="System\Net\HttpClientContentStream.cs" />
<Compile Include="System\Net\RequestStreamContent.cs" />
<Compile Include="System\Net\Cache\HttpCacheAgeControl.cs" />
<Compile Include="System\Net\Cache\HttpRequestCacheLevel.cs" />
Expand Down Expand Up @@ -87,10 +87,6 @@
Link="Common\System\Net\SecurityProtocol.cs" />
<Compile Include="$(CommonPath)System\NotImplemented.cs"
Link="Common\System\NotImplemented.cs" />
<Compile Include="$(CommonPath)\System\Net\StreamBuffer.cs"
Link="Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonPath)\System\Net\MultiArrayBuffer.cs"
Link="Common\System\Net\MultiArrayBuffer.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'windows'">
Expand Down

This file was deleted.

47 changes: 27 additions & 20 deletions src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public class HttpWebRequest : WebRequest, ISerializable
private bool _hostHasPort;
private Uri? _hostUri;

private RequestStream? _requestStream;
private Stream? _requestStream;
private TaskCompletionSource<Stream>? _requestStreamOperation;
private TaskCompletionSource<WebResponse>? _responseOperation;
private AsyncCallback? _requestStreamCallback;
Expand Down Expand Up @@ -1070,22 +1070,24 @@ private Task<Stream> InternalGetRequestStream()
throw new InvalidOperationException(SR.net_reqsubmitted);
}

// Create stream buffer for transferring data from RequestStream to the StreamContent.
StreamBuffer streamBuffer = new StreamBuffer(maxBufferSize: AllowWriteStreamBuffering ? int.MaxValue : StreamBuffer.DefaultMaxBufferSize);

// If we aren't buffering we need to open the connection right away.
// Because we need to send the data as soon as possible when it's available from the RequestStream.
// Making this allows us to keep the sync send request path for buffering cases.
if (AllowWriteStreamBuffering is false)
{
// We're calling SendRequest with async, because we need to open the connection and send the request
// Otherwise, sync path will block the current thread until the request is sent.
_sendRequestTask = SendRequest(true, new RequestStreamContent(new HttpClientContentStream(streamBuffer)));
TaskCompletionSource<Stream> getStreamTcs = new();
TaskCompletionSource completeTcs = new();
_sendRequestTask = SendRequest(async: true, new RequestStreamContent(getStreamTcs, completeTcs));
_requestStream = new RequestStream(getStreamTcs.Task.GetAwaiter().GetResult(), completeTcs);
}
else
{
_requestStream = new RequestBufferingStream();
}

_requestStream = new RequestStream(streamBuffer);

return Task.FromResult((Stream)_requestStream);
return Task.FromResult(_requestStream);
}

public Stream EndGetRequestStream(IAsyncResult asyncResult, out TransportContext? context)
Expand Down Expand Up @@ -1155,12 +1157,6 @@ private Task<HttpResponseMessage> SendRequest(bool async, HttpContent? content =

if (content is not null)
{
// Calculate Content-Length if we're buffering.
if (AllowWriteStreamBuffering && _requestStream is not null)
{
content.Headers.ContentLength = _requestStream.GetBuffer().ReadBytesAvailable;
}

_sendRequestMessage.Content = content;
}

Expand Down Expand Up @@ -1218,13 +1214,20 @@ private Task<HttpResponseMessage> SendRequest(bool async, HttpContent? content =

private async Task<WebResponse> HandleResponse(bool async)
{
// If user code used requestStream and didn't dispose it (end write on StreamBuffer)
// We're ending it here.
_requestStream?.EndWriteOnStreamBuffer();
// If user code used requestStream and didn't dispose it
// We're completing it here.
if (_requestStream is RequestStream requestStream)
{
requestStream.Complete();
}

_sendRequestTask ??= _requestStream is not null ?
SendRequest(async, new StreamContent(new HttpClientContentStream(_requestStream.GetBuffer()))) :
SendRequest(async);
if (_sendRequestTask is null && _requestStream is RequestBufferingStream requestBufferingStream)
{
ArraySegment<byte> buffer = requestBufferingStream.GetBuffer();
_sendRequestTask = SendRequest(async, new ByteArrayContent(buffer.Array!, buffer.Offset, buffer.Count));
}

_sendRequestTask ??= SendRequest(async);

try
{
Expand All @@ -1246,6 +1249,10 @@ private async Task<WebResponse> HandleResponse(bool async)
finally
{
_sendRequestMessage?.Dispose();
if (_requestStream is RequestBufferingStream bufferStream)
{
bufferStream.GetMemoryStream().Dispose();
}

if (_disposeRequired)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace System.Net
{
// Cache the request stream into a MemoryStream.
internal sealed class RequestBufferingStream : Stream
{
private bool _disposed;
private readonly MemoryStream _buffer = new MemoryStream();

public RequestBufferingStream()
{
}

public override bool CanRead
{
get
{
return false;
}
}

public override bool CanSeek
{
get
{
return false;
}
}

public override bool CanWrite
{
get
{
return true;
}
}

public override void Flush()
{
ThrowIfDisposed();
// Nothing to do.
}

public override Task FlushAsync(CancellationToken cancellationToken)
{
ThrowIfDisposed();
// Nothing to do.
return cancellationToken.IsCancellationRequested ?
Task.FromCanceled(cancellationToken) :
Task.CompletedTask;
}

public override long Length
{
get
{
throw new NotSupportedException();
}
}

public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}

public override int Read(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}

public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}

public override void SetLength(long value)
{
throw new NotSupportedException();
}

public override void Write(byte[] buffer, int offset, int count)
{
ThrowIfDisposed();
ValidateBufferArguments(buffer, offset, count);
_buffer.Write(buffer, offset, count);
}

public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ThrowIfDisposed();
ValidateBufferArguments(buffer, offset, count);
return _buffer.WriteAsync(buffer, offset, count, cancellationToken);
}

public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
return _buffer.WriteAsync(buffer, cancellationToken);
}

public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? asyncCallback, object? asyncState)
{
ThrowIfDisposed();
ValidateBufferArguments(buffer, offset, count);
return _buffer.BeginWrite(buffer, offset, count, asyncCallback, asyncState);
}

public override void EndWrite(IAsyncResult asyncResult)
{
ThrowIfDisposed();
_buffer.EndWrite(asyncResult);
}

public ArraySegment<byte> GetBuffer()
{
ArraySegment<byte> bytes;

bool success = _buffer.TryGetBuffer(out bytes);
Debug.Assert(success); // Buffer should always be visible since default MemoryStream constructor was used.

return bytes;
}

// We need this to dispose the MemoryStream.
public MemoryStream GetMemoryStream()
{
return _buffer;
}

protected override void Dispose(bool disposing)
{
if (disposing && !_disposed)
{
_disposed = true;
}
base.Dispose(disposing);
}

private void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
}
}
Loading
Loading