// 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 (c) Microsoft Corporation. All rights reserved. // // ------------------------------------------------------------------------------ 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(); } 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(); } 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(); } 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); } } } }