Reorder SendFileAsync to match Write/FlushAsync

This commit is contained in:
Chris R 2016-08-26 10:10:09 -07:00
parent c2a1b8d17f
commit c2f52db3a5
8 changed files with 143 additions and 131 deletions

View File

@ -23,6 +23,7 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -81,6 +82,17 @@ namespace Microsoft.Net.Http.Server
return tcs.Task; return tcs.Task;
} }
internal static ArraySegment<byte> GetChunkHeader(long size)
{
if (size < int.MaxValue)
{
return GetChunkHeader((int)size);
}
// Greater than 2gb, perf is no longer our concern
return new ArraySegment<byte>(Encoding.ASCII.GetBytes(size.ToString("X") + "\r\n"));
}
/// <summary> /// <summary>
/// A private utility routine to convert an integer to a chunk header, /// A private utility routine to convert an integer to a chunk header,
/// which is an ASCII hex number followed by a CRLF.The header is returned /// which is an ASCII hex number followed by a CRLF.The header is returned

View File

@ -308,12 +308,13 @@ namespace Microsoft.Net.Http.Server
// TODO: Verbose log // TODO: Verbose log
try try
{ {
if (_requestAbortSource != null) _requestAbortSource?.Dispose();
{
_requestAbortSource.Dispose();
}
Response.Dispose(); Response.Dispose();
} }
catch
{
Abort();
}
finally finally
{ {
Request.Dispose(); Request.Dispose();
@ -334,14 +335,19 @@ namespace Microsoft.Net.Http.Server
{ {
_requestAbortSource.Cancel(); _requestAbortSource.Cancel();
} }
catch (ObjectDisposedException)
{
}
catch (Exception ex) catch (Exception ex)
{ {
LogHelper.LogException(Logger, "Abort", ex); LogHelper.LogDebug(Logger, "Abort", ex);
} }
_requestAbortSource.Dispose(); _requestAbortSource.Dispose();
} }
ForceCancelRequest(); ForceCancelRequest();
Request.Dispose(); Request.Dispose();
// Only Abort, Response.Dispose() tries a graceful flush
Response.Abort();
} }
private static void Abort(object state) private static void Abort(object state)

View File

