HTTP3: Re-enable cookie and cancellation tests (#54727)
authorGeoff Kizer <geoffrek@microsoft.com>
Sat, 26 Jun 2021 01:45:42 +0000 (18:45 -0700)
committerGitHub <noreply@github.com>
Sat, 26 Jun 2021 01:45:42 +0000 (18:45 -0700)
* fix an issue with GOAWAY handling and enable cookie tests

* disable ConnectTimeout test for HTTP3

* fix Expect 100 continue handling in HTTP3

* add and use GenericLoopbackServer.SendPartialResponseHeadersAsync

* enable cancellation tests for HTTP3

* disable failing interop tests

Co-authored-by: Geoffrey Kizer <geoffrek@windows.microsoft.com>
14 files changed:
src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs
src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs
src/libraries/Common/tests/System/Net/Http/Http3LoopbackConnection.cs
src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs
src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Cancellation.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs
src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs
src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Cancellation.cs
src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs

index c145b6a..ecff49e 100644 (file)
@@ -120,10 +120,13 @@ namespace System.Net.Test.Common
         /// <summary>Read complete request body if not done by ReadRequestData.</summary>
         public abstract Task<Byte[]> ReadRequestBodyAsync();
 
-        /// <summary>Sends Response back with provided statusCode, headers and content. Can be called multiple times on same response if isFinal was set to false before.</summary>
-        public abstract Task SendResponseAsync(HttpStatusCode? statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "", bool isFinal = true, int requestId = 0);
+        /// <summary>Sends Response back with provided statusCode, headers and content.
+        /// If isFinal is false, the body is not completed and you can call SendResponseBodyAsync to send more.</summary>
+        public abstract Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "", bool isFinal = true, int requestId = 0);
         /// <summary>Sends response headers.</summary>
         public abstract Task SendResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, int requestId = 0);
+        /// <summary>Sends valid but incomplete headers. Once called, there is no way to continue the response past this point.</summary>
+        public abstract Task SendPartialResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, int requestId = 0);
         /// <summary>Sends Response body after SendResponse was called with isFinal: false.</summary>
         public abstract Task SendResponseBodyAsync(byte[] content, bool isFinal = true, int requestId = 0);
 
@@ -184,7 +187,7 @@ namespace System.Net.Test.Common
         public string Path;
         public Version Version;
         public List<HttpHeaderData> Headers { get; }
-        public int RequestId;       // Generic request ID. Currently only used for HTTP/2 to hold StreamId.
+        public int RequestId;       // HTTP/2 StreamId.
 
         public HttpRequestData()
         {
@@ -246,5 +249,7 @@ namespace System.Net.Test.Common
         {
             return Headers.Where(h => h.Name.Equals(headerName, StringComparison.OrdinalIgnoreCase)).Count();
         }
+
+        public override string ToString() => $"{Method} {Path} HTTP/{Version}\r\n{string.Join("\r\n", Headers)}\r\n\r\n";
     }
 }
index 5dcbc2d..97787e6 100644 (file)
@@ -845,11 +845,8 @@ namespace System.Net.Test.Common
             return ReadBodyAsync();
         }
 
-        public override async Task SendResponseAsync(HttpStatusCode? statusCode = null, IList<HttpHeaderData> headers = null, string body = null, bool isFinal = true, int requestId = 0)
+        public override async Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "", bool isFinal = true, int requestId = 0)
         {
-            // TODO: Header continuation support.
-            Assert.NotNull(statusCode);
-
             if (headers != null)
             {
                 bool hasDate = false;
@@ -886,16 +883,15 @@ namespace System.Net.Test.Common
             }
 
             int streamId = requestId == 0 ? _lastStreamId : requestId;
-            bool endHeaders = body != null || isFinal;
 
-            if (string.IsNullOrEmpty(body))
+            if (string.IsNullOrEmpty(content))
             {
-                await SendResponseHeadersAsync(streamId, endStream: isFinal, (HttpStatusCode)statusCode, endHeaders: endHeaders, headers: headers);
+                await SendResponseHeadersAsync(streamId, endStream: isFinal, (HttpStatusCode)statusCode, endHeaders: true, headers: headers);
             }
             else
             {
-                await SendResponseHeadersAsync(streamId, endStream: false, (HttpStatusCode)statusCode, endHeaders: endHeaders, headers: headers);
-                await SendResponseBodyAsync(body, isFinal: isFinal, requestId: streamId);
+                await SendResponseHeadersAsync(streamId, endStream: false, (HttpStatusCode)statusCode, endHeaders: true, headers: headers);
+                await SendResponseBodyAsync(content, isFinal: isFinal, requestId: streamId);
             }
         }
 
