public async Task GetAsync_DisposeBeforeReadingToEnd_DrainsRequestsAndReusesConnection(LoopbackServer.ContentMode mode) { const string simpleContent = "Hello world!"; await LoopbackServer.CreateClientAndServerAsync( async url => { using (HttpClient client = CreateHttpClient()) { HttpResponseMessage response1 = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); ValidateResponseHeaders(response1, simpleContent.Length, mode); // Read up to exactly 1 byte before the end of the response Stream responseStream = await response1.Content.ReadAsStreamAsync(TestAsync); byte[] bytes = await ReadToByteCount(responseStream, simpleContent.Length - 1); Assert.Equal(simpleContent.Substring(0, simpleContent.Length - 1), Encoding.ASCII.GetString(bytes)); // Introduce a short delay to try to ensure that when we dispose the response, // all response data is available and we can drain synchronously and reuse the connection. await Task.Delay(100); response1.Dispose(); // Issue another request. We'll confirm that it comes on the same connection. HttpResponseMessage response2 = await client.GetAsync(url); ValidateResponseHeaders(response2, simpleContent.Length, mode); Assert.Equal(simpleContent, await response2.Content.ReadAsStringAsync()); } }, async server => { await server.AcceptConnectionAsync(async connection => { server.ListenSocket.Close(); // Shut down the listen socket so attempts at additional connections would fail on the client string response = LoopbackServer.GetContentModeResponse(mode, simpleContent); await connection.ReadRequestHeaderAndSendCustomResponseAsync(response); await connection.ReadRequestHeaderAndSendCustomResponseAsync(response); }); }); }
public async Task GetAsyncWithMaxConnections_DisposeBeforeReadingToEnd_DrainsRequestsAndReusesConnection(int totalSize, int readSize, LoopbackServer.ContentMode mode) { await LoopbackServer.CreateClientAndServerAsync( async url => { HttpClientHandler handler = CreateHttpClientHandler(); SetResponseDrainTimeout(handler, Timeout.InfiniteTimeSpan); // Set MaxConnectionsPerServer to 1. This will ensure we will wait for the previous request to drain (or fail to) handler.MaxConnectionsPerServer = 1; using (HttpClient client = CreateHttpClient(handler)) { HttpResponseMessage response1 = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); ValidateResponseHeaders(response1, totalSize, mode); // Read part but not all of response Stream responseStream = await response1.Content.ReadAsStreamAsync(TestAsync); await ReadToByteCount(responseStream, readSize); response1.Dispose(); // Issue another request. We'll confirm that it comes on the same connection. HttpResponseMessage response2 = await client.GetAsync(url); ValidateResponseHeaders(response2, totalSize, mode); Assert.Equal(totalSize, (await response2.Content.ReadAsStringAsync()).Length); } }, async server => { string content = new string('a', totalSize); string response = LoopbackServer.GetContentModeResponse(mode, content); await server.AcceptConnectionAsync(async connection => { // Process the first request, with some introduced delays in the response to // stress the draining. await connection.ReadRequestHeaderAsync().ConfigureAwait(false); foreach (char c in response) { await connection.Writer.WriteAsync(c); } // Process the second request. await connection.ReadRequestHeaderAndSendCustomResponseAsync(response); }); }); }
protected static void ValidateResponseHeaders(HttpResponseMessage response, int contentLength, LoopbackServer.ContentMode mode) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); switch (mode) { case LoopbackServer.ContentMode.ContentLength: Assert.Equal(contentLength, response.Content.Headers.ContentLength); break; case LoopbackServer.ContentMode.SingleChunk: case LoopbackServer.ContentMode.BytePerChunk: Assert.True(response.Headers.TransferEncodingChunked); break; } }
public async Task GetAsyncWithMaxConnections_DisposeBeforeReadingToEnd_KillsConnection(int totalSize, int readSize, LoopbackServer.ContentMode mode) { await LoopbackServer.CreateClientAndServerAsync( async url => { HttpClientHandler handler = CreateHttpClientHandler(); SetResponseDrainTimeout(handler, Timeout.InfiniteTimeSpan); // Set MaxConnectionsPerServer to 1. This will ensure we will wait for the previous request to drain (or fail to) handler.MaxConnectionsPerServer = 1; using (HttpClient client = CreateHttpClient(handler)) { HttpResponseMessage response1 = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); ValidateResponseHeaders(response1, totalSize, mode); // Read part but not all of response Stream responseStream = await response1.Content.ReadAsStreamAsync(TestAsync); await ReadToByteCount(responseStream, readSize); response1.Dispose(); // Issue another request. We'll confirm that it comes on a new connection. HttpResponseMessage response2 = await client.GetAsync(url); ValidateResponseHeaders(response2, totalSize, mode); Assert.Equal(totalSize, (await response2.Content.ReadAsStringAsync()).Length); } }, async server => { string content = new string('a', totalSize); await server.AcceptConnectionAsync(async connection => { await connection.ReadRequestHeaderAsync(); try { await connection.Writer.WriteAsync(LoopbackServer.GetContentModeResponse(mode, content, connectionClose: false)); } catch (Exception) { } // Eat errors from client disconnect. await server.AcceptConnectionSendCustomResponseAndCloseAsync(LoopbackServer.GetContentModeResponse(mode, content, connectionClose: true)); }); }); }
public async Task GetAsyncLargeRequestWithMaxConnections_DisposeBeforeReadingToEnd_DrainsRequestsAndReusesConnection(int totalSize, int readSize, LoopbackServer.ContentMode mode) { await GetAsyncWithMaxConnections_DisposeBeforeReadingToEnd_DrainsRequestsAndReusesConnection(totalSize, readSize, mode); return; }
public async Task ResponseHeadersRead_SynchronizationContextNotUsedByHandler(bool responseHeadersRead, LoopbackServer.ContentMode contentMode) { if (IsWinHttpHandler && (PlatformDetection.IsWindows7 || PlatformDetection.IsWindows8x)) { // [ActiveIssue("https://github.com/dotnet/runtime/issues/54034")] return; } await Task.Run(async delegate // escape xunit's sync ctx { await LoopbackServer.CreateClientAndServerAsync(uri => { return(Task.Run(() => // allow client and server to run concurrently even though this is all synchronous/blocking { var sc = new TrackingSynchronizationContext(); SynchronizationContext.SetSynchronizationContext(sc); using (HttpClient client = CreateHttpClient()) { if (responseHeadersRead) { using (HttpResponseMessage resp = client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult()) using (Stream respStream = resp.Content.ReadAsStreamAsync().GetAwaiter().GetResult()) { byte[] buffer = new byte[0x1000]; while (respStream.ReadAsync(buffer, 0, buffer.Length).GetAwaiter().GetResult() > 0) { ; } } } else { client.GetStringAsync(uri).GetAwaiter().GetResult(); } } Assert.True(sc.CallStacks.Count == 0, "Sync Ctx used: " + string.Join(Environment.NewLine + Environment.NewLine, sc.CallStacks)); })); }, async server => { await server.AcceptConnectionAsync(async connection => { await connection.ReadRequestHeaderAsync(); await connection.WriteStringAsync( LoopbackServer.GetContentModeResponse( contentMode, string.Concat(Enumerable.Repeat('s', 10_000)), connectionClose: true)); }); }, new LoopbackServer.Options { StreamWrapper = s => new DribbleStream(s) }); });
public async Task GetAsyncLargeRequestWithMaxConnections_DisposeBeforeReadingToEnd_DrainsRequestsAndReusesConnection(int totalSize, int readSize, LoopbackServer.ContentMode mode) { // SocketsHttpHandler will reliably drain up to 1MB; other handlers don't. if (!UseSocketsHttpHandler) { return; } await GetAsyncWithMaxConnections_DisposeBeforeReadingToEnd_DrainsRequestsAndReusesConnection(totalSize, readSize, mode); return; }