public async Task Expect100ContinueForBody() { using (var server = new TestServer(AppChunked)) { using (var connection = new TestConnection()) { await connection.Send( "POST / HTTP/1.1", "Expect: 100-continue", "Content-Length: 11", "Connection: close", "\r\n"); await connection.Receive("HTTP/1.1 100 Continue", "\r\n"); await connection.SendEnd("Hello World"); await connection.Receive( "HTTP/1.1 200 OK", "Content-Length: 11", "Connection: close", "", "Hello World"); } } }
public async Task ConnectionClosedIfExeptionThrownAfterZeroLengthWrite() { using (var server = new TestServer(async frame => { var response = frame.Get <IHttpResponseFeature>(); response.Headers.Clear(); await response.Body.WriteAsync(new byte[0], 0, 0); throw new Exception(); })) { using (var connection = new TestConnection()) { // SendEnd is not called, so it isn't the client closing the connection. await connection.Send( "GET / HTTP/1.1", "", ""); // Headers are sent before connection is closed, but chunked body terminator isn't sent await connection.ReceiveEnd( "HTTP/1.1 200 OK", "Transfer-Encoding: chunked", "", ""); } } }
public async Task ConnectionClosedIfExeptionThrownAfterWrite() { using (var server = new TestServer(async frame => { var response = frame.Get <IHttpResponseFeature>(); response.Headers.Clear(); await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World!"), 0, 12); throw new Exception(); })) { using (var connection = new TestConnection()) { // SendEnd is not called, so it isn't the client closing the connection. // client closing the connection. await connection.Send( "GET / HTTP/1.1", "", ""); await connection.ReceiveEnd( "HTTP/1.1 200 OK", "Transfer-Encoding: chunked", "", "c", "Hello World!", ""); } } }
public async Task ThrowingAfterPartialWriteKillsConnection() { bool onStartingCalled = false; using (var server = new TestServer(async frame => { frame.OnStarting(_ => { onStartingCalled = true; return(Task.FromResult <object>(null)); }, null); frame.ResponseHeaders.Clear(); frame.ResponseHeaders["Content-Length"] = new[] { "11" }; await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("Hello"), 0, 5); throw new Exception(); })) { using (var connection = new TestConnection()) { await connection.Send( "GET / HTTP/1.1", "", ""); await connection.ReceiveEnd( "HTTP/1.1 200 OK", "Content-Length: 11", "", "Hello"); Assert.True(onStartingCalled); } } }
public async Task ThrowingInOnCompletedIsLoggedAndClosesConnection(ServiceContext testContext) { var onCompletedCalled1 = false; var onCompletedCalled2 = false; var testLogger = new TestApplicationErrorLogger(); testContext.Log = new KestrelTrace(testLogger); using (var server = new TestServer(async httpContext => { var response = httpContext.Response; response.OnCompleted(_ => { onCompletedCalled1 = true; throw new Exception(); }, null); response.OnCompleted(_ => { onCompletedCalled2 = true; throw new Exception(); }, null); response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "11" }; await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11); }, testContext)) { using (var connection = new TestConnection()) { await connection.Send( "GET / HTTP/1.1", "", ""); await connection.ReceiveEnd( "HTTP/1.1 200 OK", "Content-Length: 11", "", "Hello World"); } // All OnCompleted callbacks should be called even if they throw. Assert.Equal(2, testLogger.ApplicationErrorsLogged); Assert.True(onCompletedCalled1); Assert.True(onCompletedCalled2); } }
public async Task Http10TransferEncoding() { using (var server = new TestServer(App)) { using (var connection = new TestConnection()) { await connection.Send( "POST / HTTP/1.0", "Transfer-Encoding: chunked", "", "5", "Hello", "6", " World", "0\r\n"); await connection.ReceiveEnd( "HTTP/1.0 200 OK", "", "Hello World"); } } }
public async Task Http10ContentLength() { using (var server = new TestServer(App)) { using (var connection = new TestConnection()) { await connection.Send( "POST / HTTP/1.0", "Content-Length: 11", "", "Hello World"); await connection.ReceiveEnd( "HTTP/1.0 200 OK", "", "Hello World"); } } }
public async Task ThrowingAfterPartialWriteKillsConnection(ServiceContext testContext) { bool onStartingCalled = false; var testLogger = new TestApplicationErrorLogger(); testContext.Log = new KestrelTrace(testLogger); using (var server = new TestServer(async httpContext => { var response = httpContext.Response; response.OnStarting(_ => { onStartingCalled = true; return(Task.FromResult <object>(null)); }, null); response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "11" }; await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello"), 0, 5); throw new Exception(); }, testContext)) { using (var connection = new TestConnection()) { await connection.Send( "GET / HTTP/1.1", "", ""); await connection.ReceiveEnd( "HTTP/1.1 200 OK", "Content-Length: 11", "", "Hello"); Assert.True(onStartingCalled); Assert.Equal(1, testLogger.ApplicationErrorsLogged); } } }
public async Task ThrowingInOnStartingResultsInFailedWrites() { using (var server = new TestServer(async frame => { var onStartingException = new Exception(); frame.OnStarting(_ => { throw onStartingException; }, null); frame.ResponseHeaders.Clear(); frame.ResponseHeaders["Content-Length"] = new[] { "11" }; var writeException = await Assert.ThrowsAsync <Exception>(async() => await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11)); Assert.Same(onStartingException, writeException); // The second write should succeed since the OnStarting callback will not be called again await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("Exception!!"), 0, 11); })) { using (var connection = new TestConnection()) { await connection.Send( "GET / HTTP/1.1", "", ""); await connection.Receive( "HTTP/1.1 200 OK", "Content-Length: 11", "", "Exception!!");; } } }
public async Task ThrowingInOnCompletedIsLoggedAndClosesConnection(ServiceContext testContext) { var onCompletedCalled1 = false; var onCompletedCalled2 = false; var testLogger = new TestApplicationErrorLogger(); testContext.Log = new KestrelTrace(testLogger); using (var server = new TestServer(async frame => { var response = frame.Get<IHttpResponseFeature>(); response.OnCompleted(_ => { onCompletedCalled1 = true; throw new Exception(); }, null); response.OnCompleted(_ => { onCompletedCalled2 = true; throw new Exception(); }, null); response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "11" }; await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11); }, testContext)) { using (var connection = new TestConnection()) { await connection.Send( "GET / HTTP/1.1", "", ""); await connection.ReceiveEnd( "HTTP/1.1 200 OK", "Content-Length: 11", "", "Hello World"); } // All OnCompleted callbacks should be called even if they throw. Assert.Equal(2, testLogger.ApplicationErrorsLogged); Assert.True(onCompletedCalled1); Assert.True(onCompletedCalled2); } }
public async Task ThrowingAfterPartialWriteKillsConnection(ServiceContext testContext) { bool onStartingCalled = false; var testLogger = new TestApplicationErrorLogger(); testContext.Log = new KestrelTrace(testLogger); using (var server = new TestServer(async frame => { var response = frame.Get<IHttpResponseFeature>(); response.OnStarting(_ => { onStartingCalled = true; return Task.FromResult<object>(null); }, null); response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "11" }; await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello"), 0, 5); throw new Exception(); }, testContext)) { using (var connection = new TestConnection()) { await connection.Send( "GET / HTTP/1.1", "", ""); await connection.ReceiveEnd( "HTTP/1.1 200 OK", "Content-Length: 11", "", "Hello"); Assert.True(onStartingCalled); Assert.Equal(1, testLogger.ApplicationErrorsLogged); } } }
public async Task ConnectionClosedIfExeptionThrownAfterZeroLengthWrite() { using (var server = new TestServer(async httpContext => { var response = httpContext.Response; response.Headers.Clear(); await response.Body.WriteAsync(new byte[0], 0, 0); throw new Exception(); })) { using (var connection = new TestConnection()) { // SendEnd is not called, so it isn't the client closing the connection. await connection.Send( "GET / HTTP/1.1", "", ""); // Headers are sent before connection is closed, but chunked body terminator isn't sent await connection.ReceiveEnd( "HTTP/1.1 200 OK", "Transfer-Encoding: chunked", "", ""); } } }
public async Task FailedWritesResultInAbortedRequest(ServiceContext testContext) { var writeTcs = new TaskCompletionSource <object>(); var registrationWh = new ManualResetEventSlim(); var connectionCloseWh = new ManualResetEventSlim(); using (var server = new TestServer(async httpContext => { var response = httpContext.Response; var request = httpContext.Request; var lifetime = httpContext.Features.Get <IHttpRequestLifetimeFeature>(); lifetime.RequestAborted.Register(() => registrationWh.Set()); await request.Body.CopyToAsync(Stream.Null); connectionCloseWh.Wait(); response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "5" }; try { // Ensure write is long enough to disable write-behind buffering for (int i = 0; i < 10; i++) { await response.WriteAsync(new string('a', 65537)); } } catch (Exception ex) { writeTcs.SetException(ex); // Give a chance for RequestAborted to trip before the app completes registrationWh.Wait(1000); throw; } writeTcs.SetCanceled(); }, testContext)) { using (var connection = new TestConnection()) { await connection.Send( "POST / HTTP/1.1", "Content-Length: 5", "", "Hello"); // Don't wait to receive the response. Just close the socket. } connectionCloseWh.Set(); // Write failed await Assert.ThrowsAsync <IOException>(async() => await writeTcs.Task); // RequestAborted tripped Assert.True(registrationWh.Wait(200)); } }
public async Task ConnectionClosedIfExeptionThrownAfterZeroLengthWrite() { using (var server = new TestServer(async frame => { frame.ResponseHeaders.Clear(); await frame.ResponseBody.WriteAsync(new byte[0], 0, 0); throw new Exception(); })) { using (var connection = new TestConnection()) { // SendEnd is not called, so it isn't the client closing the connection. await connection.Send( "GET / HTTP/1.1", "", ""); // Nothing (not even headers) are written, but the connection is closed. await connection.ReceiveEnd(); } } }
public async Task RequestsCanBeAbortedMidRead(ServiceContext testContext) { var readTcs = new TaskCompletionSource<object>(); var registrationTcs = new TaskCompletionSource<int>(); var requestId = 0; using (var server = new TestServer(async httpContext => { requestId++; var response = httpContext.Response; var request = httpContext.Request; var lifetime = httpContext.Features.Get<IHttpRequestLifetimeFeature>(); lifetime.RequestAborted.Register(() => registrationTcs.TrySetResult(requestId)); if (requestId == 1) { response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "5" }; await response.WriteAsync("World"); } else { var readTask = request.Body.CopyToAsync(Stream.Null); lifetime.Abort(); try { await readTask; } catch (Exception ex) { readTcs.SetException(ex); throw; } readTcs.SetCanceled(); } }, testContext)) { using (var connection = new TestConnection()) { // Never send the body so CopyToAsync always fails. await connection.Send( "POST / HTTP/1.1", "Content-Length: 5", "", "HelloPOST / HTTP/1.1", "Content-Length: 5", "", ""); await connection.ReceiveEnd( "HTTP/1.1 200 OK", "Content-Length: 5", "", "World"); } } await Assert.ThrowsAsync<IOException>(async () => await readTcs.Task); // The cancellation token for only the last request should be triggered. var abortedRequestId = await registrationTcs.Task; Assert.Equal(2, abortedRequestId); }
public async Task FailedWritesResultInAbortedRequest(ServiceContext testContext) { var writeTcs = new TaskCompletionSource<object>(); var registrationWh = new ManualResetEventSlim(); var connectionCloseWh = new ManualResetEventSlim(); using (var server = new TestServer(async httpContext => { var response = httpContext.Response; var request = httpContext.Request; var lifetime = httpContext.Features.Get<IHttpRequestLifetimeFeature>(); lifetime.RequestAborted.Register(() => registrationWh.Set()); await request.Body.CopyToAsync(Stream.Null); connectionCloseWh.Wait(); response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "5" }; try { // Ensure write is long enough to disable write-behind buffering for (int i = 0; i < 10; i++) { await response.WriteAsync(new string('a', 65537)); } } catch (Exception ex) { writeTcs.SetException(ex); // Give a chance for RequestAborted to trip before the app completes registrationWh.Wait(1000); throw; } writeTcs.SetCanceled(); }, testContext)) { using (var connection = new TestConnection()) { await connection.Send( "POST / HTTP/1.1", "Content-Length: 5", "", "Hello"); // Don't wait to receive the response. Just close the socket. } connectionCloseWh.Set(); // Write failed await Assert.ThrowsAsync<IOException>(async () => await writeTcs.Task); // RequestAborted tripped Assert.True(registrationWh.Wait(200)); } }
public async Task ThrowingAfterPartialWriteKillsConnection() { bool onStartingCalled = false; using (var server = new TestServer(async frame => { frame.OnStarting(_ => { onStartingCalled = true; return Task.FromResult<object>(null); }, null); frame.ResponseHeaders.Clear(); frame.ResponseHeaders["Content-Length"] = new[] { "11" }; await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("Hello"), 0, 5); throw new Exception(); })) { using (var connection = new TestConnection()) { await connection.Send( "GET / HTTP/1.1", "", ""); await connection.ReceiveEnd( "HTTP/1.1 200 OK", "Content-Length: 11", "", "Hello"); Assert.True(onStartingCalled); } } }
public async Task ConnectionClosedIfExeptionThrownAfterWrite() { using (var server = new TestServer(async httpContext => { var response = httpContext.Response; response.Headers.Clear(); await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World!"), 0, 12); throw new Exception(); })) { using (var connection = new TestConnection()) { // SendEnd is not called, so it isn't the client closing the connection. // client closing the connection. await connection.Send( "GET / HTTP/1.1", "", ""); await connection.ReceiveEnd( "HTTP/1.1 200 OK", "Transfer-Encoding: chunked", "", "c", "Hello World!", ""); } } }
public async Task ThrowingInOnStartingResultsInFailedWrites() { using (var server = new TestServer(async frame => { var onStartingException = new Exception(); frame.OnStarting(_ => { throw onStartingException; }, null); frame.ResponseHeaders.Clear(); frame.ResponseHeaders["Content-Length"] = new[] { "11" }; var writeException = await Assert.ThrowsAsync<Exception>(async () => await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11)); Assert.Same(onStartingException, writeException); // The second write should succeed since the OnStarting callback will not be called again await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("Exception!!"), 0, 11); })) { using (var connection = new TestConnection()) { await connection.Send( "GET / HTTP/1.1", "", ""); await connection.Receive( "HTTP/1.1 200 OK", "Content-Length: 11", "", "Exception!!"); ; } } }
public async Task RequestsCanBeAbortedMidRead(ServiceContext testContext) { var readTcs = new TaskCompletionSource <object>(); var registrationTcs = new TaskCompletionSource <int>(); var requestId = 0; using (var server = new TestServer(async httpContext => { requestId++; var response = httpContext.Response; var request = httpContext.Request; var lifetime = httpContext.Features.Get <IHttpRequestLifetimeFeature>(); lifetime.RequestAborted.Register(() => registrationTcs.TrySetResult(requestId)); if (requestId == 1) { response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "5" }; await response.WriteAsync("World"); } else { var readTask = request.Body.CopyToAsync(Stream.Null); lifetime.Abort(); try { await readTask; } catch (Exception ex) { readTcs.SetException(ex); throw; } readTcs.SetCanceled(); } }, testContext)) { using (var connection = new TestConnection()) { // Never send the body so CopyToAsync always fails. await connection.Send( "POST / HTTP/1.1", "Content-Length: 5", "", "HelloPOST / HTTP/1.1", "Content-Length: 5", "", ""); await connection.ReceiveEnd( "HTTP/1.1 200 OK", "Content-Length: 5", "", "World"); } } await Assert.ThrowsAsync <IOException>(async() => await readTcs.Task); // The cancellation token for only the last request should be triggered. var abortedRequestId = await registrationTcs.Task; Assert.Equal(2, abortedRequestId); }