@@ -905,10 +901,16 @@ namespace System.Net.Test.Common
             return SendResponseHeadersAsync(streamId, endStream: false, statusCode, isTrailingHeader: false, endHeaders: true, headers);
         }
 
-        public override Task SendResponseBodyAsync(byte[] body, bool isFinal = true, int requestId = 0)
+        public override Task SendPartialResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, int requestId = 0)
+        {
+            int streamId = requestId == 0 ? _lastStreamId : requestId;
+            return SendResponseHeadersAsync(streamId, endStream: false, statusCode, isTrailingHeader: false, endHeaders: false, headers);
+        }
+
+        public override Task SendResponseBodyAsync(byte[] content, bool isFinal = true, int requestId = 0)
         {
             int streamId = requestId == 0 ? _lastStreamId : requestId;
-            return SendResponseBodyAsync(streamId, body, isFinal);
+            return SendResponseBodyAsync(streamId, content, isFinal);
         }
 
         public override async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "")
index 4c74a50..555a548 100644 (file)
@@ -33,6 +33,7 @@ namespace System.Net.Test.Common
 
         private readonly QuicConnection _connection;
         private readonly Dictionary<int, Http3LoopbackStream> _openStreams = new Dictionary<int, Http3LoopbackStream>();
+        private Http3LoopbackStream _controlStream;     // Our outbound control stream
         private Http3LoopbackStream _currentStream;
         private bool _closed;
 
@@ -138,6 +139,13 @@ namespace System.Net.Test.Common
             }
         }
 
+        public async Task EstablishControlStreamAsync()
+        {
+            _controlStream = OpenUnidirectionalStream();
+            await _controlStream.SendUnidirectionalStreamTypeAsync(Http3LoopbackStream.ControlStream);
+            await _controlStream.SendSettingsFrameAsync();
+       }
+
         public override async Task<byte[]> ReadRequestBodyAsync()
         {
             return await _currentStream.ReadRequestBodyAsync().ConfigureAwait(false);
@@ -149,7 +157,7 @@ namespace System.Net.Test.Common
             return await stream.ReadRequestDataAsync(readBody).ConfigureAwait(false);
         }
 
-        public override Task SendResponseAsync(HttpStatusCode? statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "", bool isFinal = true, int requestId = 0)
+        public override Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "", bool isFinal = true, int requestId = 0)
         {
             return GetOpenRequest(requestId).SendResponseAsync(statusCode, headers, content, isFinal);
         }
@@ -164,12 +172,25 @@ namespace System.Net.Test.Common
             return GetOpenRequest(requestId).SendResponseHeadersAsync(statusCode, headers);
         }
 
