// Simpler than Flush because it will never be called at the end of the request from Dispose. private unsafe Task FlushInternalAsync(ArraySegment <byte> data, CancellationToken cancellationToken) { if (_skipWrites) { return(Task.CompletedTask); } var started = _requestContext.Response.HasStarted; if (data.Count == 0 && started) { // No data to send and we've already sent the headers return(Task.CompletedTask); } if (cancellationToken.IsCancellationRequested) { Abort(ThrowWriteExceptions); return(Task.FromCanceled <int>(cancellationToken)); } // Make sure all validation is performed before this computes the headers var flags = ComputeLeftToWrite(data.Count); uint statusCode = 0; var chunked = _requestContext.Response.BoundaryType == BoundaryType.Chunked; var asyncResult = new ResponseStreamAsyncResult(this, data, chunked, cancellationToken); uint bytesSent = 0; try { if (!started) { statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false); bytesSent = asyncResult.BytesSent; } else { statusCode = HttpApi.HttpSendResponseEntityBody( RequestQueueHandle, RequestId, (uint)flags, asyncResult.DataChunkCount, asyncResult.DataChunks, &bytesSent, IntPtr.Zero, 0, asyncResult.NativeOverlapped, IntPtr.Zero); } } catch (Exception e) { LogHelper.LogException(Logger, "FlushAsync", e); asyncResult.Dispose(); Abort(); throw; } if (statusCode != ErrorCodes.ERROR_SUCCESS && statusCode != ErrorCodes.ERROR_IO_PENDING) { if (cancellationToken.IsCancellationRequested) { LogHelper.LogDebug(Logger, "FlushAsync", $"Write cancelled with error code: {statusCode}"); asyncResult.Cancel(ThrowWriteExceptions); } else if (ThrowWriteExceptions) { asyncResult.Dispose(); Exception exception = new IOException(string.Empty, new HttpSysException((int)statusCode)); LogHelper.LogException(Logger, "FlushAsync", exception); Abort(); throw exception; } else { // Abort the request but do not close the stream, let future writes complete silently LogHelper.LogDebug(Logger, "FlushAsync", $"Ignored write exception: {statusCode}"); asyncResult.FailSilently(); } } if (statusCode == ErrorCodes.ERROR_SUCCESS && HttpSysListener.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 & HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0) { _lastWrite = asyncResult; } return(asyncResult.Task); }
internal unsafe Task SendFileAsyncCore(string fileName, long offset, long?count, CancellationToken cancellationToken) { if (_skipWrites) { return(Task.CompletedTask); } var started = _requestContext.Response.HasStarted; if (count == 0 && started) { // No data to send and we've already sent the headers return(Task.CompletedTask); } if (cancellationToken.IsCancellationRequested) { Abort(ThrowWriteExceptions); return(Task.FromCanceled <int>(cancellationToken)); } // 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(count.Value); uint statusCode; uint bytesSent = 0; var chunked = _requestContext.Response.BoundaryType == BoundaryType.Chunked; var asyncResult = new ResponseStreamAsyncResult(this, fileStream, offset, count.Value, chunked, cancellationToken); try { if (!started) { statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false); bytesSent = asyncResult.BytesSent; } else { // TODO: If opaque then include the buffer data flag. statusCode = HttpApi.HttpSendResponseEntityBody( RequestQueueHandle, RequestId, (uint)flags, asyncResult.DataChunkCount, asyncResult.DataChunks, &bytesSent, IntPtr.Zero, 0, asyncResult.NativeOverlapped, IntPtr.Zero); } } catch (Exception e) { LogHelper.LogException(Logger, "SendFileAsync", e); asyncResult.Dispose(); Abort(); throw; } if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING) { if (cancellationToken.IsCancellationRequested) { LogHelper.LogDebug(Logger, "SendFileAsync", $"Write cancelled with error code: {statusCode}"); asyncResult.Cancel(ThrowWriteExceptions); } else if (ThrowWriteExceptions) { asyncResult.Dispose(); var exception = new IOException(string.Empty, new HttpSysException((int)statusCode)); LogHelper.LogException(Logger, "SendFileAsync", exception); Abort(); throw exception; } else { // Abort the request but do not close the stream, let future writes complete silently LogHelper.LogDebug(Logger, "SendFileAsync", $"Ignored write exception: {statusCode}"); asyncResult.FailSilently(); } } if (statusCode == ErrorCodes.ERROR_SUCCESS && HttpSysListener.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 & HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0) { _lastWrite = asyncResult; } return(asyncResult.Task); }
// We never expect endOfRequest and data at the same time private unsafe void FlushInternal(bool endOfRequest, ArraySegment <byte> data = new ArraySegment <byte>()) { Debug.Assert(!(endOfRequest && data.Count > 0), "Data is not supported at the end of the request."); if (_skipWrites) { return; } var started = _requestContext.Response.HasStarted; if (data.Count == 0 && started && !endOfRequest) { // No data to send and we've already sent the headers return; } // Make sure all validation is performed before this computes the headers var flags = ComputeLeftToWrite(data.Count, endOfRequest); if (endOfRequest && _leftToWrite > 0) { _requestContext.Abort(); // This is logged rather than thrown because it is too late for an exception to be visible in user code. LogHelper.LogError(Logger, "ResponseStream::Dispose", "Fewer bytes were written than were specified in the Content-Length."); return; } uint statusCode = 0; HttpApiTypes.HTTP_DATA_CHUNK[] dataChunks; var pinnedBuffers = PinDataBuffers(endOfRequest, data, out dataChunks); try { if (!started) { statusCode = _requestContext.Response.SendHeaders(dataChunks, null, flags, false); } else { fixed(HttpApiTypes.HTTP_DATA_CHUNK *pDataChunks = dataChunks) { statusCode = HttpApi.HttpSendResponseEntityBody( RequestQueueHandle, RequestId, (uint)flags, (ushort)dataChunks.Length, pDataChunks, null, IntPtr.Zero, 0, SafeNativeOverlapped.Zero, IntPtr.Zero); } } } finally { FreeDataBuffers(pinnedBuffers); } if (statusCode != ErrorCodes.ERROR_SUCCESS && statusCode != ErrorCodes.ERROR_HANDLE_EOF // Don't throw for disconnects, we were already finished with the response. && (!endOfRequest || (statusCode != ErrorCodes.ERROR_CONNECTION_INVALID && statusCode != ErrorCodes.ERROR_INVALID_PARAMETER))) { if (ThrowWriteExceptions) { var exception = new IOException(string.Empty, new HttpSysException((int)statusCode)); LogHelper.LogException(Logger, "Flush", exception); Abort(); throw exception; } else { // Abort the request but do not close the stream, let future writes complete silently LogHelper.LogDebug(Logger, "Flush", $"Ignored write exception: {statusCode}"); Abort(dispose: false); } } }