@ -109,7 +109,6 @@ namespace Microsoft.Net.Http.Server
{ {
get get
{ {
CheckDisposed();
EnsureResponseStream(); EnsureResponseStream();
return _nativeStream; return _nativeStream;
} }
@ -245,6 +244,12 @@ namespace Microsoft.Net.Http.Server
} }
} }
internal void Abort()
{
// Update state for HasStarted. Do not attempt a graceful Dispose.
_responseState = ResponseState.Closed;
}
// should only be called from RequestContext // should only be called from RequestContext
internal void Dispose() internal void Dispose()
{ {
@ -685,14 +690,6 @@ namespace Microsoft.Net.Http.Server
} }
} }
private void CheckDisposed()
{
if (_responseState >= ResponseState.Closed)
{
throw new ObjectDisposedException(GetType().FullName);
}
}
internal void CancelLastWrite() internal void CancelLastWrite()
{ {
_nativeStream?.CancelLastWrite(); _nativeStream?.CancelLastWrite();

View File

@ -132,12 +132,13 @@ namespace Microsoft.Net.Http.Server
var started = _requestContext.Response.HasStarted; var started = _requestContext.Response.HasStarted;
if (data.Count == 0 && started && !endOfRequest) if (data.Count == 0 && started && !endOfRequest)
{ {
// Empty flush // No data to send and we've already sent the headers
return; return;
} }
// Make sure all validation is performed before this computes the headers
var flags = ComputeLeftToWrite(endOfRequest); var flags = ComputeLeftToWrite(endOfRequest);
if (!_inOpaqueMode && endOfRequest && _leftToWrite > data.Count) if (!_inOpaqueMode && endOfRequest && _leftToWrite > 0)
{ {
_requestContext.Abort(); _requestContext.Abort();
// This is logged rather than thrown because it is too late for an exception to be visible in user code. // This is logged rather than thrown because it is too late for an exception to be visible in user code.
@ -303,7 +304,7 @@ namespace Microsoft.Net.Http.Server
var started = _requestContext.Response.HasStarted; var started = _requestContext.Response.HasStarted;
if (data.Count == 0 && started) if (data.Count == 0 && started)
{ {
// Empty flush // No data to send and we've already sent the headers
return Helpers.CompletedTask(); return Helpers.CompletedTask();
} }
@ -313,6 +314,7 @@ namespace Microsoft.Net.Http.Server
return Helpers.CanceledTask<int>(); return Helpers.CanceledTask<int>();
} }
// Make sure all validation is performed before this computes the headers
var flags = ComputeLeftToWrite(); var flags = ComputeLeftToWrite();
if (_leftToWrite != data.Count) if (_leftToWrite != data.Count)
{ {
@ -464,30 +466,31 @@ namespace Microsoft.Net.Http.Server
public override void Write(byte[] buffer, int offset, int count) public override void Write(byte[] buffer, int offset, int count)
{ {
// Validates for null and bounds. Allows count == 0. // Validates for null and bounds. Allows count == 0.
// TODO: Verbose log parameters
var data = new ArraySegment<byte>(buffer, offset, count); var data = new ArraySegment<byte>(buffer, offset, count);
CheckDisposed(); CheckDisposed();
// TODO: Verbose log parameters
var contentLength = _requestContext.Response.ContentLength; CheckWriteCount(count);
if (contentLength.HasValue && !_requestContext.Response.HasComputedHeaders && contentLength.Value <= data.Count)
{
if (contentLength.Value < data.Count)
{
throw new InvalidOperationException("More bytes written than specified in the Content-Length header.");
}
}
// The last write in a response that has already started, flush immediately
else if (_requestContext.Response.HasComputedHeaders && _leftToWrite >= 0 && _leftToWrite <= data.Count)
{
if (_leftToWrite < data.Count)
{
throw new InvalidOperationException("More bytes written than specified in the Content-Length header.");
}
}
FlushInternal(endOfRequest: false, data: data); FlushInternal(endOfRequest: false, data: data);
} }
private void CheckWriteCount(long? count)
{
var contentLength = _requestContext.Response.ContentLength;
// First write with more bytes written than the entire content-length
if (!_requestContext.Response.HasComputedHeaders && contentLength < count)
{
throw new InvalidOperationException("More bytes written than specified in the Content-Length header.");
}
// A write in a response that has already started where the count exceeds the remainder of the content-length
else if (_requestContext.Response.HasComputedHeaders && _requestContext.Response.BoundaryType == BoundaryType.ContentLength
&& _leftToWrite < count)
{
throw new InvalidOperationException("More bytes written than specified in the Content-Length header.");
}
}
#if NETSTANDARD1_3 #if NETSTANDARD1_3
public IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) public IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
#else #else
@ -512,26 +515,11 @@ namespace Microsoft.Net.Http.Server
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
// Validates for null and bounds. Allows count == 0. // Validates for null and bounds. Allows count == 0.
// TODO: Verbose log parameters
var data = new ArraySegment<byte>(buffer, offset, count); var data = new ArraySegment<byte>(buffer, offset, count);
CheckDisposed(); CheckDisposed();
// TODO: Verbose log parameters
var contentLength = _requestContext.Response.ContentLength; CheckWriteCount(count);
if (contentLength.HasValue && !_requestContext.Response.HasComputedHeaders && contentLength.Value <= data.Count)
{
if (contentLength.Value < data.Count)
{
throw new InvalidOperationException("More bytes written than specified in the Content-Length header.");
}
}
// The last write in a response that has already started, flush immediately
else if (_requestContext.Response.HasComputedHeaders && _leftToWrite > 0 && _leftToWrite <= data.Count)
{
if (_leftToWrite < data.Count)
{
throw new InvalidOperationException("More bytes written than specified in the Content-Length header.");
}
}
return FlushInternalAsync(data, cancellationToken); return FlushInternalAsync(data, cancellationToken);
} }
@ -540,12 +528,15 @@ namespace Microsoft.Net.Http.Server
{ {
// It's too expensive to validate the file attributes before opening the file. Open the file and then check the lengths. // It's too expensive to validate the file attributes before opening the file. Open the file and then check the lengths.
// This all happens inside of ResponseStreamAsyncResult. // This all happens inside of ResponseStreamAsyncResult.
// TODO: Verbose log parameters
if (string.IsNullOrWhiteSpace(fileName)) if (string.IsNullOrWhiteSpace(fileName))
{ {
throw new ArgumentNullException("fileName"); throw new ArgumentNullException("fileName");
} }
CheckDisposed(); CheckDisposed();
CheckWriteCount(count);
// We can't mix await and unsafe so separate the unsafe code into another method. // We can't mix await and unsafe so separate the unsafe code into another method.
await SendFileAsyncCore(fileName, offset, count, cancellationToken); await SendFileAsyncCore(fileName, offset, count, cancellationToken);
} }
@ -557,43 +548,65 @@ namespace Microsoft.Net.Http.Server
return Helpers.CompletedTask(); return Helpers.CompletedTask();
} }
var flags = ComputeLeftToWrite(); var started = _requestContext.Response.HasStarted;
if (count == 0 && _leftToWrite != 0) if (count == 0 && started)
{ {
// No data to send and we've already sent the headers
return Helpers.CompletedTask(); return Helpers.CompletedTask();
} }
if (_leftToWrite >= 0 && count > _leftToWrite)
{
throw new InvalidOperationException(Resources.Exception_TooMuchWritten);
}
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
Abort(ThrowWriteExceptions); Abort(ThrowWriteExceptions);
return Helpers.CanceledTask<int>(); return Helpers.CanceledTask<int>();
} }
// TODO: Verbose log
// We are setting buffer size to 1 to prevent FileStream from allocating it's internal buffer
// It's too expensive to validate anything before opening the file. Open the file and then check the lengths.
var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, bufferSize: 1,
options: FileOptions.Asynchronous | FileOptions.SequentialScan); // Extremely expensive.
try
{
var length = fileStream.Length; // Expensive, only do it once
if (!count.HasValue)
{
count = length - offset;
}
if (offset < 0 || offset > length)
{
throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty);
}
if (count < 0 || count > length - offset)
{
throw new ArgumentOutOfRangeException(nameof(count), count, string.Empty);
}
CheckWriteCount(count);
}
catch
{
fileStream.Dispose();
throw;
}
// Make sure all validation is performed before this computes the headers
var flags = ComputeLeftToWrite();
uint statusCode; uint statusCode;
uint bytesSent = 0; uint bytesSent = 0;
var started = _requestContext.Response.HasStarted;
var chunked = _requestContext.Response.BoundaryType == BoundaryType.Chunked; var chunked = _requestContext.Response.BoundaryType == BoundaryType.Chunked;
var asyncResult = new ResponseStreamAsyncResult(this, fileName, offset, count, chunked, cancellationToken); var asyncResult = new ResponseStreamAsyncResult(this, fileStream, offset, count.Value, chunked, cancellationToken);
long bytesWritten; long bytesWritten;
if (chunked) if (chunked)
{ {
bytesWritten = 0; bytesWritten = 0;
} }
else if (count.HasValue) else
{ {
bytesWritten = count.Value; bytesWritten = count.Value;
} }
else
{
bytesWritten = asyncResult.FileLength - offset;
}
// Update _leftToWrite now so we can queue up additional calls to SendFileAsync. // Update _leftToWrite now so we can queue up additional calls to SendFileAsync.
flags |= _leftToWrite == bytesWritten ? HttpApi.HTTP_FLAGS.NONE : HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA; flags |= _leftToWrite == bytesWritten ? HttpApi.HTTP_FLAGS.NONE : HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
UpdateWritenCount((uint)bytesWritten); UpdateWritenCount((uint)bytesWritten);