+        public override Task SendPartialResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, int requestId = 0)
+        {
+            return GetOpenRequest(requestId).SendPartialResponseHeadersAsync(statusCode, headers);
+        }
+
         public override async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "")
         {
             Http3LoopbackStream stream = await AcceptRequestStreamAsync().ConfigureAwait(false);
-            HttpRequestData request = await stream.HandleRequestAsync(statusCode, headers, content);
+
+            HttpRequestData request = await stream.ReadRequestDataAsync().ConfigureAwait(false);
+
+            // We are about to close the connection, after we send the response.
+            // So, send a GOAWAY frame now so the client won't inadvertantly try to reuse the connection.
+            await _controlStream.SendGoAwayFrameAsync(stream.StreamId + 4);
+
+            await stream.SendResponseAsync(statusCode, headers, content).ConfigureAwait(false);
 
             // closing the connection here causes bytes written to streams to go missing.
+            // Regardless, we told the client we are closing so it shouldn't matter -- they should not use this connection anymore.
             //await CloseAsync(H3_NO_ERROR).ConfigureAwait(false);
 
             return request;
index b84393a..6e51191 100644 (file)
@@ -57,7 +57,10 @@ namespace System.Net.Test.Common
         public override async Task<GenericLoopbackConnection> EstablishGenericConnectionAsync()
         {
             QuicConnection con = await _listener.AcceptConnectionAsync().ConfigureAwait(false);
-            return new Http3LoopbackConnection(con);
+            Http3LoopbackConnection connection = new Http3LoopbackConnection(con);
+
+            await connection.EstablishControlStreamAsync();
+            return connection;
         }
 
         public override async Task AcceptConnectionAsync(Func<GenericLoopbackConnection, Task> funcAsync)
index faccfab..8bccc13 100644 (file)
@@ -23,6 +23,7 @@ namespace System.Net.Test.Common
         private const long DataFrame = 0x0;
         private const long HeadersFrame = 0x1;
         private const long SettingsFrame = 0x4;
+        private const long GoAwayFrame = 0x7;
 
         public const long ControlStream = 0x0;
         public const long PushStream = 0x1;
@@ -43,6 +44,9 @@ namespace System.Net.Test.Common
         {
             _stream.Dispose();
         }
+
+        public long StreamId => _stream.StreamId;
+
         public async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "")
         {
             HttpRequestData request = await ReadRequestDataAsync().ConfigureAwait(false);
@@ -57,8 +61,10 @@ namespace System.Net.Test.Common
             await _stream.WriteAsync(buffer.AsMemory(0, bytesWritten)).ConfigureAwait(false);
         }
 
-        public async Task SendSettingsFrameAsync(ICollection<(long settingId, long settingValue)> settings)
+        public async Task SendSettingsFrameAsync(ICollection<(long settingId, long settingValue)> settings = null)
         {
+            settings ??= Array.Empty<(long settingId, long settingValue)>();
+
             var buffer = new byte[settings.Count * MaximumVarIntBytes * 2];
 
             int bytesWritten = 0;
@@ -72,7 +78,7 @@ namespace System.Net.Test.Common
             await SendFrameAsync(SettingsFrame, buffer.AsMemory(0, bytesWritten)).ConfigureAwait(false);
         }
 
-        public async Task SendHeadersFrameAsync(IEnumerable<HttpHeaderData> headers)
+        private Memory<byte> ConstructHeadersPayload(IEnumerable<HttpHeaderData> headers)
         {
             int bufferLength = QPackTestEncoder.MaxPrefixLength;
 
@@ -95,7 +101,24 @@ namespace System.Net.Test.Common
                 bytesWritten += QPackTestEncoder.EncodeHeader(buffer.AsSpan(bytesWritten), header.Name, header.Value, header.ValueEncoding, header.HuffmanEncoded ? QPackFlags.HuffmanEncode : QPackFlags.None);
             }
 
-            await SendFrameAsync(HeadersFrame, buffer.AsMemory(0, bytesWritten)).ConfigureAwait(false);
+            return buffer.AsMemory(0, bytesWritten);
+        }
+
+        private async Task SendHeadersFrameAsync(IEnumerable<HttpHeaderData> headers)
+        {
+            await SendFrameAsync(HeadersFrame, ConstructHeadersPayload(headers)).ConfigureAwait(false);
+        }
+
+        private async Task SendPartialHeadersFrameAsync(IEnumerable<HttpHeaderData> headers)
+        {
+            Memory<byte> payload = ConstructHeadersPayload(headers);
+
+            await SendFrameHeaderAsync(HeadersFrame, payload.Length);
+
+            // Slice off final byte so the payload is not complete
+            payload = payload.Slice(0, payload.Length - 1);
+
+            await _stream.WriteAsync(payload).ConfigureAwait(false);
         }
 
         public async Task SendDataFrameAsync(ReadOnlyMemory<byte> data)
