| File: Core\IISHttpContext.IO.cs | Web Access |
| Project: src\aspnetcore\src\Servers\IIS\IIS\src\Microsoft.AspNetCore.Server.IIS.csproj (Microsoft.AspNetCore.Server.IIS) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Server.IIS.Core; internal partial class IISHttpContext { private long _consumedBytes; internal bool ClientDisconnected { get; private set; } /// <summary> /// Reads data from the Input pipe to the user. /// </summary> /// <param name="memory"></param> /// <param name="cancellationToken"></param> /// <returns></returns> internal async ValueTask<int> ReadAsync(Memory<byte> memory, CancellationToken cancellationToken) { if (!HasStartedConsumingRequestBody) { InitializeRequestIO(); } while (true) { var result = await _bodyInputPipe!.Reader.ReadAsync(cancellationToken); var readableBuffer = result.Buffer; try { if (!readableBuffer.IsEmpty) { var actual = Math.Min(readableBuffer.Length, memory.Length); readableBuffer = readableBuffer.Slice(0, actual); readableBuffer.CopyTo(memory.Span); return (int)actual; } else if (result.IsCompleted) { return 0; } } finally { _bodyInputPipe.Reader.AdvanceTo(readableBuffer.End, readableBuffer.End); } } } internal Task CopyToAsync(Stream destination, CancellationToken cancellationToken) { if (!HasStartedConsumingRequestBody) { InitializeRequestIO(); } return _bodyInputPipe!.Reader.CopyToAsync(destination, cancellationToken); } /// <summary> /// Writes data to the output pipe. /// </summary> /// <param name="memory"></param> /// <param name="cancellationToken"></param> /// <returns></returns> internal Task WriteAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken = default(CancellationToken)) { async Task WriteFirstAsync() { await InitializeResponse(flushHeaders: false); await _bodyOutput.WriteAsync(memory, cancellationToken); } return !HasResponseStarted ? WriteFirstAsync() : _bodyOutput.WriteAsync(memory, cancellationToken); } /// <summary> /// Flushes the data in the output pipe /// </summary> /// <param name="cancellationToken"></param> /// <returns></returns> internal Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken)) { async Task FlushFirstAsync() { await InitializeResponse(flushHeaders: true); await _bodyOutput.FlushAsync(cancellationToken); } return !HasResponseStarted ? FlushFirstAsync() : _bodyOutput.FlushAsync(cancellationToken); } private async Task ReadBody() { Exception? error = null; try { while (true) { var memory = _bodyInputPipe!.Writer.GetMemory(); var read = await AsyncIO!.ReadAsync(memory); // End of body if (read == 0) { break; } // Read was not canceled because of incoming write or IO stopping if (read != -1) { _consumedBytes += read; _bodyInputPipe.Writer.Advance(read); } if (_consumedBytes > MaxRequestBodySize) { IISBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge); } var result = await _bodyInputPipe.Writer.FlushAsync(); if (result.IsCompleted || result.IsCanceled) { break; } } } catch (ConnectionResetException ex) { AbortIO(clientDisconnect: true); error = ex; } catch (Http.BadHttpRequestException ex) { // Similar to a ConnectionResetException, this shouldn't be logged as an "Unexpected exception." // This should be logged by whatever catches it. Likely IISHttpContextOfT.ProcessRequestsAsync(). error = ex; } catch (Exception ex) { error = ex; Log.UnexpectedError(_logger, nameof(IISHttpContext), ex); } finally { _bodyInputPipe!.Writer.Complete(error); } } private async Task WriteBody(bool flush = false) { Exception? error = null; try { while (true) { var result = await _bodyOutput.Reader.ReadAsync(); var buffer = result.Buffer; try { if (_bodyOutput.Aborted) { break; } if (!buffer.IsEmpty) { // We are ignoring the result of the write operation here. If we fix this behavior, // we should also fix AsyncIOEngine.WriteDataOverChunksLimit. await AsyncIO!.WriteAsync(buffer); } // if request is done no need to flush, http.sys would do it for us if (result.IsCompleted) { // When is the reader completed? Is it always after the request pipeline exits? Or can CompleteAsync make it complete early? if (HasTrailers) { SetResponseTrailers(); } // Done with response, say there is no more data after writing trailers. await AsyncIO!.FlushAsync(moreData: false); break; } flush |= result.IsCanceled; if (flush) { await AsyncIO!.FlushAsync(moreData: true); flush = false; } } finally { _bodyOutput.Reader.AdvanceTo(buffer.End); } } } // We want to swallow IO exception and allow app to finish writing catch (ConnectionResetException) { AbortIO(clientDisconnect: true); } catch (Exception ex) { error = ex; Log.UnexpectedError(_logger, nameof(IISHttpContext), ex); } finally { _bodyOutput.Reader.Complete(error); } } internal void AbortIO(bool clientDisconnect) { var shouldScheduleCancellation = false; lock (_abortLock) { if (_requestAborted) { return; } shouldScheduleCancellation = _abortedCts != null; _requestAborted = true; } lock (_contextLock) { ClientDisconnected = clientDisconnect; } if (clientDisconnect) { Log.ConnectionDisconnect(_logger, ((IHttpConnectionFeature)this).ConnectionId); } _bodyOutput.Complete(); if (shouldScheduleCancellation) { // Potentially calling user code. CancelRequestAbortedToken logs any exceptions. CancelRequestAbortedToken(); } } private void CancelRequestAbortedToken() { ThreadPool.UnsafeQueueUserWorkItem(ctx => { try { CancellationTokenSource? localAbortCts = null; lock (ctx._abortLock) { if (ctx._abortedCts != null) { localAbortCts = ctx._abortedCts; ctx._abortedCts = null; } } // If we cancel the cts, we don't dispose as people may still be using // the cts. It also isn't necessary to dispose a canceled cts. localAbortCts?.Cancel(); } catch (Exception ex) { Log.ApplicationError(_logger, ((IHttpConnectionFeature)this).ConnectionId, TraceIdentifier!, ex); // TODO: Can TraceIdentifier be null? } }, this, preferLocal: false); } public void Abort(Exception reason) { _bodyOutput.Abort(); _streams.Abort(reason); NativeMethods.HttpCloseConnection(_requestNativeHandle); AbortIO(clientDisconnect: false); } }