Skip to content
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 @@ -48,6 +48,7 @@
<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\Cache\HttpCacheAgeControl.cs" />
<Compile Include="System\Net\Cache\HttpRequestCacheLevel.cs" />
<Compile Include="System\Net\Cache\HttpRequestCachePolicy.cs" />
Expand Down Expand Up @@ -84,6 +85,10 @@
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// 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.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace System.Net
{
internal sealed class HttpClientContentStream : Stream
{
private readonly StreamBuffer _buffer;
private readonly StrongBox<int> _refCount;
private bool _disposed;

internal HttpClientContentStream(StreamBuffer buffer, StrongBox<int> refCount)
{
_buffer = buffer;
_refCount = refCount;
}

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

_buffer.AbortRead();

if (Interlocked.Decrement(ref _refCount.Value) == 0)
{
_buffer.Dispose();
}
}

base.Dispose(disposing);
}

public override bool CanRead => !_disposed;
public override bool CanWrite => false;
public override bool CanSeek => false;

public override void Flush() => ThrowIfDisposed();
public override Task FlushAsync(CancellationToken cancellationToken)
{
ThrowIfDisposed();

if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}

return Task.CompletedTask;
}

public override int Read(byte[] buffer, int offset, int count)
{
ValidateBufferArguments(buffer, offset, count);
ThrowIfDisposed();

return _buffer.Read(new Span<byte>(buffer, offset, count));
}

public override int ReadByte()
{
ThrowIfDisposed();

byte b = 0;
int n = _buffer.Read(MemoryMarshal.CreateSpan(ref b, 1));
return n != 0 ? b : -1;
}

public override int Read(Span<byte> buffer)
{
ThrowIfDisposed();

return _buffer.Read(buffer);
}

public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) =>
TaskToAsyncResult.Begin(ReadAsync(buffer, offset, count), callback, state);

public override int EndRead(IAsyncResult asyncResult)
{
ThrowIfDisposed();

return TaskToAsyncResult.End<int>(asyncResult);
}

public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ValidateBufferArguments(buffer, offset, count);
ThrowIfDisposed();

return _buffer.ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
}

public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
ThrowIfDisposed();

return _buffer.ReadAsync(buffer, cancellationToken);
}

public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();

public override void WriteByte(byte value) => throw new NotSupportedException();

public override void Write(ReadOnlySpan<byte> buffer) => throw new NotSupportedException();

public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw new NotSupportedException();

public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default) => throw new NotSupportedException();

public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => throw new NotSupportedException();

public override void EndWrite(IAsyncResult asyncResult) => throw new NotSupportedException();

public override long Length => throw new NotSupportedException();
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();

private void ThrowIfDisposed()
{
if (_disposed)
ThrowDisposedException();

[StackTraceHidden]
static void ThrowDisposedException() => throw new ObjectDisposedException(nameof(HttpClientContentStream));
}

[DoesNotReturn]
private static void ThrowNotSupportedException() =>
throw new NotSupportedException();
}
}
88 changes: 52 additions & 36 deletions src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
Expand All @@ -11,6 +12,7 @@
using System.Net.Http.Headers;
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
Expand Down Expand Up @@ -990,17 +992,17 @@ public override void Abort()
{
_responseCallback(_responseOperation.Task);
}

// Cancel the underlying send operation.
Debug.Assert(_sendRequestCts != null);
_sendRequestCts.Cancel();
}
else if (_requestStreamOperation != null)
if (_requestStreamOperation != null)
{
if (_requestStreamOperation.TrySetCanceled() && _requestStreamCallback != null)
{
_requestStreamCallback(_requestStreamOperation.Task);
}

// Cancel the underlying send operation.
Debug.Assert(_sendRequestCts != null);
_sendRequestCts.Cancel();
}
}

Expand Down Expand Up @@ -1028,8 +1030,7 @@ public override WebResponse GetResponse()
{
try
{
_sendRequestCts = new CancellationTokenSource();
return SendRequest(async: false).GetAwaiter().GetResult();
return HandleResponse(false).GetAwaiter().GetResult();
}
catch (Exception ex)
{
Expand All @@ -1039,10 +1040,10 @@ public override WebResponse GetResponse()

public override Stream GetRequestStream()
{
return InternalGetRequestStream().Result;
return InternalGetRequestStream(false).Result;
}

private Task<Stream> InternalGetRequestStream()
private Task<Stream> InternalGetRequestStream(bool async)
{
CheckAbort();

Expand All @@ -1061,7 +1062,14 @@ private Task<Stream> InternalGetRequestStream()
throw new InvalidOperationException(SR.net_reqsubmitted);
}

_requestStream = new RequestStream();
// Let's create stream buffer for transferring data from RequestStream to the StreamContent.
StrongBox<int> box = new StrongBox<int>();
StreamBuffer streamBuffer = new StreamBuffer();
//s_cachedHttpClient = GetCachedOrCreateHttpClient(async, out bool disposeRequired);
_sendRequestTask = SendRequest(async, new StreamContent(new HttpClientContentStream(streamBuffer, box)));

// If any parameter changed let's change the RequestStream.
_requestStream ??= new RequestStream(box, streamBuffer, AllowWriteStreamBuffering);

return Task.FromResult((Stream)_requestStream);
}
Expand All @@ -1088,7 +1096,7 @@ public override IAsyncResult BeginGetRequestStream(AsyncCallback? callback, obje
}

_requestStreamCallback = callback;
_requestStreamOperation = InternalGetRequestStream().ToApm(callback, state);
_requestStreamOperation = InternalGetRequestStream(true).ToApm(callback, state);

return _requestStreamOperation.Task;
}
Expand Down Expand Up @@ -1120,7 +1128,7 @@ public override Stream EndGetRequestStream(IAsyncResult asyncResult)
return stream;
}