@@ -103,16 +126,31 @@ namespace System.Net.Test.Common
             await SendFrameAsync(DataFrame, data).ConfigureAwait(false);
         }
 
-        public async Task SendFrameAsync(long frameType, ReadOnlyMemory<byte> framePayload)
+        // Note that unlike HTTP2, the stream ID here indicates the *first invalid* stream.
+        public async Task SendGoAwayFrameAsync(long firstInvalidStreamId)
+        {
+            var buffer = new byte[QPackTestEncoder.MaxVarIntLength];
+            int bytesWritten = 0;
+
+            bytesWritten += EncodeHttpInteger(firstInvalidStreamId, buffer);
+            await SendFrameAsync(GoAwayFrame, buffer.AsMemory(0, bytesWritten));
+        }
+
+        private async Task SendFrameHeaderAsync(long frameType, int payloadLength)
         {
             var buffer = new byte[MaximumVarIntBytes * 2];
 
             int bytesWritten = 0;
 
             bytesWritten += EncodeHttpInteger(frameType, buffer.AsSpan(bytesWritten));
-            bytesWritten += EncodeHttpInteger(framePayload.Length, buffer.AsSpan(bytesWritten));
+            bytesWritten += EncodeHttpInteger(payloadLength, buffer.AsSpan(bytesWritten));
 
             await _stream.WriteAsync(buffer.AsMemory(0, bytesWritten)).ConfigureAwait(false);
+        }
+
+        public async Task SendFrameAsync(long frameType, ReadOnlyMemory<byte> framePayload)
+        {
+            await SendFrameHeaderAsync(frameType, framePayload.Length).ConfigureAwait(false);
             await _stream.WriteAsync(framePayload).ConfigureAwait(false);
         }
 
@@ -189,7 +227,7 @@ namespace System.Net.Test.Common
             return requestData;
         }
 
-        public async Task SendResponseAsync(HttpStatusCode? statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "", bool isFinal = true)
+        public async Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "", bool isFinal = true)
         {
             IEnumerable<HttpHeaderData> newHeaders = headers ?? Enumerable.Empty<HttpHeaderData>();
 
@@ -202,21 +240,30 @@ namespace System.Net.Test.Common
             await SendResponseBodyAsync(Encoding.UTF8.GetBytes(content ?? ""), isFinal).ConfigureAwait(false);
         }
 
-        public async Task SendResponseHeadersAsync(HttpStatusCode? statusCode = HttpStatusCode.OK, IEnumerable<HttpHeaderData> headers = null)
+        private IEnumerable<HttpHeaderData> PrepareHeaders(HttpStatusCode statusCode, IEnumerable<HttpHeaderData> headers)
         {
             headers ??= Enumerable.Empty<HttpHeaderData>();
 
             // Some tests use Content-Length with a null value to indicate Content-Length should not be set.
             headers = headers.Where(x => x.Name != "Content-Length" || x.Value != null);
 
-            if (statusCode != null)
-            {
-                headers = headers.Prepend(new HttpHeaderData(":status", ((int)statusCode).ToString(CultureInfo.InvariantCulture)));
-            }
+            headers = headers.Prepend(new HttpHeaderData(":status", ((int)statusCode).ToString(CultureInfo.InvariantCulture)));
+
+            return headers;
+        }
 
+        public async Task SendResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IEnumerable<HttpHeaderData> headers = null)
+        {
+            headers = PrepareHeaders(statusCode, headers);
             await SendHeadersFrameAsync(headers).ConfigureAwait(false);
         }
 