View File

@ -114,33 +114,15 @@ namespace Microsoft.Net.Http.Server
} }
} }
internal ResponseStreamAsyncResult(ResponseStream responseStream, string fileName, long offset, internal ResponseStreamAsyncResult(ResponseStream responseStream, FileStream fileStream, long offset,
long? count, bool chunked, CancellationToken cancellationToken) long count, bool chunked, CancellationToken cancellationToken)
: this(responseStream, cancellationToken) : this(responseStream, cancellationToken)
{ {
var boundHandle = responseStream.RequestContext.Server.RequestQueue.BoundHandle; var boundHandle = responseStream.RequestContext.Server.RequestQueue.BoundHandle;
int bufferSize = 1024 * 64; // TODO: Validate buffer size choice. _fileStream = fileStream;
#if NETSTANDARD1_3
_fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, bufferSize /*, useAsync: true*/); // Extremely expensive.
#else
// It's too expensive to validate anything before opening the file. Open the file and then check the lengths.
_fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, bufferSize,
FileOptions.Asynchronous | FileOptions.SequentialScan); // Extremely expensive.
#endif
long length = _fileStream.Length; // Expensive
if (offset < 0 || offset > length)
{
_fileStream.Dispose();
throw new ArgumentOutOfRangeException("offset", offset, string.Empty);
}
if (count.HasValue && (count < 0 || count > length - offset))
{
_fileStream.Dispose();
throw new ArgumentOutOfRangeException("count", count, string.Empty);
}
if (count == 0 || (!count.HasValue && _fileStream.Length == 0)) if (count == 0)
{ {
_dataChunks = null; _dataChunks = null;
_overlapped = new SafeNativeOverlapped(boundHandle, _overlapped = new SafeNativeOverlapped(boundHandle,
@ -156,14 +138,14 @@ namespace Microsoft.Net.Http.Server
var chunkHeaderBuffer = new ArraySegment<byte>(); var chunkHeaderBuffer = new ArraySegment<byte>();
if (chunked) if (chunked)
{ {
chunkHeaderBuffer = Helpers.GetChunkHeader((int)(count ?? _fileStream.Length - offset)); chunkHeaderBuffer = Helpers.GetChunkHeader(count);
_dataChunks[0].DataChunkType = HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory; _dataChunks[0].DataChunkType = HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
_dataChunks[0].fromMemory.BufferLength = (uint)chunkHeaderBuffer.Count; _dataChunks[0].fromMemory.BufferLength = (uint)chunkHeaderBuffer.Count;
objectsToPin[0] = chunkHeaderBuffer.Array; objectsToPin[0] = chunkHeaderBuffer.Array;
_dataChunks[1].DataChunkType = HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromFileHandle; _dataChunks[1].DataChunkType = HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromFileHandle;
_dataChunks[1].fromFile.offset = (ulong)offset; _dataChunks[1].fromFile.offset = (ulong)offset;
_dataChunks[1].fromFile.count = (ulong)(count ?? -1); _dataChunks[1].fromFile.count = (ulong)count;
_dataChunks[1].fromFile.fileHandle = _fileStream.SafeFileHandle.DangerousGetHandle(); _dataChunks[1].fromFile.fileHandle = _fileStream.SafeFileHandle.DangerousGetHandle();
// Nothing to pin for the file handle. // Nothing to pin for the file handle.
@ -175,7 +157,7 @@ namespace Microsoft.Net.Http.Server
{ {
_dataChunks[0].DataChunkType = HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromFileHandle; _dataChunks[0].DataChunkType = HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromFileHandle;
_dataChunks[0].fromFile.offset = (ulong)offset; _dataChunks[0].fromFile.offset = (ulong)offset;
_dataChunks[0].fromFile.count = (ulong)(count ?? -1); _dataChunks[0].fromFile.count = (ulong)count;
_dataChunks[0].fromFile.fileHandle = _fileStream.SafeFileHandle.DangerousGetHandle(); _dataChunks[0].fromFile.fileHandle = _fileStream.SafeFileHandle.DangerousGetHandle();
} }
@ -248,11 +230,6 @@ namespace Microsoft.Net.Http.Server
} }
} }
internal long FileLength
{
get { return _fileStream == null ? 0 : _fileStream.Length; }
}
internal bool EndCalled { get; set; } internal bool EndCalled { get; set; }
internal void IOCompleted(uint errorCode) internal void IOCompleted(uint errorCode)

View File

@ -40,7 +40,7 @@ namespace Microsoft.Net.Http.Server
// returned from HttpReceiveClientCertificate when using the // returned from HttpReceiveClientCertificate when using the
// FileCompletionNotificationModes.SkipCompletionPortOnSuccess flag. // FileCompletionNotificationModes.SkipCompletionPortOnSuccess flag.
// This bug was only hit when the buffer passed into HttpReceiveClientCertificate // This bug was only hit when the buffer passed into HttpReceiveClientCertificate
// (1500 bytes initially) is tool small for the certificate. // (1500 bytes initially) is too small for the certificate.
// Due to this bug in downlevel operating systems the FileCompletionNotificationModes.SkipCompletionPortOnSuccess // Due to this bug in downlevel operating systems the FileCompletionNotificationModes.SkipCompletionPortOnSuccess
// flag is only used on Win8 and later. // flag is only used on Win8 and later.
internal static readonly bool SkipIOCPCallbackOnSuccess = ComNetOS.IsWin8orLater; internal static readonly bool SkipIOCPCallbackOnSuccess = ComNetOS.IsWin8orLater;

View File

@ -221,12 +221,14 @@ namespace Microsoft.AspNetCore.Server.WebListener
using (Utilities.CreateHttpServer(out address, async httpContext => using (Utilities.CreateHttpServer(out address, async httpContext =>
{ {
var sendFile = httpContext.Features.Get<IHttpSendFileFeature>(); var sendFile = httpContext.Features.Get<IHttpSendFileFeature>();
await sendFile.SendFileAsync(AbsoluteFilePath, 1234567, null, CancellationToken.None); await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() =>
sendFile.SendFileAsync(AbsoluteFilePath, 1234567, null, CancellationToken.None));
completed = true; completed = true;
})) }))
{ {
await Assert.ThrowsAsync<HttpRequestException>(() => SendRequestAsync(address)); var response = await SendRequestAsync(address);
Assert.False(completed); response.EnsureSuccessStatusCode();
Assert.True(completed);
} }
} }
@ -238,12 +240,14 @@ namespace Microsoft.AspNetCore.Server.WebListener
using (Utilities.CreateHttpServer(out address, async httpContext => using (Utilities.CreateHttpServer(out address, async httpContext =>
{ {
var sendFile = httpContext.Features.Get<IHttpSendFileFeature>(); var sendFile = httpContext.Features.Get<IHttpSendFileFeature>();
await sendFile.SendFileAsync(AbsoluteFilePath, 0, 1234567, CancellationToken.None); await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() =>
sendFile.SendFileAsync(AbsoluteFilePath, 0, 1234567, CancellationToken.None));
completed = true; completed = true;
})) }))
{ {
await Assert.ThrowsAsync<HttpRequestException>(() => SendRequestAsync(address)); var response = await SendRequestAsync(address);
Assert.False(completed); response.EnsureSuccessStatusCode();
Assert.True(completed);
} }
} }

