// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Buffers; using System.IO.Pipelines; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { public class Http1OutputProducer : IHttpOutputProducer, IHttpOutputAborter, IDisposable { private static readonly ReadOnlyMemory _continueBytes = new ReadOnlyMemory(Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n")); private static readonly byte[] _bytesHttpVersion11 = Encoding.ASCII.GetBytes("HTTP/1.1 "); private static readonly byte[] _bytesEndHeaders = Encoding.ASCII.GetBytes("\r\n\r\n"); private static readonly ReadOnlyMemory _endChunkedResponseBytes = new ReadOnlyMemory(Encoding.ASCII.GetBytes("0\r\n\r\n")); private readonly string _connectionId; private readonly ConnectionContext _connectionContext; private readonly IKestrelTrace _log; private readonly IHttpMinResponseDataRateFeature _minResponseDataRateFeature; private readonly TimingPipeFlusher _flusher; // This locks access to to all of the below fields private readonly object _contextLock = new object(); private bool _completed = false; private bool _aborted; private long _unflushedBytes; private readonly PipeWriter _pipeWriter; public Http1OutputProducer( PipeWriter pipeWriter, string connectionId, ConnectionContext connectionContext, IKestrelTrace log, ITimeoutControl timeoutControl, IHttpMinResponseDataRateFeature minResponseDataRateFeature) { _pipeWriter = pipeWriter; _connectionId = connectionId; _connectionContext = connectionContext; _log = log; _minResponseDataRateFeature = minResponseDataRateFeature; _flusher = new TimingPipeFlusher(pipeWriter, timeoutControl); } public Task WriteDataAsync(ReadOnlySpan buffer, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } return WriteAsync(buffer, cancellationToken); } public Task WriteStreamSuffixAsync() { return WriteAsync(_endChunkedResponseBytes.Span); } public Task FlushAsync(CancellationToken cancellationToken = default) { return WriteAsync(Constants.EmptyData, cancellationToken); } public Task WriteAsync(Func callback, T state, CancellationToken cancellationToken) { lock (_contextLock) { if (_completed) { return Task.CompletedTask; } var buffer = _pipeWriter; var bytesCommitted = callback(buffer, state); _unflushedBytes += bytesCommitted; } return FlushAsync(cancellationToken); } public void WriteResponseHeaders(int statusCode, string reasonPhrase, HttpResponseHeaders responseHeaders) { lock (_contextLock) { if (_completed) { return; } var buffer = _pipeWriter; var writer = new BufferWriter(buffer); writer.Write(_bytesHttpVersion11); var statusBytes = ReasonPhrases.ToStatusBytes(statusCode, reasonPhrase); writer.Write(statusBytes); responseHeaders.CopyTo(ref writer); writer.Write(_bytesEndHeaders); writer.Commit(); _unflushedBytes += writer.BytesCommitted; } } public void Dispose() { lock (_contextLock) { if (_completed) { return; } _log.ConnectionDisconnect(_connectionId); _completed = true; _pipeWriter.Complete(); } } public void Abort(ConnectionAbortedException error) { // Abort can be called after Dispose if there's a flush timeout. // It's important to still call _lifetimeFeature.Abort() in this case. lock (_contextLock) { if (_aborted) { return; } _aborted = true; _connectionContext.Abort(error); Dispose(); } } public Task Write100ContinueAsync() { return WriteAsync(_continueBytes.Span); } private Task WriteAsync( ReadOnlySpan buffer, CancellationToken cancellationToken = default) { lock (_contextLock) { if (_completed) { return Task.CompletedTask; } var writer = new BufferWriter(_pipeWriter); if (buffer.Length > 0) { writer.Write(buffer); _unflushedBytes += buffer.Length; } writer.Commit(); var bytesWritten = _unflushedBytes; _unflushedBytes = 0; return _flusher.FlushAsync( _minResponseDataRateFeature.MinDataRate, bytesWritten, this, cancellationToken); } } } }