+        public async Task SendPartialResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IEnumerable<HttpHeaderData> headers = null)
+        {
+            headers = PrepareHeaders(statusCode, headers);
+            await SendPartialHeadersFrameAsync(headers).ConfigureAwait(false);
+        }
+
         public async Task SendResponseBodyAsync(byte[] content, bool isFinal = true)
         {
             if (content?.Length != 0)
index 410d2a6..d4c4a57 100644 (file)
@@ -109,7 +109,7 @@ namespace System.Net.Http.Functional.Tests
                     Task serverTask = server.AcceptConnectionAsync(async connection =>
                     {
                         await connection.ReadRequestDataAsync();
-                        await connection.SendResponseAsync(HttpStatusCode.OK, content: null, isFinal: false);
+                        await connection.SendPartialResponseHeadersAsync(HttpStatusCode.OK);
 
                         partialResponseHeadersSent.TrySetResult(true);
                         await clientFinished.Task;
index 912f356..d9958e9 100644 (file)
@@ -1226,7 +1226,7 @@ namespace System.Net.Http.Functional.Tests
                         Task serverTask2 = server2.AcceptConnectionAsync(async connection2 =>
                         {
                             await connection2.ReadRequestDataAsync();
-                            await connection2.SendResponseAsync(HttpStatusCode.OK, content: null, isFinal : false);
+                            await connection2.SendPartialResponseHeadersAsync(HttpStatusCode.OK);
                             await unblockServers.Task;
                         });
 
index 5685e79..7594b49 100644 (file)
@@ -866,7 +866,7 @@ namespace System.Net.Test.Common
                 return buffer;
             }
 
-            public override async Task SendResponseAsync(HttpStatusCode? statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = null, bool isFinal = true, int requestId = 0)
+            public override async Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "", bool isFinal = true, int requestId = 0)
             {
                 MemoryStream headerBytes = new MemoryStream();
                 int contentLength = -1;
@@ -909,25 +909,18 @@ namespace System.Net.Test.Common
                     headerBytes.Write(corsBytes, 0, corsBytes.Length);
                 }
 
-                bool endHeaders = content != null || isFinal;
-                if (statusCode != null)
-                {
-                    byte[] temp = headerBytes.ToArray();
+                byte[] temp = headerBytes.ToArray();
 
-                    headerBytes.SetLength(0);
+                headerBytes.SetLength(0);
 
-                    byte[] headerStartBytes = Encoding.ASCII.GetBytes(
-                        $"HTTP/1.1 {(int)statusCode} {GetStatusDescription((HttpStatusCode)statusCode)}\r\n" +
-                        (!hasContentLength && !isChunked && content != null ? $"Content-length: {content.Length}\r\n" : ""));
+                byte[] headerStartBytes = Encoding.ASCII.GetBytes(
+                    $"HTTP/1.1 {(int)statusCode} {GetStatusDescription(statusCode)}\r\n" +
+                    (!hasContentLength && !isChunked && content != null ? $"Content-length: {content.Length}\r\n" : ""));
 
-                    headerBytes.Write(headerStartBytes, 0, headerStartBytes.Length);
-                    headerBytes.Write(temp, 0, temp.Length);
+                headerBytes.Write(headerStartBytes, 0, headerStartBytes.Length);
+                headerBytes.Write(temp, 0, temp.Length);
 
-                    if (endHeaders)
-                    {
-                        headerBytes.Write(s_newLineBytes, 0, s_newLineBytes.Length);
-                    }
-                }
+                headerBytes.Write(s_newLineBytes, 0, s_newLineBytes.Length);
 
                 headerBytes.Position = 0;
                 await headerBytes.CopyToAsync(_stream).ConfigureAwait(false);
@@ -938,7 +931,7 @@ namespace System.Net.Test.Common
                 }
             }
 