View File

@ -30,14 +30,15 @@ namespace Microsoft.Net.Http.Server
string address; string address;
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
{ {
Task<HttpResponseMessage> responseTask = SendRequestAsync(address); var responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(); var context = await server.AcceptAsync();
await Assert.ThrowsAsync<FileNotFoundException>(() => await Assert.ThrowsAsync<FileNotFoundException>(() =>
context.Response.SendFileAsync("Missing.txt", 0, null, CancellationToken.None)); context.Response.SendFileAsync("Missing.txt", 0, null, CancellationToken.None));
context.Dispose(); context.Dispose();
HttpResponseMessage response = await responseTask; var response = await responseTask;
response.EnsureSuccessStatusCode();
} }
} }
@ -47,13 +48,13 @@ namespace Microsoft.Net.Http.Server
string address; string address;
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
{ {
Task<HttpResponseMessage> responseTask = SendRequestAsync(address); var responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(); var context = await server.AcceptAsync();
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None); await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
context.Dispose(); context.Dispose();
HttpResponseMessage response = await responseTask; var response = await responseTask;
Assert.Equal(200, (int)response.StatusCode); Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> ignored; IEnumerable<string> ignored;
Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length"); Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
@ -68,13 +69,13 @@ namespace Microsoft.Net.Http.Server
string address; string address;
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
{ {
Task<HttpResponseMessage> responseTask = SendRequestAsync(address); var responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(); var context = await server.AcceptAsync();
await context.Response.SendFileAsync(RelativeFilePath, 0, null, CancellationToken.None); await context.Response.SendFileAsync(RelativeFilePath, 0, null, CancellationToken.None);
context.Dispose(); context.Dispose();
HttpResponseMessage response = await responseTask; var response = await responseTask;
Assert.Equal(200, (int)response.StatusCode); Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> ignored; IEnumerable<string> ignored;
Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length"); Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
@ -89,13 +90,13 @@ namespace Microsoft.Net.Http.Server
string address; string address;
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
{ {
Task<HttpResponseMessage> responseTask = SendRequestAsync(address); var responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(); var context = await server.AcceptAsync();
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None); await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
context.Dispose(); context.Dispose();
HttpResponseMessage response = await responseTask; var response = await responseTask;
Assert.Equal(200, (int)response.StatusCode); Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> contentLength; IEnumerable<string> contentLength;
Assert.False(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length"); Assert.False(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
@ -110,14 +111,14 @@ namespace Microsoft.Net.Http.Server
string address; string address;
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
{ {
Task<HttpResponseMessage> responseTask = SendRequestAsync(address); var responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(); var context = await server.AcceptAsync();
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None); await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None); await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
context.Dispose(); context.Dispose();
HttpResponseMessage response = await responseTask; var response = await responseTask;
Assert.Equal(200, (int)response.StatusCode); Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> contentLength; IEnumerable<string> contentLength;
Assert.False(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length"); Assert.False(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
@ -132,13 +133,13 @@ namespace Microsoft.Net.Http.Server
string address; string address;
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
{ {
Task<HttpResponseMessage> responseTask = SendRequestAsync(address); var responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(); var context = await server.AcceptAsync();
await context.Response.SendFileAsync(AbsoluteFilePath, 0, FileLength / 2, CancellationToken.None); await context.Response.SendFileAsync(AbsoluteFilePath, 0, FileLength / 2, CancellationToken.None);
context.Dispose(); context.Dispose();
HttpResponseMessage response = await responseTask; var response = await responseTask;
Assert.Equal(200, (int)response.StatusCode); Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> contentLength; IEnumerable<string> contentLength;
Assert.False(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length"); Assert.False(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
@ -153,14 +154,15 @@ namespace Microsoft.Net.Http.Server
string address; string address;
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
{ {
Task<HttpResponseMessage> responseTask = SendRequestAsync(address); var responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(); var context = await server.AcceptAsync();
await Assert.ThrowsAsync<ArgumentOutOfRangeException>( await Assert.ThrowsAsync<ArgumentOutOfRangeException>(
() => context.Response.SendFileAsync(AbsoluteFilePath, 1234567, null, CancellationToken.None)); () => context.Response.SendFileAsync(AbsoluteFilePath, 1234567, null, CancellationToken.None));
context.Dispose(); context.Dispose();
HttpResponseMessage response = await responseTask; var response = await responseTask;
response.EnsureSuccessStatusCode();
} }
} }
@ -170,14 +172,15 @@ namespace Microsoft.Net.Http.Server
string address; string address;
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
{ {
Task<HttpResponseMessage> responseTask = SendRequestAsync(address); var responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(); var context = await server.AcceptAsync();
await Assert.ThrowsAsync<ArgumentOutOfRangeException>( await Assert.ThrowsAsync<ArgumentOutOfRangeException>(
() => context.Response.SendFileAsync(AbsoluteFilePath, 0, 1234567, CancellationToken.None)); () => context.Response.SendFileAsync(AbsoluteFilePath, 0, 1234567, CancellationToken.None));
context.Dispose(); context.Dispose();
HttpResponseMessage response = await responseTask; var response = await responseTask;
response.EnsureSuccessStatusCode();
} }
} }
@ -187,13 +190,13 @@ namespace Microsoft.Net.Http.Server
string address; string address;
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
{ {
Task<HttpResponseMessage> responseTask = SendRequestAsync(address); var responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(); var context = await server.AcceptAsync();
await context.Response.SendFileAsync(AbsoluteFilePath, 0, 0, CancellationToken.None); await context.Response.SendFileAsync(AbsoluteFilePath, 0, 0, CancellationToken.None);
context.Dispose(); context.Dispose();
HttpResponseMessage response = await responseTask; var response = await responseTask;
Assert.Equal(200, (int)response.StatusCode); Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> contentLength; IEnumerable<string> contentLength;
Assert.False(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length"); Assert.False(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
@ -212,7 +215,7 @@ namespace Microsoft.Net.Http.Server
string address; string address;
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
{ {
Task<HttpResponseMessage> responseTask = SendRequestAsync(address); var responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(); var context = await server.AcceptAsync();
await context.Response.SendFileAsync(emptyFilePath, 0, null, CancellationToken.None); await context.Response.SendFileAsync(emptyFilePath, 0, null, CancellationToken.None);
@ -221,7 +224,7 @@ namespace Microsoft.Net.Http.Server
context.Dispose(); context.Dispose();
File.Delete(emptyFilePath); File.Delete(emptyFilePath);
HttpResponseMessage response = await responseTask; var response = await responseTask;
Assert.Equal(200, (int)response.StatusCode); Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> contentLength; IEnumerable<string> contentLength;
Assert.False(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length"); Assert.False(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
@ -236,13 +239,13 @@ namespace Microsoft.Net.Http.Server
string address; string address;
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
{ {
Task<HttpResponseMessage> responseTask = SendRequestAsync(address); var responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(); var context = await server.AcceptAsync();
context.Response.Headers["Content-lenGth"] = FileLength.ToString(); context.Response.Headers["Content-lenGth"] = FileLength.ToString();
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None); await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
HttpResponseMessage response = await responseTask; var response = await responseTask;
Assert.Equal(200, (int)response.StatusCode); Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> contentLength; IEnumerable<string> contentLength;
Assert.True(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length"); Assert.True(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
@ -258,14 +261,14 @@ namespace Microsoft.Net.Http.Server
string address; string address;
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
{ {
Task<HttpResponseMessage> responseTask = SendRequestAsync(address); var responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(); var context = await server.AcceptAsync();
context.Response.Headers["Content-lenGth"] = "10"; context.Response.Headers["Content-lenGth"] = "10";
await context.Response.SendFileAsync(AbsoluteFilePath, 0, 10, CancellationToken.None); await context.Response.SendFileAsync(AbsoluteFilePath, 0, 10, CancellationToken.None);
context.Dispose(); context.Dispose();
HttpResponseMessage response = await responseTask; var response = await responseTask;
Assert.Equal(200, (int)response.StatusCode); Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> contentLength; IEnumerable<string> contentLength;
Assert.True(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length"); Assert.True(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
@ -281,14 +284,14 @@ namespace Microsoft.Net.Http.Server
string address; string address;
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
{ {
Task<HttpResponseMessage> responseTask = SendRequestAsync(address); var responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(); var context = await server.AcceptAsync();
context.Response.Headers["Content-lenGth"] = "0"; context.Response.Headers["Content-lenGth"] = "0";
await context.Response.SendFileAsync(AbsoluteFilePath, 0, 0, CancellationToken.None); await context.Response.SendFileAsync(AbsoluteFilePath, 0, 0, CancellationToken.None);
context.Dispose(); context.Dispose();
HttpResponseMessage response = await responseTask; var response = await responseTask;
Assert.Equal(200, (int)response.StatusCode); Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> contentLength; IEnumerable<string> contentLength;
Assert.True(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length"); Assert.True(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
@ -313,7 +316,7 @@ namespace Microsoft.Net.Http.Server
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token); await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
context.Dispose(); context.Dispose();
HttpResponseMessage response = await responseTask; var response = await responseTask;
Assert.Equal(200, (int)response.StatusCode); Assert.Equal(200, (int)response.StatusCode);
Assert.Equal(FileLength * 2, (await response.Content.ReadAsByteArrayAsync()).Length); Assert.Equal(FileLength * 2, (await response.Content.ReadAsByteArrayAsync()).Length);
} }
@ -335,7 +338,7 @@ namespace Microsoft.Net.Http.Server
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token); await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, cts.Token);
context.Dispose(); context.Dispose();
HttpResponseMessage response = await responseTask; var response = await responseTask;
Assert.Equal(200, (int)response.StatusCode); Assert.Equal(200, (int)response.StatusCode);
Assert.Equal(FileLength * 2, (await response.Content.ReadAsByteArrayAsync()).Length); Assert.Equal(FileLength * 2, (await response.Content.ReadAsByteArrayAsync()).Length);
} }