aspnetcore/src/Microsoft.Net.Http.Server/RequestProcessing/ResponseStream.cs

867 lines
36 KiB
C#

// Copyright (c) Microsoft Open Technologies, Inc.
// All Rights Reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR
// NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing
// permissions and limitations under the License.
// ------------------------------------------------------------------------------
// <copyright file="_HttpResponseStream.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// ------------------------------------------------------------------------------
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Net.Http.Server
{
internal class ResponseStream : Stream
{
private static readonly byte[] ChunkTerminator = new byte[] { (byte)'0', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' };
private RequestContext _requestContext;
private long _leftToWrite = long.MinValue;
private bool _closed;
private bool _inOpaqueMode;
// The last write needs special handling to cancel.
private ResponseStreamAsyncResult _lastWrite;
internal ResponseStream(RequestContext requestContext)
{
_requestContext = requestContext;
}
public override bool CanSeek
{
get
{
return false;
}
}
public override bool CanWrite
{
get
{
return true;
}
}
public override bool CanRead
{
get
{
return false;
}
}
public override long Length
{
get
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
}
public override long Position
{
get
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
set
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
}
// Send headers
public override void Flush()
{
if (_closed || _requestContext.Response.SentHeaders)
{
return;
}
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
// TODO: Verbose log
try
{
uint statusCode;
unsafe
{
// TODO: Don't add MoreData flag if content-length == 0?
flags |= UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
statusCode = _requestContext.Response.SendHeaders(null, null, flags, false);
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
{
throw new IOException(string.Empty, new WebListenerException((int)statusCode));
}
}
catch (Exception e)
{
LogHelper.LogException(_requestContext.Logger, "Flush", e);
Abort();
throw;
}
}
// Send headers
public override Task FlushAsync(CancellationToken cancellationToken)
{
if (_closed || _requestContext.Response.SentHeaders)
{
return Helpers.CompletedTask();
}
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
// TODO: Verbose log
if (cancellationToken.IsCancellationRequested)
{
return Helpers.CanceledTask<int>();
}
var cancellationRegistration = default(CancellationTokenRegistration);
if (cancellationToken.CanBeCanceled)
{
cancellationRegistration = cancellationToken.Register(RequestContext.AbortDelegate, _requestContext);
}
// TODO: Don't add MoreData flag if content-length == 0?
flags |= UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
ResponseStreamAsyncResult asyncResult = new ResponseStreamAsyncResult(this, null, null, null, 0, 0, _requestContext.Response.BoundaryType == BoundaryType.Chunked, false, cancellationRegistration);
try
{
uint statusCode;
unsafe
{
statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false);
}
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && WebListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
asyncResult.IOCompleted(statusCode);
}
else if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
throw new IOException(string.Empty, new WebListenerException((int)statusCode));
}
}
catch (Exception e)
{
LogHelper.LogException(_requestContext.Logger, "FlushAsync", e);
asyncResult.Dispose();
Abort();
throw;
}
return asyncResult.Task;
}
#region NotSupported Read/Seek
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
public override void SetLength(long value)
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
public override int Read([In, Out] byte[] buffer, int offset, int size)
{
throw new InvalidOperationException(Resources.Exception_WriteOnlyStream);
}
#if ASPNET50
public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
{
throw new InvalidOperationException(Resources.Exception_WriteOnlyStream);
}
public override int EndRead(IAsyncResult asyncResult)
{
throw new InvalidOperationException(Resources.Exception_WriteOnlyStream);
}
#endif
#endregion
internal void Abort()
{
_closed = true;
_requestContext.Abort();
}
private UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS ComputeLeftToWrite(bool endOfRequest = false)
{
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE;
if (!_requestContext.Response.ComputedHeaders)
{
flags = _requestContext.Response.ComputeHeaders(endOfRequest: endOfRequest);
}
if (_leftToWrite == long.MinValue)
{
UnsafeNclNativeMethods.HttpApi.HTTP_VERB method = _requestContext.Request.GetKnownMethod();
if (method == UnsafeNclNativeMethods.HttpApi.HTTP_VERB.HttpVerbHEAD)
{
_leftToWrite = 0;
}
else if (_requestContext.Response.BoundaryType == BoundaryType.ContentLength)
{
_leftToWrite = _requestContext.Response.CalculatedLength;
}
else
{
_leftToWrite = -1; // unlimited
}
}
return flags;
}
public override unsafe void Write(byte[] buffer, int offset, int size)
{
if (buffer == null)
{
throw new ArgumentNullException("buffer");
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException("offset");
}
if (size < 0 || size > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException("size");
}
if (_closed)
{
throw new ObjectDisposedException(GetType().FullName);
}
// TODO: Verbose log parameters
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
if (size == 0 && _leftToWrite != 0)
{
return;
}
if (_leftToWrite >= 0 && size > _leftToWrite)
{
throw new InvalidOperationException(Resources.Exception_TooMuchWritten);
}
// TODO: Verbose log
uint statusCode;
uint dataToWrite = (uint)size;
SafeLocalFree bufferAsIntPtr = null;
IntPtr pBufferAsIntPtr = IntPtr.Zero;
bool sentHeaders = _requestContext.Response.SentHeaders;
try
{
if (size == 0)
{
// TODO: Is this code path accessible? Is this like a Flush?
statusCode = _requestContext.Response.SendHeaders(null, null, flags, false);
}
else
{
fixed (byte* pDataBuffer = buffer)
{
byte* pBuffer = pDataBuffer;
if (_requestContext.Response.BoundaryType == BoundaryType.Chunked)
{
// TODO:
// here we need some heuristics, some time it is definitely better to split this in 3 write calls
// but for small writes it is probably good enough to just copy the data internally.
string chunkHeader = size.ToString("x", CultureInfo.InvariantCulture);
dataToWrite = dataToWrite + (uint)(chunkHeader.Length + 4);
bufferAsIntPtr = SafeLocalFree.LocalAlloc((int)dataToWrite);
pBufferAsIntPtr = bufferAsIntPtr.DangerousGetHandle();
for (int i = 0; i < chunkHeader.Length; i++)
{
Marshal.WriteByte(pBufferAsIntPtr, i, (byte)chunkHeader[i]);
}
Marshal.WriteInt16(pBufferAsIntPtr, chunkHeader.Length, 0x0A0D);
Marshal.Copy(buffer, offset, IntPtrHelper.Add(pBufferAsIntPtr, chunkHeader.Length + 2), size);
Marshal.WriteInt16(pBufferAsIntPtr, (int)(dataToWrite - 2), 0x0A0D);
pBuffer = (byte*)pBufferAsIntPtr;
offset = 0;
}
UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK dataChunk = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
dataChunk.DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
dataChunk.fromMemory.pBuffer = (IntPtr)(pBuffer + offset);
dataChunk.fromMemory.BufferLength = dataToWrite;
flags |= _leftToWrite == size ? UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE : UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
if (!sentHeaders)
{
statusCode = _requestContext.Response.SendHeaders(&dataChunk, null, flags, false);
}
else
{
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody(
_requestContext.RequestQueueHandle,
_requestContext.RequestId,
(uint)flags,
1,
&dataChunk,
null,
SafeLocalFree.Zero,
0,
SafeNativeOverlapped.Zero,
IntPtr.Zero);
if (_requestContext.Server.IgnoreWriteExceptions)
{
statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS;
}
}
}
}
}
finally
{
if (bufferAsIntPtr != null)
{
// free unmanaged buffer
bufferAsIntPtr.Dispose();
}
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
{
Exception exception = new IOException(string.Empty, new WebListenerException((int)statusCode));
LogHelper.LogException(_requestContext.Logger, "Write", exception);
Abort();
throw exception;
}
UpdateWritenCount(dataToWrite);
// TODO: Verbose log data written
}
#if ASPNET50
public override unsafe IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
#else
public unsafe IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
#endif
{
if (buffer == null)
{
throw new ArgumentNullException("buffer");
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException("offset");
}
if (size < 0 || size > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException("size");
}
if (_closed)
{
throw new ObjectDisposedException(GetType().FullName);
}
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
if (size == 0 && _leftToWrite != 0)
{
ResponseStreamAsyncResult result = new ResponseStreamAsyncResult(this, state, callback);
result.Complete();
return result;
}
if (_leftToWrite >= 0 && size > _leftToWrite)
{
throw new InvalidOperationException(Resources.Exception_TooMuchWritten);
}
// TODO: Verbose log parameters
uint statusCode;
uint bytesSent = 0;
flags |= _leftToWrite == size ? UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE : UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
bool sentHeaders = _requestContext.Response.SentHeaders;
ResponseStreamAsyncResult asyncResult = new ResponseStreamAsyncResult(this, state, callback, buffer, offset, size, _requestContext.Response.BoundaryType == BoundaryType.Chunked, sentHeaders);
// Update m_LeftToWrite now so we can queue up additional BeginWrite's without waiting for EndWrite.
UpdateWritenCount((uint)((_requestContext.Response.BoundaryType == BoundaryType.Chunked) ? 0 : size));
try
{
if (!sentHeaders)
{
statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false);
bytesSent = asyncResult.BytesSent;
}
else
{
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody(
_requestContext.RequestQueueHandle,
_requestContext.RequestId,
(uint)flags,
asyncResult.DataChunkCount,
asyncResult.DataChunks,
&bytesSent,
SafeLocalFree.Zero,
0,
asyncResult.NativeOverlapped,
IntPtr.Zero);
}
}
catch (Exception e)
{
LogHelper.LogException(_requestContext.Logger, "BeginWrite", e);
asyncResult.Dispose();
Abort();
throw;
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
asyncResult.Dispose();
if (_requestContext.Server.IgnoreWriteExceptions && sentHeaders)
{
asyncResult.Complete();
}
else
{
Exception exception = new IOException(string.Empty, new WebListenerException((int)statusCode));
LogHelper.LogException(_requestContext.Logger, "BeginWrite", exception);
Abort();
throw exception;
}
}
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && WebListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
asyncResult.IOCompleted(statusCode, bytesSent);
}
// Last write, cache it for special cancellation handling.
if ((flags & UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0)
{
_lastWrite = asyncResult;
}
return asyncResult;
}
#if ASPNET50
public override void EndWrite(IAsyncResult asyncResult)
#else
public void EndWrite(IAsyncResult asyncResult)
#endif
{
if (asyncResult == null)
{
throw new ArgumentNullException("asyncResult");
}
ResponseStreamAsyncResult castedAsyncResult = asyncResult as ResponseStreamAsyncResult;
if (castedAsyncResult == null || castedAsyncResult.ResponseStream != this)
{
throw new ArgumentException(Resources.Exception_WrongIAsyncResult, "asyncResult");
}
if (castedAsyncResult.EndCalled)
{
throw new InvalidOperationException(Resources.Exception_EndCalledMultipleTimes);
}
castedAsyncResult.EndCalled = true;
try
{
// wait & then check for errors
// TODO: Graceful re-throw
castedAsyncResult.Task.Wait();
}
catch (Exception exception)
{
LogHelper.LogException(_requestContext.Logger, "EndWrite", exception);
Abort();
throw;
}
}
public override unsafe Task WriteAsync(byte[] buffer, int offset, int size, CancellationToken cancellationToken)
{
if (buffer == null)
{
throw new ArgumentNullException("buffer");
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException("offset");
}
if (size < 0 || size > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException("size");
}
if (_closed)
{
throw new ObjectDisposedException(GetType().FullName);
}
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
if (size == 0 && _leftToWrite != 0)
{
return Helpers.CompletedTask();
}
if (_leftToWrite >= 0 && size > _leftToWrite)
{
throw new InvalidOperationException(Resources.Exception_TooMuchWritten);
}
// TODO: Verbose log
if (cancellationToken.IsCancellationRequested)
{
return Helpers.CanceledTask<int>();
}
var cancellationRegistration = default(CancellationTokenRegistration);
if (cancellationToken.CanBeCanceled)
{
cancellationRegistration = cancellationToken.Register(RequestContext.AbortDelegate, _requestContext);
}
uint statusCode;
uint bytesSent = 0;
flags |= _leftToWrite == size ? UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE : UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
bool sentHeaders = _requestContext.Response.SentHeaders;
ResponseStreamAsyncResult asyncResult = new ResponseStreamAsyncResult(this, null, null, buffer, offset, size, _requestContext.Response.BoundaryType == BoundaryType.Chunked, sentHeaders, cancellationRegistration);
// Update m_LeftToWrite now so we can queue up additional BeginWrite's without waiting for EndWrite.
UpdateWritenCount((uint)((_requestContext.Response.BoundaryType == BoundaryType.Chunked) ? 0 : size));
try
{
if (!sentHeaders)
{
statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false);
bytesSent = asyncResult.BytesSent;
}
else
{
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody(
_requestContext.RequestQueueHandle,
_requestContext.RequestId,
(uint)flags,
asyncResult.DataChunkCount,
asyncResult.DataChunks,
&bytesSent,
SafeLocalFree.Zero,
0,
asyncResult.NativeOverlapped,
IntPtr.Zero);
}
}
catch (Exception e)
{
LogHelper.LogException(_requestContext.Logger, "WriteAsync", e);
asyncResult.Dispose();
Abort();
throw;
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
asyncResult.Dispose();
if (_requestContext.Server.IgnoreWriteExceptions && sentHeaders)
{
asyncResult.Complete();
}
else
{
Exception exception = new IOException(string.Empty, new WebListenerException((int)statusCode));
LogHelper.LogException(_requestContext.Logger, "WriteAsync", exception);
Abort();
throw exception;
}
}
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && WebListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
asyncResult.IOCompleted(statusCode, bytesSent);
}
// Last write, cache it for special cancellation handling.
if ((flags & UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0)
{
_lastWrite = asyncResult;
}
return asyncResult.Task;
}
internal unsafe Task SendFileAsync(string fileName, long offset, long? size, CancellationToken cancellationToken)
{
// 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.
if (string.IsNullOrWhiteSpace(fileName))
{
throw new ArgumentNullException("fileName");
}
if (_closed)
{
throw new ObjectDisposedException(GetType().FullName);
}
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
if (size == 0 && _leftToWrite != 0)
{
return Helpers.CompletedTask();
}
if (_leftToWrite >= 0 && size > _leftToWrite)
{
throw new InvalidOperationException(Resources.Exception_TooMuchWritten);
}
// TODO: Verbose log
if (cancellationToken.IsCancellationRequested)
{
return Helpers.CanceledTask<int>();
}
var cancellationRegistration = default(CancellationTokenRegistration);
if (cancellationToken.CanBeCanceled)
{
cancellationRegistration = cancellationToken.Register(RequestContext.AbortDelegate, _requestContext);
}
uint statusCode;
uint bytesSent = 0;
flags |= _leftToWrite == size ? UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE : UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
bool sentHeaders = _requestContext.Response.SentHeaders;
ResponseStreamAsyncResult asyncResult = new ResponseStreamAsyncResult(this, null, null, fileName, offset, size,
_requestContext.Response.BoundaryType == BoundaryType.Chunked, sentHeaders, cancellationRegistration);
long bytesWritten;
if (_requestContext.Response.BoundaryType == BoundaryType.Chunked)
{
bytesWritten = 0;
}
else if (size.HasValue)
{
bytesWritten = size.Value;
}
else
{
bytesWritten = asyncResult.FileLength - offset;
}
// Update m_LeftToWrite now so we can queue up additional calls to SendFileAsync.
UpdateWritenCount((uint)bytesWritten);
try
{
if (!sentHeaders)
{
statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false);
bytesSent = asyncResult.BytesSent;
}
else
{
// TODO: If opaque then include the buffer data flag.
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody(
_requestContext.RequestQueueHandle,
_requestContext.RequestId,
(uint)flags,
asyncResult.DataChunkCount,
asyncResult.DataChunks,
&bytesSent,
SafeLocalFree.Zero,
0,
asyncResult.NativeOverlapped,
IntPtr.Zero);
}
}
catch (Exception e)
{
LogHelper.LogException(_requestContext.Logger, "SendFileAsync", e);
asyncResult.Dispose();
Abort();
throw;
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
asyncResult.Dispose();
if (_requestContext.Server.IgnoreWriteExceptions && sentHeaders)
{
asyncResult.Complete();
}
else
{
Exception exception = new IOException(string.Empty, new WebListenerException((int)statusCode));
LogHelper.LogException(_requestContext.Logger, "SendFileAsync", exception);
Abort();
throw exception;
}
}
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && WebListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
asyncResult.IOCompleted(statusCode, bytesSent);
}
// Last write, cache it for special cancellation handling.
if ((flags & UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0)
{
_lastWrite = asyncResult;
}
return asyncResult.Task;
}
private void UpdateWritenCount(uint dataWritten)
{
if (!_inOpaqueMode)
{
if (_leftToWrite > 0)
{
// keep track of the data transferred
_leftToWrite -= dataWritten;
}
if (_leftToWrite == 0)
{
// in this case we already passed 0 as the flag, so we don't need to call HttpSendResponseEntityBody() when we Close()
_closed = true;
}
}
}
protected override unsafe void Dispose(bool disposing)
{
try
{
if (disposing)
{
if (_closed)
{
return;
}
_closed = true;
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite(endOfRequest: true);
if (_leftToWrite > 0 && !_inOpaqueMode)
{
_requestContext.Abort();
// TODO: Reduce this to a logged warning, it is thrown too late to be visible in user code.
LogHelper.LogError(_requestContext.Logger, "ResponseStream::Dispose", "Fewer bytes were written than were specified in the Content-Length.");
return;
}
bool sentHeaders = _requestContext.Response.SentHeaders;
if (sentHeaders && _leftToWrite == 0)
{
return;
}
uint statusCode = 0;
if ((_requestContext.Response.BoundaryType == BoundaryType.Chunked || _requestContext.Response.BoundaryType == BoundaryType.None) && (String.Compare(_requestContext.Request.Method, "HEAD", StringComparison.OrdinalIgnoreCase) != 0))
{
if (_requestContext.Response.BoundaryType == BoundaryType.None)
{
flags |= UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
}
fixed (void* pBuffer = ChunkTerminator)
{
UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK* pDataChunk = null;
if (_requestContext.Response.BoundaryType == BoundaryType.Chunked)
{
UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK dataChunk = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
dataChunk.DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
dataChunk.fromMemory.pBuffer = (IntPtr)pBuffer;
dataChunk.fromMemory.BufferLength = (uint)ChunkTerminator.Length;
pDataChunk = &dataChunk;
}
if (!sentHeaders)
{
statusCode = _requestContext.Response.SendHeaders(pDataChunk, null, flags, false);
}
else
{
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody(
_requestContext.RequestQueueHandle,
_requestContext.RequestId,
(uint)flags,
pDataChunk != null ? (ushort)1 : (ushort)0,
pDataChunk,
null,
SafeLocalFree.Zero,
0,
SafeNativeOverlapped.Zero,
IntPtr.Zero);
if (_requestContext.Server.IgnoreWriteExceptions)
{
statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS;
}
}
}
}
else
{
if (!sentHeaders)
{
statusCode = _requestContext.Response.SendHeaders(null, null, flags, false);
}
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF
// Don't throw for disconnects, we were already finished with the response.
&& statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_CONNECTION_INVALID
&& statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER)
{
Exception exception = new IOException(string.Empty, new WebListenerException((int)statusCode));
LogHelper.LogException(_requestContext.Logger, "Dispose", exception);
_requestContext.Abort();
throw exception;
}
_leftToWrite = 0;
}
}
finally
{
base.Dispose(disposing);
}
}
internal void SwitchToOpaqueMode()
{
_inOpaqueMode = true;
_leftToWrite = long.MaxValue;
}
// The final Content-Length async write can only be Canceled by CancelIoEx.
// Sync can only be Canceled by CancelSynchronousIo, but we don't attempt this right now.
[SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Justification =
"It is safe to ignore the return value on a cancel operation because the connection is being closed")]
internal unsafe void CancelLastWrite(SafeHandle requestQueueHandle)
{
ResponseStreamAsyncResult asyncState = _lastWrite;
if (asyncState != null && !asyncState.IsCompleted)
{
UnsafeNclNativeMethods.CancelIoEx(requestQueueHandle, asyncState.NativeOverlapped);
}
}
}
}