-            public override async Task SendResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, int requestId = 0)
+            private string GetResponseHeaderString(HttpStatusCode statusCode, IList<HttpHeaderData> headers)
             {
                 string headerString = null;
 
@@ -953,12 +946,28 @@ namespace System.Net.Test.Common
 
                 headerString = GetHttpResponseHeaders(statusCode, headerString, 0, connectionClose: true);
 
+                return headerString;
+            }
+
+            public override async Task SendResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, int requestId = 0)
+            {
+                string headerString = GetResponseHeaderString(statusCode, headers);
+                await SendResponseAsync(headerString).ConfigureAwait(false);
+            }
+
+            public override async Task SendPartialResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, int requestId = 0)
+            {
+                string headerString = GetResponseHeaderString(statusCode, headers);
+
+                // Lop off the final \r\n so the headers are not complete.
+                headerString = headerString.Substring(0, headerString.Length - 2);
+
                 await SendResponseAsync(headerString).ConfigureAwait(false);
             }
 
-            public override async Task SendResponseBodyAsync(byte[] body, bool isFinal = true, int requestId = 0)
+            public override async Task SendResponseBodyAsync(byte[] content, bool isFinal = true, int requestId = 0)
             {
-                await SendResponseAsync(body).ConfigureAwait(false);
+                await SendResponseAsync(content).ConfigureAwait(false);
             }
 
             public async Task<HttpRequestData> HandleCORSPreFlight(HttpRequestData requestData)
index c091127..ce4f3e7 100644 (file)
@@ -568,7 +568,7 @@ namespace System.Net.Http
                     switch (frameType)
                     {
                         case Http3FrameType.GoAway:
-                            await ProcessGoAwayFameAsync(payloadLength).ConfigureAwait(false);
+                            await ProcessGoAwayFrameAsync(payloadLength).ConfigureAwait(false);
                             break;
                         case Http3FrameType.Settings:
                             // If an endpoint receives a second SETTINGS frame on the control stream, the endpoint MUST respond with a connection error of type H3_FRAME_UNEXPECTED.
@@ -685,12 +685,12 @@ namespace System.Net.Http
                 }
             }
 
-            async ValueTask ProcessGoAwayFameAsync(long goawayPayloadLength)
+            async ValueTask ProcessGoAwayFrameAsync(long goawayPayloadLength)
             {
                 long lastStreamId;
                 int bytesRead;
 
-                while (!VariableLengthIntegerHelper.TryRead(buffer.AvailableSpan, out lastStreamId, out bytesRead))
+                while (!VariableLengthIntegerHelper.TryRead(buffer.ActiveSpan, out lastStreamId, out bytesRead))
                 {
                     buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength);
                     bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false);
index 3d469f0..4b00bb3 100644 (file)
@@ -55,6 +55,9 @@ namespace System.Net.Http
         // Keep track of how much is remaining in that frame.
         private long _requestContentLengthRemaining;
 
+        // For the precomputed length case, we need to add the DATA framing for the first write only.
+        private bool _singleDataFrameWritten;
+
         public long StreamId
         {
             get => Volatile.Read(ref _streamId);
@@ -393,9 +396,9 @@ namespace System.Net.Http
                 }
                 _requestContentLengthRemaining -= buffer.Length;
 
-                if (_sendBuffer.ActiveLength != 0)
+                if (!_singleDataFrameWritten)
                 {
-                    // We haven't sent out headers yet, so write them together with the user's content buffer.
+                    // Note we may not have sent headers yet; if so, _sendBuffer.ActiveLength will be > 0, and we will write them in a single write.
 
                     // Because we have a Content-Length, we can write it in a single DATA frame.
                     BufferFrameEnvelope(Http3FrameType.Data, remaining);
@@ -405,10 +408,12 @@ namespace System.Net.Http
                     await _stream.WriteAsync(_gatheredSendBuffer, cancellationToken).ConfigureAwait(false);
 
                     _sendBuffer.Discard(_sendBuffer.ActiveLength);
+
+                    _singleDataFrameWritten = true;
                 }
                 else
                 {
-                    // Headers already sent, send just the content buffer directly.
+                    // DATA frame already sent, send just the content buffer directly.
                     await _stream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
                 }
             }
