185 lines
6.0 KiB
C#
185 lines
6.0 KiB
C#
// 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<byte> _continueBytes = new ReadOnlyMemory<byte>(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<byte> _endChunkedResponseBytes = new ReadOnlyMemory<byte>(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<byte> 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<T>(Func<PipeWriter, T, long> 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<PipeWriter>(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<byte> buffer,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
lock (_contextLock)
|
|
{
|
|
if (_completed)
|
|
{
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
var writer = new BufferWriter<PipeWriter>(_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);
|
|
}
|
|
}
|
|
}
|
|
}
|