private async Task<WebResponse> SendRequest(bool async)
private Task<HttpResponseMessage> SendRequest(bool async, HttpContent? content = null)
{
if (RequestSubmitted)
{
Expand All @@ -1133,14 +1141,14 @@ private async Task<WebResponse> SendRequest(bool async)
HttpClient? client = null;
try
{
_sendRequestCts = new CancellationTokenSource();
client = GetCachedOrCreateHttpClient(async, out disposeRequired);
if (_requestStream != null)
if (content is not null)
{
ArraySegment<byte> bytes = _requestStream.GetBuffer();
request.Content = new ByteArrayContent(bytes.Array!, bytes.Offset, bytes.Count);
request.Content = content;
}

if (_hostUri != null)
if (_hostUri is not null)
{
request.Headers.Host = Host;
}
Expand Down Expand Up @@ -1179,36 +1187,45 @@ private async Task<WebResponse> SendRequest(bool async)
}

request.Version = ProtocolVersion;

HttpCompletionOption completionOption = _allowReadStreamBuffering ?
HttpCompletionOption.ResponseContentRead : HttpCompletionOption.ResponseHeadersRead;
_sendRequestTask = async ?
client.SendAsync(request, _allowReadStreamBuffering ? HttpCompletionOption.ResponseContentRead : HttpCompletionOption.ResponseHeadersRead, _sendRequestCts!.Token) :
Task.FromResult(client.Send(request, _allowReadStreamBuffering ? HttpCompletionOption.ResponseContentRead : HttpCompletionOption.ResponseHeadersRead, _sendRequestCts!.Token));

HttpResponseMessage responseMessage = await _sendRequestTask.ConfigureAwait(false);

HttpWebResponse response = new HttpWebResponse(responseMessage, _requestUri, _cookieContainer);
client.SendAsync(request, completionOption, _sendRequestCts!.Token) :
Task.Run(() => client.Send(request, completionOption, _sendRequestCts!.Token));

int maxSuccessStatusCode = AllowAutoRedirect ? 299 : 399;
if ((int)response.StatusCode > maxSuccessStatusCode || (int)response.StatusCode < 200)
{
throw new WebException(
SR.Format(SR.net_servererror, (int)response.StatusCode, response.StatusDescription),
null,
WebExceptionStatus.ProtocolError,
response);
}

return response;
return _sendRequestTask!;
}
finally
{
if (disposeRequired)
{
client?.Dispose();
_requestStream = null;
}
}
}

private async Task<WebResponse> HandleResponse(bool async)
{
_sendRequestTask ??= SendRequest(async);

HttpResponseMessage responseMessage = await _sendRequestTask!.ConfigureAwait(false);

HttpWebResponse response = new HttpWebResponse(responseMessage, _requestUri, _cookieContainer);

int maxSuccessStatusCode = AllowAutoRedirect ? 299 : 399;
if ((int)response.StatusCode > maxSuccessStatusCode || (int)response.StatusCode < 200)
{
throw new WebException(
SR.Format(SR.net_servererror, (int)response.StatusCode, response.StatusDescription),
null,
WebExceptionStatus.ProtocolError,
response);
}

return response;
}

private void AddCacheControlHeaders(HttpRequestMessage request)
{
RequestCachePolicy? policy = GetApplicableCachePolicy();
Expand Down Expand Up @@ -1330,9 +1347,8 @@ public override IAsyncResult BeginGetResponse(AsyncCallback? callback, object? s
throw new InvalidOperationException(SR.net_repcall);
}

_sendRequestCts = new CancellationTokenSource();
_responseCallback = callback;
_responseOperation = SendRequest(async: true).ToApm(callback, state);
_responseOperation = HandleResponse(true).ToApm(callback, state);

return _responseOperation.Task;
}
Expand Down
Loading