index 668d194..b24c703 100644 (file)
@@ -206,7 +206,7 @@ namespace System.Net.Http.Functional.Tests
                 HttpRequestData requestData = await connection.ReadRequestDataAsync();                
                 string requestContent = requestData.Body is null ? (string)null : Encoding.ASCII.GetString(requestData.Body);
                 Assert.Equal(clientContent, requestContent);
-                await connection.SendResponseAsync(HttpStatusCode.OK, body: serverContent);
+                await connection.SendResponseAsync(HttpStatusCode.OK, content: serverContent);
             }, new Http2Options() { UseSsl = false });
         }
 
index 6a41bfb..d4d806f 100644 (file)
@@ -317,8 +317,14 @@ namespace System.Net.Http.Functional.Tests
         [OuterLoop]
         [ConditionalTheory(nameof(IsMsQuicSupported))]
         [MemberData(nameof(InteropUris))]
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/54726")]
         public async Task Public_Interop_ExactVersion_Success(string uri)
         {
+            if (UseQuicImplementationProvider == QuicImplementationProviders.Mock)
+            {
+                return;
+            }
+
             using HttpClient client = CreateHttpClient();
             using HttpRequestMessage request = new HttpRequestMessage
             {
@@ -336,8 +342,14 @@ namespace System.Net.Http.Functional.Tests
         [OuterLoop]
         [ConditionalTheory(nameof(IsMsQuicSupported))]
         [MemberData(nameof(InteropUris))]
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/54726")]
         public async Task Public_Interop_Upgrade_Success(string uri)
         {
+            if (UseQuicImplementationProvider == QuicImplementationProviders.Mock)
+            {
+                return;
+            }
+
             using HttpClient client = CreateHttpClient();
 
             // First request uses HTTP/1 or HTTP/2 and receives an Alt-Svc either by header or (with HTTP/2) by frame.
index 53fcb8f..bc4213b 100644 (file)
@@ -48,6 +48,12 @@ namespace System.Net.Http.Functional.Tests
         [Fact]
         public async Task ConnectTimeout_ConnectCallbackTimesOut_Throws()
         {
+            if (UseVersion == HttpVersion.Version30)
+            {
+                // HTTP3 does not support ConnectCallback
+                return;
+            }
+
             await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
             {
                 using (var handler = CreateHttpClientHandler())
index 4f18763..7d00616 100644 (file)
@@ -3098,8 +3098,6 @@ namespace System.Net.Http.Functional.Tests
         protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.Mock;
     }
 
-    // TODO: Many Cookie tests are failing for HTTP3.
-    [ActiveIssue("https://github.com/dotnet/runtime/issues/53093")]
     [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsMsQuicSupported))]
     public sealed class SocketsHttpHandlerTest_Cookies_Http3_MsQuic : HttpClientHandlerTest_Cookies
     {
@@ -3108,7 +3106,6 @@ namespace System.Net.Http.Functional.Tests
         protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.MsQuic;
     }
 
-    [ActiveIssue("https://github.com/dotnet/runtime/issues/53093")]
     [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsMockQuicSupported))]
     public sealed class SocketsHttpHandlerTest_Cookies_Http3_Mock : HttpClientHandlerTest_Cookies
     {
@@ -3133,8 +3130,6 @@ namespace System.Net.Http.Functional.Tests
         protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.Mock;
     }
 
-    // TODO: Many cancellation tests are failing for HTTP3.
-    [ActiveIssue("https://github.com/dotnet/runtime/issues/53093")]
     [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsMsQuicSupported))]
     public sealed class SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http3_MsQuic : SocketsHttpHandler_Cancellation_Test
     {
@@ -3143,7 +3138,6 @@ namespace System.Net.Http.Functional.Tests
         protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.MsQuic;
     }
 
-    [ActiveIssue("https://github.com/dotnet/runtime/issues/53093")]
     [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsMockQuicSupported))]
     public sealed class SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http3_Mock : SocketsHttpHandler_Cancellation_Test
     {