Introduce multiplexed bedrock types (#17213)
This commit is contained in:
parent
eb552a6fbd
commit
d8b6823111
|
|
@ -8,6 +8,19 @@ namespace Microsoft.AspNetCore.Connections
|
||||||
public AddressInUseException(string message) { }
|
public AddressInUseException(string message) { }
|
||||||
public AddressInUseException(string message, System.Exception inner) { }
|
public AddressInUseException(string message, System.Exception inner) { }
|
||||||
}
|
}
|
||||||
|
public abstract partial class BaseConnectionContext : System.IAsyncDisposable
|
||||||
|
{
|
||||||
|
protected BaseConnectionContext() { }
|
||||||
|
public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||||
|
public abstract string ConnectionId { get; set; }
|
||||||
|
public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; }
|
||||||
|
public abstract System.Collections.Generic.IDictionary<object, object> Items { get; set; }
|
||||||
|
public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||||
|
public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||||
|
public abstract void Abort();
|
||||||
|
public abstract void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason);
|
||||||
|
public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
|
||||||
|
}
|
||||||
public partial class ConnectionAbortedException : System.OperationCanceledException
|
public partial class ConnectionAbortedException : System.OperationCanceledException
|
||||||
{
|
{
|
||||||
public ConnectionAbortedException() { }
|
public ConnectionAbortedException() { }
|
||||||
|
|
@ -27,19 +40,12 @@ namespace Microsoft.AspNetCore.Connections
|
||||||
public static Microsoft.AspNetCore.Connections.IConnectionBuilder Use(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder, System.Func<Microsoft.AspNetCore.Connections.ConnectionContext, System.Func<System.Threading.Tasks.Task>, System.Threading.Tasks.Task> middleware) { throw null; }
|
public static Microsoft.AspNetCore.Connections.IConnectionBuilder Use(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder, System.Func<Microsoft.AspNetCore.Connections.ConnectionContext, System.Func<System.Threading.Tasks.Task>, System.Threading.Tasks.Task> middleware) { throw null; }
|
||||||
public static Microsoft.AspNetCore.Connections.IConnectionBuilder UseConnectionHandler<TConnectionHandler>(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder) where TConnectionHandler : Microsoft.AspNetCore.Connections.ConnectionHandler { throw null; }
|
public static Microsoft.AspNetCore.Connections.IConnectionBuilder UseConnectionHandler<TConnectionHandler>(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder) where TConnectionHandler : Microsoft.AspNetCore.Connections.ConnectionHandler { throw null; }
|
||||||
}
|
}
|
||||||
public abstract partial class ConnectionContext : System.IAsyncDisposable
|
public abstract partial class ConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable
|
||||||
{
|
{
|
||||||
protected ConnectionContext() { }
|
protected ConnectionContext() { }
|
||||||
public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
|
||||||
public abstract string ConnectionId { get; set; }
|
|
||||||
public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; }
|
|
||||||
public abstract System.Collections.Generic.IDictionary<object, object> Items { get; set; }
|
|
||||||
public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
|
||||||
public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
|
||||||
public abstract System.IO.Pipelines.IDuplexPipe Transport { get; set; }
|
public abstract System.IO.Pipelines.IDuplexPipe Transport { get; set; }
|
||||||
public virtual void Abort() { }
|
public override void Abort() { }
|
||||||
public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { }
|
public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { }
|
||||||
public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
|
|
||||||
}
|
}
|
||||||
public delegate System.Threading.Tasks.Task ConnectionDelegate(Microsoft.AspNetCore.Connections.ConnectionContext connection);
|
public delegate System.Threading.Tasks.Task ConnectionDelegate(Microsoft.AspNetCore.Connections.ConnectionContext connection);
|
||||||
public abstract partial class ConnectionHandler
|
public abstract partial class ConnectionHandler
|
||||||
|
|
@ -123,9 +129,40 @@ namespace Microsoft.AspNetCore.Connections
|
||||||
{
|
{
|
||||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.IConnectionListener> BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.IConnectionListener> BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
}
|
}
|
||||||
public partial interface IMultiplexedConnectionListenerFactory : Microsoft.AspNetCore.Connections.IConnectionListenerFactory
|
public partial interface IMultiplexedConnectionBuilder
|
||||||
{
|
{
|
||||||
|
System.IServiceProvider ApplicationServices { get; }
|
||||||
|
Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build();
|
||||||
|
Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func<Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate, Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate> middleware);
|
||||||
}
|
}
|
||||||
|
public partial interface IMultiplexedConnectionFactory
|
||||||
|
{
|
||||||
|
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.MultiplexedConnectionContext> ConnectAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
}
|
||||||
|
public partial interface IMultiplexedConnectionListener : System.IAsyncDisposable
|
||||||
|
{
|
||||||
|
System.Net.EndPoint EndPoint { get; }
|
||||||
|
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.MultiplexedConnectionContext> AcceptAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
System.Threading.Tasks.ValueTask UnbindAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
}
|
||||||
|
public partial interface IMultiplexedConnectionListenerFactory
|
||||||
|
{
|
||||||
|
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.IMultiplexedConnectionListener> BindAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
}
|
||||||
|
public partial class MultiplexedConnectionBuilder : Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder
|
||||||
|
{
|
||||||
|
public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) { }
|
||||||
|
public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
|
||||||
|
public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build() { throw null; }
|
||||||
|
public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func<Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate, Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate> middleware) { throw null; }
|
||||||
|
}
|
||||||
|
public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable
|
||||||
|
{
|
||||||
|
protected MultiplexedConnectionContext() { }
|
||||||
|
public abstract System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
public abstract System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
}
|
||||||
|
public delegate System.Threading.Tasks.Task MultiplexedConnectionDelegate(Microsoft.AspNetCore.Connections.MultiplexedConnectionContext connection);
|
||||||
[System.FlagsAttribute]
|
[System.FlagsAttribute]
|
||||||
public enum TransferFormat
|
public enum TransferFormat
|
||||||
{
|
{
|
||||||
|
|
@ -139,14 +176,6 @@ namespace Microsoft.AspNetCore.Connections
|
||||||
public override string ToString() { throw null; }
|
public override string ToString() { throw null; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
namespace Microsoft.AspNetCore.Connections.Abstractions.Features
|
|
||||||
{
|
|
||||||
public partial interface IQuicCreateStreamFeature
|
|
||||||
{
|
|
||||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> StartBidirectionalStreamAsync();
|
|
||||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> StartUnidirectionalStreamAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
namespace Microsoft.AspNetCore.Connections.Features
|
namespace Microsoft.AspNetCore.Connections.Features
|
||||||
{
|
{
|
||||||
public partial interface IConnectionCompleteFeature
|
public partial interface IConnectionCompleteFeature
|
||||||
|
|
@ -196,15 +225,18 @@ namespace Microsoft.AspNetCore.Connections.Features
|
||||||
{
|
{
|
||||||
System.Buffers.MemoryPool<byte> MemoryPool { get; }
|
System.Buffers.MemoryPool<byte> MemoryPool { get; }
|
||||||
}
|
}
|
||||||
public partial interface IQuicStreamFeature
|
public partial interface IProtocolErrorCodeFeature
|
||||||
|
{
|
||||||
|
long Error { get; set; }
|
||||||
|
}
|
||||||
|
public partial interface IStreamDirectionFeature
|
||||||
{
|
{
|
||||||
bool CanRead { get; }
|
bool CanRead { get; }
|
||||||
bool CanWrite { get; }
|
bool CanWrite { get; }
|
||||||
long StreamId { get; }
|
|
||||||
}
|
}
|
||||||
public partial interface IQuicStreamListenerFeature
|
public partial interface IStreamIdFeature
|
||||||
{
|
{
|
||||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> AcceptAsync();
|
long StreamId { get; }
|
||||||
}
|
}
|
||||||
public partial interface ITlsHandshakeFeature
|
public partial interface ITlsHandshakeFeature
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,19 @@ namespace Microsoft.AspNetCore.Connections
|
||||||
public AddressInUseException(string message) { }
|
public AddressInUseException(string message) { }
|
||||||
public AddressInUseException(string message, System.Exception inner) { }
|
public AddressInUseException(string message, System.Exception inner) { }
|
||||||
}
|
}
|
||||||
|
public abstract partial class BaseConnectionContext : System.IAsyncDisposable
|
||||||
|
{
|
||||||
|
protected BaseConnectionContext() { }
|
||||||
|
public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||||
|
public abstract string ConnectionId { get; set; }
|
||||||
|
public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; }
|
||||||
|
public abstract System.Collections.Generic.IDictionary<object, object> Items { get; set; }
|
||||||
|
public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||||
|
public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||||
|
public abstract void Abort();
|
||||||
|
public abstract void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason);
|
||||||
|
public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
|
||||||
|
}
|
||||||
public partial class ConnectionAbortedException : System.OperationCanceledException
|
public partial class ConnectionAbortedException : System.OperationCanceledException
|
||||||
{
|
{
|
||||||
public ConnectionAbortedException() { }
|
public ConnectionAbortedException() { }
|
||||||
|
|
@ -27,19 +40,12 @@ namespace Microsoft.AspNetCore.Connections
|
||||||
public static Microsoft.AspNetCore.Connections.IConnectionBuilder Use(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder, System.Func<Microsoft.AspNetCore.Connections.ConnectionContext, System.Func<System.Threading.Tasks.Task>, System.Threading.Tasks.Task> middleware) { throw null; }
|
public static Microsoft.AspNetCore.Connections.IConnectionBuilder Use(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder, System.Func<Microsoft.AspNetCore.Connections.ConnectionContext, System.Func<System.Threading.Tasks.Task>, System.Threading.Tasks.Task> middleware) { throw null; }
|
||||||
public static Microsoft.AspNetCore.Connections.IConnectionBuilder UseConnectionHandler<TConnectionHandler>(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder) where TConnectionHandler : Microsoft.AspNetCore.Connections.ConnectionHandler { throw null; }
|
public static Microsoft.AspNetCore.Connections.IConnectionBuilder UseConnectionHandler<TConnectionHandler>(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder) where TConnectionHandler : Microsoft.AspNetCore.Connections.ConnectionHandler { throw null; }
|
||||||
}
|
}
|
||||||
public abstract partial class ConnectionContext : System.IAsyncDisposable
|
public abstract partial class ConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable
|
||||||
{
|
{
|
||||||
protected ConnectionContext() { }
|
protected ConnectionContext() { }
|
||||||
public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
|
||||||
public abstract string ConnectionId { get; set; }
|
|
||||||
public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; }
|
|
||||||
public abstract System.Collections.Generic.IDictionary<object, object> Items { get; set; }
|
|
||||||
public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
|
||||||
public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
|
||||||
public abstract System.IO.Pipelines.IDuplexPipe Transport { get; set; }
|
public abstract System.IO.Pipelines.IDuplexPipe Transport { get; set; }
|
||||||
public virtual void Abort() { }
|
public override void Abort() { }
|
||||||
public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { }
|
public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { }
|
||||||
public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
|
|
||||||
}
|
}
|
||||||
public delegate System.Threading.Tasks.Task ConnectionDelegate(Microsoft.AspNetCore.Connections.ConnectionContext connection);
|
public delegate System.Threading.Tasks.Task ConnectionDelegate(Microsoft.AspNetCore.Connections.ConnectionContext connection);
|
||||||
public abstract partial class ConnectionHandler
|
public abstract partial class ConnectionHandler
|
||||||
|
|
@ -123,9 +129,40 @@ namespace Microsoft.AspNetCore.Connections
|
||||||
{
|
{
|
||||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.IConnectionListener> BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.IConnectionListener> BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
}
|
}
|
||||||
public partial interface IMultiplexedConnectionListenerFactory : Microsoft.AspNetCore.Connections.IConnectionListenerFactory
|
public partial interface IMultiplexedConnectionBuilder
|
||||||
{
|
{
|
||||||
|
System.IServiceProvider ApplicationServices { get; }
|
||||||
|
Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build();
|
||||||
|
Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func<Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate, Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate> middleware);
|
||||||
}
|
}
|
||||||
|
public partial interface IMultiplexedConnectionFactory
|
||||||
|
{
|
||||||
|
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.MultiplexedConnectionContext> ConnectAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
}
|
||||||
|
public partial interface IMultiplexedConnectionListener : System.IAsyncDisposable
|
||||||
|
{
|
||||||
|
System.Net.EndPoint EndPoint { get; }
|
||||||
|
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.MultiplexedConnectionContext> AcceptAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
System.Threading.Tasks.ValueTask UnbindAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
}
|
||||||
|
public partial interface IMultiplexedConnectionListenerFactory
|
||||||
|
{
|
||||||
|
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.IMultiplexedConnectionListener> BindAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
}
|
||||||
|
public partial class MultiplexedConnectionBuilder : Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder
|
||||||
|
{
|
||||||
|
public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) { }
|
||||||
|
public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
|
||||||
|
public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build() { throw null; }
|
||||||
|
public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func<Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate, Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate> middleware) { throw null; }
|
||||||
|
}
|
||||||
|
public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable
|
||||||
|
{
|
||||||
|
protected MultiplexedConnectionContext() { }
|
||||||
|
public abstract System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
public abstract System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
}
|
||||||
|
public delegate System.Threading.Tasks.Task MultiplexedConnectionDelegate(Microsoft.AspNetCore.Connections.MultiplexedConnectionContext connection);
|
||||||
[System.FlagsAttribute]
|
[System.FlagsAttribute]
|
||||||
public enum TransferFormat
|
public enum TransferFormat
|
||||||
{
|
{
|
||||||
|
|
@ -139,14 +176,6 @@ namespace Microsoft.AspNetCore.Connections
|
||||||
public override string ToString() { throw null; }
|
public override string ToString() { throw null; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
namespace Microsoft.AspNetCore.Connections.Abstractions.Features
|
|
||||||
{
|
|
||||||
public partial interface IQuicCreateStreamFeature
|
|
||||||
{
|
|
||||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> StartBidirectionalStreamAsync();
|
|
||||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> StartUnidirectionalStreamAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
namespace Microsoft.AspNetCore.Connections.Features
|
namespace Microsoft.AspNetCore.Connections.Features
|
||||||
{
|
{
|
||||||
public partial interface IConnectionCompleteFeature
|
public partial interface IConnectionCompleteFeature
|
||||||
|
|
@ -196,15 +225,18 @@ namespace Microsoft.AspNetCore.Connections.Features
|
||||||
{
|
{
|
||||||
System.Buffers.MemoryPool<byte> MemoryPool { get; }
|
System.Buffers.MemoryPool<byte> MemoryPool { get; }
|
||||||
}
|
}
|
||||||
public partial interface IQuicStreamFeature
|
public partial interface IProtocolErrorCodeFeature
|
||||||
|
{
|
||||||
|
long Error { get; set; }
|
||||||
|
}
|
||||||
|
public partial interface IStreamDirectionFeature
|
||||||
{
|
{
|
||||||
bool CanRead { get; }
|
bool CanRead { get; }
|
||||||
bool CanWrite { get; }
|
bool CanWrite { get; }
|
||||||
long StreamId { get; }
|
|
||||||
}
|
}
|
||||||
public partial interface IQuicStreamListenerFeature
|
public partial interface IStreamIdFeature
|
||||||
{
|
{
|
||||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> AcceptAsync();
|
long StreamId { get; }
|
||||||
}
|
}
|
||||||
public partial interface ITlsHandshakeFeature
|
public partial interface ITlsHandshakeFeature
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,19 @@ namespace Microsoft.AspNetCore.Connections
|
||||||
public AddressInUseException(string message) { }
|
public AddressInUseException(string message) { }
|
||||||
public AddressInUseException(string message, System.Exception inner) { }
|
public AddressInUseException(string message, System.Exception inner) { }
|
||||||
}
|
}
|
||||||
|
public abstract partial class BaseConnectionContext : System.IAsyncDisposable
|
||||||
|
{
|
||||||
|
protected BaseConnectionContext() { }
|
||||||
|
public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||||
|
public abstract string ConnectionId { get; set; }
|
||||||
|
public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; }
|
||||||
|
public abstract System.Collections.Generic.IDictionary<object, object> Items { get; set; }
|
||||||
|
public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||||
|
public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||||
|
public abstract void Abort();
|
||||||
|
public abstract void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason);
|
||||||
|
public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
|
||||||
|
}
|
||||||
public partial class ConnectionAbortedException : System.OperationCanceledException
|
public partial class ConnectionAbortedException : System.OperationCanceledException
|
||||||
{
|
{
|
||||||
public ConnectionAbortedException() { }
|
public ConnectionAbortedException() { }
|
||||||
|
|
@ -27,19 +40,12 @@ namespace Microsoft.AspNetCore.Connections
|
||||||
public static Microsoft.AspNetCore.Connections.IConnectionBuilder Use(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder, System.Func<Microsoft.AspNetCore.Connections.ConnectionContext, System.Func<System.Threading.Tasks.Task>, System.Threading.Tasks.Task> middleware) { throw null; }
|
public static Microsoft.AspNetCore.Connections.IConnectionBuilder Use(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder, System.Func<Microsoft.AspNetCore.Connections.ConnectionContext, System.Func<System.Threading.Tasks.Task>, System.Threading.Tasks.Task> middleware) { throw null; }
|
||||||
public static Microsoft.AspNetCore.Connections.IConnectionBuilder UseConnectionHandler<TConnectionHandler>(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder) where TConnectionHandler : Microsoft.AspNetCore.Connections.ConnectionHandler { throw null; }
|
public static Microsoft.AspNetCore.Connections.IConnectionBuilder UseConnectionHandler<TConnectionHandler>(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder) where TConnectionHandler : Microsoft.AspNetCore.Connections.ConnectionHandler { throw null; }
|
||||||
}
|
}
|
||||||
public abstract partial class ConnectionContext : System.IAsyncDisposable
|
public abstract partial class ConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable
|
||||||
{
|
{
|
||||||
protected ConnectionContext() { }
|
protected ConnectionContext() { }
|
||||||
public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
|
||||||
public abstract string ConnectionId { get; set; }
|
|
||||||
public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; }
|
|
||||||
public abstract System.Collections.Generic.IDictionary<object, object> Items { get; set; }
|
|
||||||
public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
|
||||||
public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
|
||||||
public abstract System.IO.Pipelines.IDuplexPipe Transport { get; set; }
|
public abstract System.IO.Pipelines.IDuplexPipe Transport { get; set; }
|
||||||
public virtual void Abort() { }
|
public override void Abort() { }
|
||||||
public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { }
|
public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { }
|
||||||
public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
|
|
||||||
}
|
}
|
||||||
public delegate System.Threading.Tasks.Task ConnectionDelegate(Microsoft.AspNetCore.Connections.ConnectionContext connection);
|
public delegate System.Threading.Tasks.Task ConnectionDelegate(Microsoft.AspNetCore.Connections.ConnectionContext connection);
|
||||||
public abstract partial class ConnectionHandler
|
public abstract partial class ConnectionHandler
|
||||||
|
|
@ -123,9 +129,40 @@ namespace Microsoft.AspNetCore.Connections
|
||||||
{
|
{
|
||||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.IConnectionListener> BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.IConnectionListener> BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
}
|
}
|
||||||
public partial interface IMultiplexedConnectionListenerFactory : Microsoft.AspNetCore.Connections.IConnectionListenerFactory
|
public partial interface IMultiplexedConnectionBuilder
|
||||||
{
|
{
|
||||||
|
System.IServiceProvider ApplicationServices { get; }
|
||||||
|
Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build();
|
||||||
|
Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func<Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate, Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate> middleware);
|
||||||
}
|
}
|
||||||
|
public partial interface IMultiplexedConnectionFactory
|
||||||
|
{
|
||||||
|
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.MultiplexedConnectionContext> ConnectAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
}
|
||||||
|
public partial interface IMultiplexedConnectionListener : System.IAsyncDisposable
|
||||||
|
{
|
||||||
|
System.Net.EndPoint EndPoint { get; }
|
||||||
|
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.MultiplexedConnectionContext> AcceptAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
System.Threading.Tasks.ValueTask UnbindAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
}
|
||||||
|
public partial interface IMultiplexedConnectionListenerFactory
|
||||||
|
{
|
||||||
|
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.IMultiplexedConnectionListener> BindAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
}
|
||||||
|
public partial class MultiplexedConnectionBuilder : Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder
|
||||||
|
{
|
||||||
|
public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) { }
|
||||||
|
public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
|
||||||
|
public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build() { throw null; }
|
||||||
|
public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func<Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate, Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate> middleware) { throw null; }
|
||||||
|
}
|
||||||
|
public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable
|
||||||
|
{
|
||||||
|
protected MultiplexedConnectionContext() { }
|
||||||
|
public abstract System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
public abstract System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||||
|
}
|
||||||
|
public delegate System.Threading.Tasks.Task MultiplexedConnectionDelegate(Microsoft.AspNetCore.Connections.MultiplexedConnectionContext connection);
|
||||||
[System.FlagsAttribute]
|
[System.FlagsAttribute]
|
||||||
public enum TransferFormat
|
public enum TransferFormat
|
||||||
{
|
{
|
||||||
|
|
@ -139,14 +176,6 @@ namespace Microsoft.AspNetCore.Connections
|
||||||
public override string ToString() { throw null; }
|
public override string ToString() { throw null; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
namespace Microsoft.AspNetCore.Connections.Abstractions.Features
|
|
||||||
{
|
|
||||||
public partial interface IQuicCreateStreamFeature
|
|
||||||
{
|
|
||||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> StartBidirectionalStreamAsync();
|
|
||||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> StartUnidirectionalStreamAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
namespace Microsoft.AspNetCore.Connections.Features
|
namespace Microsoft.AspNetCore.Connections.Features
|
||||||
{
|
{
|
||||||
public partial interface IConnectionCompleteFeature
|
public partial interface IConnectionCompleteFeature
|
||||||
|
|
@ -196,15 +225,18 @@ namespace Microsoft.AspNetCore.Connections.Features
|
||||||
{
|
{
|
||||||
System.Buffers.MemoryPool<byte> MemoryPool { get; }
|
System.Buffers.MemoryPool<byte> MemoryPool { get; }
|
||||||
}
|
}
|
||||||
public partial interface IQuicStreamFeature
|
public partial interface IProtocolErrorCodeFeature
|
||||||
|
{
|
||||||
|
long Error { get; set; }
|
||||||
|
}
|
||||||
|
public partial interface IStreamDirectionFeature
|
||||||
{
|
{
|
||||||
bool CanRead { get; }
|
bool CanRead { get; }
|
||||||
bool CanWrite { get; }
|
bool CanWrite { get; }
|
||||||
long StreamId { get; }
|
|
||||||
}
|
}
|
||||||
public partial interface IQuicStreamListenerFeature
|
public partial interface IStreamIdFeature
|
||||||
{
|
{
|
||||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> AcceptAsync();
|
long StreamId { get; }
|
||||||
}
|
}
|
||||||
public partial interface ITlsHandshakeFeature
|
public partial interface ITlsHandshakeFeature
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Connections
|
||||||
|
{
|
||||||
|
public abstract class BaseConnectionContext : IAsyncDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a unique identifier to represent this connection in trace logs.
|
||||||
|
/// </summary>
|
||||||
|
public abstract string ConnectionId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the collection of features provided by the server and middleware available on this connection.
|
||||||
|
/// </summary>
|
||||||
|
public abstract IFeatureCollection Features { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this connection.
|
||||||
|
/// </summary>
|
||||||
|
public abstract IDictionary<object, object> Items { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when the client connection is closed.
|
||||||
|
/// </summary>
|
||||||
|
public virtual CancellationToken ConnectionClosed { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the local endpoint for this connection.
|
||||||
|
/// </summary>
|
||||||
|
public virtual EndPoint LocalEndPoint { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the remote endpoint for this connection.
|
||||||
|
/// </summary>
|
||||||
|
public virtual EndPoint RemoteEndPoint { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aborts the underlying connection.
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Abort();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aborts the underlying connection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="abortReason">An optional <see cref="ConnectionAbortedException"/> describing the reason the connection is being terminated.</param>
|
||||||
|
public abstract void Abort(ConnectionAbortedException abortReason);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases resources for the underlying connection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <see cref="ValueTask"/> that completes when resources have been released.</returns>
|
||||||
|
public virtual ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -40,4 +40,4 @@ namespace Microsoft.AspNetCore.Connections
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,61 +2,26 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Net;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Connections.Features;
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Connections
|
namespace Microsoft.AspNetCore.Connections
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Encapsulates all information about an individual connection.
|
/// Encapsulates all information about an individual connection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class ConnectionContext : IAsyncDisposable
|
public abstract class ConnectionContext : BaseConnectionContext, IAsyncDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a unique identifier to represent this connection in trace logs.
|
|
||||||
/// </summary>
|
|
||||||
public abstract string ConnectionId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the collection of features provided by the server and middleware available on this connection.
|
|
||||||
/// </summary>
|
|
||||||
public abstract IFeatureCollection Features { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this connection.
|
|
||||||
/// </summary>
|
|
||||||
public abstract IDictionary<object, object> Items { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="IDuplexPipe"/> that can be used to read or write data on this connection.
|
/// Gets or sets the <see cref="IDuplexPipe"/> that can be used to read or write data on this connection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract IDuplexPipe Transport { get; set; }
|
public abstract IDuplexPipe Transport { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Triggered when the client connection is closed.
|
|
||||||
/// </summary>
|
|
||||||
public virtual CancellationToken ConnectionClosed { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the local endpoint for this connection.
|
|
||||||
/// </summary>
|
|
||||||
public virtual EndPoint LocalEndPoint { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the remote endpoint for this connection.
|
|
||||||
/// </summary>
|
|
||||||
public virtual EndPoint RemoteEndPoint { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Aborts the underlying connection.
|
/// Aborts the underlying connection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="abortReason">An optional <see cref="ConnectionAbortedException"/> describing the reason the connection is being terminated.</param>
|
/// <param name="abortReason">An optional <see cref="ConnectionAbortedException"/> describing the reason the connection is being terminated.</param>
|
||||||
public virtual void Abort(ConnectionAbortedException abortReason)
|
public override void Abort(ConnectionAbortedException abortReason)
|
||||||
{
|
{
|
||||||
// We expect this to be overridden, but this helps maintain back compat
|
// We expect this to be overridden, but this helps maintain back compat
|
||||||
// with implementations of ConnectionContext that predate the addition of
|
// with implementations of ConnectionContext that predate the addition of
|
||||||
|
|
@ -67,15 +32,6 @@ namespace Microsoft.AspNetCore.Connections
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Aborts the underlying connection.
|
/// Aborts the underlying connection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via ConnectionContext.Abort()."));
|
public override void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via ConnectionContext.Abort()."));
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Releases resources for the underlying connection.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A <see cref="ValueTask"/> that completes when resources have been released.</returns>
|
|
||||||
public virtual ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Connections.Features
|
namespace Microsoft.AspNetCore.Connections.Features
|
||||||
{
|
{
|
||||||
public interface IQuicStreamListenerFeature
|
public interface IProtocolErrorCodeFeature
|
||||||
{
|
{
|
||||||
ValueTask<ConnectionContext> AcceptAsync();
|
long Error { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
// 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.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Connections.Abstractions.Features
|
|
||||||
{
|
|
||||||
public interface IQuicCreateStreamFeature
|
|
||||||
{
|
|
||||||
ValueTask<ConnectionContext> StartUnidirectionalStreamAsync();
|
|
||||||
ValueTask<ConnectionContext> StartBidirectionalStreamAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,10 +3,9 @@
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Connections.Features
|
namespace Microsoft.AspNetCore.Connections.Features
|
||||||
{
|
{
|
||||||
public interface IQuicStreamFeature
|
public interface IStreamDirectionFeature
|
||||||
{
|
{
|
||||||
bool CanRead { get; }
|
bool CanRead { get; }
|
||||||
bool CanWrite { get; }
|
bool CanWrite { get; }
|
||||||
long StreamId { get; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Connections.Features
|
||||||
|
{
|
||||||
|
public interface IStreamIdFeature
|
||||||
|
{
|
||||||
|
long StreamId { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
// 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.Net;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Connections
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines an interface that represents a listener bound to a specific <see cref="EndPoint"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMultiplexedConnectionListener : IAsyncDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The endpoint that was bound. This may differ from the requested endpoint, such as when the caller requested that any free port be selected.
|
||||||
|
/// </summary>
|
||||||
|
EndPoint EndPoint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops listening for incoming connections.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
|
||||||
|
/// <returns>A <see cref="ValueTask"/> that represents the un-bind operation.</returns>
|
||||||
|
ValueTask UnbindAsync(CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begins an asynchronous operation to accept an incoming connection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="features">A feature collection to pass options when accepting a connection.</param>
|
||||||
|
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
|
||||||
|
/// <returns>A <see cref="ValueTask{ConnectionContext}"/> that completes when a connection is accepted, yielding the <see cref="MultiplexedConnectionContext" /> representing the connection.</returns>
|
||||||
|
ValueTask<MultiplexedConnectionContext> AcceptAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Connections
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines an interface that provides the mechanisms to configure a connection pipeline.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMultiplexedConnectionBuilder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="IServiceProvider"/> that provides access to the application's service container.
|
||||||
|
/// </summary>
|
||||||
|
IServiceProvider ApplicationServices { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a middleware delegate to the application's connection pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="middleware">The middleware delegate.</param>
|
||||||
|
/// <returns>The <see cref="IMultiplexedConnectionBuilder"/>.</returns>
|
||||||
|
IMultiplexedConnectionBuilder Use(Func<MultiplexedConnectionDelegate, MultiplexedConnectionDelegate> middleware);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds the delegate used by this application to process connections.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The connection handling delegate.</returns>
|
||||||
|
MultiplexedConnectionDelegate Build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// 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.Net;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Connections
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A factory abstraction for creating connections to an endpoint.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMultiplexedConnectionFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new connection to an endpoint.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoint">The <see cref="EndPoint"/> to connect to.</param>
|
||||||
|
/// <param name="features">A feature collection to pass options when connecting.</param>
|
||||||
|
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None" />.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A <see cref="ValueTask{TResult}" /> that represents the asynchronous connect, yielding the <see cref="MultiplexedConnectionContext" /> for the new connection when completed.
|
||||||
|
/// </returns>
|
||||||
|
ValueTask<MultiplexedConnectionContext> ConnectAsync(EndPoint endpoint, IFeatureCollection features = null, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,25 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Connections
|
namespace Microsoft.AspNetCore.Connections
|
||||||
{
|
{
|
||||||
public interface IMultiplexedConnectionListenerFactory : IConnectionListenerFactory
|
/// <summary>
|
||||||
|
/// Defines an interface that provides the mechanisms for binding to various types of <see cref="EndPoint"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMultiplexedConnectionListenerFactory
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an <see cref="IMultiplexedConnectionListener"/> bound to the specified <see cref="EndPoint"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoint">The <see cref="EndPoint" /> to bind to.</param>
|
||||||
|
/// <param name="features">A feature collection to pass options when binding.</param>
|
||||||
|
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
|
||||||
|
/// <returns>A <see cref="ValueTask{IMultiplexedConnectionListener}"/> that completes when the listener has been bound, yielding a <see cref="IMultiplexedConnectionListener" /> representing the new listener.</returns>
|
||||||
|
ValueTask<IMultiplexedConnectionListener> BindAsync(EndPoint endpoint, IFeatureCollection features = null, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Connections
|
||||||
|
{
|
||||||
|
public class MultiplexedConnectionBuilder : IMultiplexedConnectionBuilder
|
||||||
|
{
|
||||||
|
private readonly IList<Func<MultiplexedConnectionDelegate, MultiplexedConnectionDelegate>> _components = new List<Func<MultiplexedConnectionDelegate, MultiplexedConnectionDelegate>>();
|
||||||
|
|
||||||
|
public IServiceProvider ApplicationServices { get; }
|
||||||
|
|
||||||
|
public MultiplexedConnectionBuilder(IServiceProvider applicationServices)
|
||||||
|
{
|
||||||
|
ApplicationServices = applicationServices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IMultiplexedConnectionBuilder Use(Func<MultiplexedConnectionDelegate, MultiplexedConnectionDelegate> middleware)
|
||||||
|
{
|
||||||
|
_components.Add(middleware);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultiplexedConnectionDelegate Build()
|
||||||
|
{
|
||||||
|
MultiplexedConnectionDelegate app = features =>
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var component in _components.Reverse())
|
||||||
|
{
|
||||||
|
app = component(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// 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.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Connections
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Encapsulates all information about a multiplexed connection.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class MultiplexedConnectionContext : BaseConnectionContext, IAsyncDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously accept an incoming stream on the connection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public abstract ValueTask<ConnectionContext> AcceptAsync(CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an outbound connection
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="features"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public abstract ValueTask<ConnectionContext> ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Connections
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A function that can process a connection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connection">A <see cref="MultiplexedConnectionContext" /> representing the connection.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that represents the connection lifetime. When the task completes, the connection will be closed.</returns>
|
||||||
|
public delegate Task MultiplexedConnectionDelegate(MultiplexedConnectionContext connection);
|
||||||
|
}
|
||||||
|
|
@ -96,6 +96,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
public partial class KestrelServer : Microsoft.AspNetCore.Hosting.Server.IServer, System.IDisposable
|
public partial class KestrelServer : Microsoft.AspNetCore.Hosting.Server.IServer, System.IDisposable
|
||||||
{
|
{
|
||||||
public KestrelServer(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions> options, System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Connections.IConnectionListenerFactory> transportFactories, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
|
public KestrelServer(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions> options, System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Connections.IConnectionListenerFactory> transportFactories, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
|
||||||
|
public KestrelServer(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions> options, System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Connections.IConnectionListenerFactory> transportFactories, System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Connections.IMultiplexedConnectionListenerFactory> multiplexedFactories, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
|
||||||
public Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
|
public Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
|
||||||
public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions Options { get { throw null; } }
|
public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions Options { get { throw null; } }
|
||||||
public void Dispose() { }
|
public void Dispose() { }
|
||||||
|
|
@ -149,7 +150,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
public void ListenUnixSocket(string socketPath) { }
|
public void ListenUnixSocket(string socketPath) { }
|
||||||
public void ListenUnixSocket(string socketPath, System.Action<Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions> configure) { }
|
public void ListenUnixSocket(string socketPath, System.Action<Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions> configure) { }
|
||||||
}
|
}
|
||||||
public partial class ListenOptions : Microsoft.AspNetCore.Connections.IConnectionBuilder
|
public partial class ListenOptions : Microsoft.AspNetCore.Connections.IConnectionBuilder, Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder
|
||||||
{
|
{
|
||||||
internal ListenOptions() { }
|
internal ListenOptions() { }
|
||||||
public System.IServiceProvider ApplicationServices { get { throw null; } }
|
public System.IServiceProvider ApplicationServices { get { throw null; } }
|
||||||
|
|
@ -159,6 +160,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
public Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols Protocols { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
public Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols Protocols { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||||
public string SocketPath { get { throw null; } }
|
public string SocketPath { get { throw null; } }
|
||||||
public Microsoft.AspNetCore.Connections.ConnectionDelegate Build() { throw null; }
|
public Microsoft.AspNetCore.Connections.ConnectionDelegate Build() { throw null; }
|
||||||
|
Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder.Build() { throw null; }
|
||||||
|
Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder.Use(System.Func<Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate, Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate> middleware) { throw null; }
|
||||||
public override string ToString() { throw null; }
|
public override string ToString() { throw null; }
|
||||||
public Microsoft.AspNetCore.Connections.IConnectionBuilder Use(System.Func<Microsoft.AspNetCore.Connections.ConnectionDelegate, Microsoft.AspNetCore.Connections.ConnectionDelegate> middleware) { throw null; }
|
public Microsoft.AspNetCore.Connections.IConnectionBuilder Use(System.Func<Microsoft.AspNetCore.Connections.ConnectionDelegate, Microsoft.AspNetCore.Connections.ConnectionDelegate> middleware) { throw null; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -527,6 +527,24 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
|
||||||
<data name="Http2ErrorMaxStreams" xml:space="preserve">
|
<data name="Http2ErrorMaxStreams" xml:space="preserve">
|
||||||
<value>A new stream was refused because this connection has reached its stream limit.</value>
|
<value>A new stream was refused because this connection has reached its stream limit.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Http3ErrorConnectMustNotSendSchemeOrPath" xml:space="preserve">
|
||||||
|
<value>CONNECT requests must not send :scheme or :path headers.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Http3StreamErrorSchemeMismatch" xml:space="preserve">
|
||||||
|
<value>The request :scheme header '{requestScheme}' does not match the transport scheme '{transportScheme}'.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Http3ErrorMethodInvalid" xml:space="preserve">
|
||||||
|
<value>The Method '{method}' is invalid.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Http3StreamErrorPathInvalid" xml:space="preserve">
|
||||||
|
<value>The request :path is invalid: '{path}'</value>
|
||||||
|
</data>
|
||||||
|
<data name="Http3StreamErrorLessDataThanLength" xml:space="preserve">
|
||||||
|
<value>Less data received than specified in the Content-Length header.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Http3StreamErrorMoreDataThanLength" xml:space="preserve">
|
||||||
|
<value>More data received than specified in the Content-Length header.</value>
|
||||||
|
</data>
|
||||||
<data name="GreaterThanZeroRequired" xml:space="preserve">
|
<data name="GreaterThanZeroRequired" xml:space="preserve">
|
||||||
<value>A value greater than zero is required.</value>
|
<value>A value greater than zero is required.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
|
||||||
// Add the connection to the connection manager before we queue it for execution
|
// Add the connection to the connection manager before we queue it for execution
|
||||||
var id = Interlocked.Increment(ref _lastConnectionId);
|
var id = Interlocked.Increment(ref _lastConnectionId);
|
||||||
var kestrelConnection = new KestrelConnection(id, _serviceContext, _connectionDelegate, connection, Log);
|
var kestrelConnection = new KestrelConnection<ConnectionContext>(id, _serviceContext, c => _connectionDelegate(c), connection, Log);
|
||||||
|
|
||||||
_serviceContext.ConnectionManager.AddConnection(id, kestrelConnection);
|
_serviceContext.ConnectionManager.AddConnection(id, kestrelConnection);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -511,7 +511,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
|
public virtual void OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
|
||||||
{
|
{
|
||||||
_requestHeadersParsed++;
|
_requestHeadersParsed++;
|
||||||
if (_requestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount)
|
if (_requestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount)
|
||||||
|
|
@ -1196,7 +1196,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
foreach (var option in ServerOptions.ListenOptions)
|
foreach (var option in ServerOptions.ListenOptions)
|
||||||
{
|
{
|
||||||
if (option.Protocols == HttpProtocols.Http3)
|
if ((option.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3)
|
||||||
{
|
{
|
||||||
responseHeaders.HeaderAltSvc = $"h3-25=\":{option.IPEndPoint.Port}\"; ma=84600";
|
responseHeaders.HeaderAltSvc = $"h3-25=\":{option.IPEndPoint.Port}\"; ma=84600";
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Connections.Features;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
|
{
|
||||||
|
internal class DefaultStreamDirectionFeature : IStreamDirectionFeature
|
||||||
|
{
|
||||||
|
public DefaultStreamDirectionFeature(bool canRead, bool canWrite)
|
||||||
|
{
|
||||||
|
CanRead = canRead;
|
||||||
|
CanWrite = canWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanRead { get; }
|
||||||
|
|
||||||
|
public bool CanWrite { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,39 +3,50 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Connections.Abstractions.Features;
|
|
||||||
using Microsoft.AspNetCore.Connections.Features;
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
{
|
{
|
||||||
internal class Http3Connection : IRequestProcessor
|
internal class Http3Connection : IRequestProcessor, ITimeoutHandler
|
||||||
{
|
{
|
||||||
public HttpConnectionContext Context { get; private set; }
|
|
||||||
|
|
||||||
public DynamicTable DynamicTable { get; set; }
|
public DynamicTable DynamicTable { get; set; }
|
||||||
|
|
||||||
public Http3ControlStream ControlStream { get; set; }
|
public Http3ControlStream ControlStream { get; set; }
|
||||||
public Http3ControlStream EncoderStream { get; set; }
|
public Http3ControlStream EncoderStream { get; set; }
|
||||||
public Http3ControlStream DecoderStream { get; set; }
|
public Http3ControlStream DecoderStream { get; set; }
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<long, Http3Stream> _streams = new ConcurrentDictionary<long, Http3Stream>();
|
internal readonly Dictionary<long, Http3Stream> _streams = new Dictionary<long, Http3Stream>();
|
||||||
|
|
||||||
private long _highestOpenedStreamId; // TODO lock to access
|
private long _highestOpenedStreamId; // TODO lock to access
|
||||||
private volatile bool _haveSentGoAway;
|
private volatile bool _haveSentGoAway;
|
||||||
private object _sync = new object();
|
private object _sync = new object();
|
||||||
|
private MultiplexedConnectionContext _multiplexedContext;
|
||||||
|
private readonly Http3ConnectionContext _context;
|
||||||
|
private readonly ISystemClock _systemClock;
|
||||||
|
private readonly TimeoutControl _timeoutControl;
|
||||||
|
private bool _aborted;
|
||||||
|
private object _protocolSelectionLock = new object();
|
||||||
|
|
||||||
public Http3Connection(HttpConnectionContext context)
|
public Http3Connection(Http3ConnectionContext context)
|
||||||
{
|
{
|
||||||
Context = context;
|
_multiplexedContext = context.ConnectionContext;
|
||||||
|
_context = context;
|
||||||
DynamicTable = new DynamicTable(0);
|
DynamicTable = new DynamicTable(0);
|
||||||
|
_systemClock = context.ServiceContext.SystemClock;
|
||||||
|
_timeoutControl = new TimeoutControl(this);
|
||||||
|
_context.TimeoutControl ??= _timeoutControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal long HighestStreamId
|
internal long HighestStreamId
|
||||||
|
|
@ -53,10 +64,133 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application)
|
private IKestrelTrace Log => _context.ServiceContext.Log;
|
||||||
{
|
|
||||||
var streamListenerFeature = Context.ConnectionFeatures.Get<IQuicStreamListenerFeature>();
|
|
||||||
|
|
||||||
|
public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> httpApplication)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs.
|
||||||
|
_timeoutControl.Initialize(_systemClock.UtcNowTicks);
|
||||||
|
|
||||||
|
var connectionHeartbeatFeature = _context.ConnectionFeatures.Get<IConnectionHeartbeatFeature>();
|
||||||
|
var connectionLifetimeNotificationFeature = _context.ConnectionFeatures.Get<IConnectionLifetimeNotificationFeature>();
|
||||||
|
|
||||||
|
// These features should never be null in Kestrel itself, if this middleware is ever refactored to run outside of kestrel,
|
||||||
|
// we'll need to handle these missing.
|
||||||
|
Debug.Assert(connectionHeartbeatFeature != null, nameof(IConnectionHeartbeatFeature) + " is missing!");
|
||||||
|
Debug.Assert(connectionLifetimeNotificationFeature != null, nameof(IConnectionLifetimeNotificationFeature) + " is missing!");
|
||||||
|
|
||||||
|
// Register the various callbacks once we're going to start processing requests
|
||||||
|
|
||||||
|
// The heart beat for various timeouts
|
||||||
|
connectionHeartbeatFeature?.OnHeartbeat(state => ((Http3Connection)state).Tick(), this);
|
||||||
|
|
||||||
|
// Register for graceful shutdown of the server
|
||||||
|
using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((Http3Connection)state).StopProcessingNextRequest(), this);
|
||||||
|
|
||||||
|
// Register for connection close
|
||||||
|
using var closedRegistration = _context.ConnectionContext.ConnectionClosed.Register(state => ((Http3Connection)state).OnConnectionClosed(), this);
|
||||||
|
|
||||||
|
await InnerProcessRequestsAsync(httpApplication);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.LogCritical(0, ex, $"Unexpected exception in {nameof(Http3Connection)}.{nameof(ProcessRequestsAsync)}.");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For testing only
|
||||||
|
internal void Initialize()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopProcessingNextRequest()
|
||||||
|
{
|
||||||
|
bool previousState;
|
||||||
|
lock (_protocolSelectionLock)
|
||||||
|
{
|
||||||
|
previousState = _aborted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO figure out how to gracefully close next requests
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnConnectionClosed()
|
||||||
|
{
|
||||||
|
bool previousState;
|
||||||
|
lock (_protocolSelectionLock)
|
||||||
|
{
|
||||||
|
previousState = _aborted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO figure out how to gracefully close next requests
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Abort(ConnectionAbortedException ex)
|
||||||
|
{
|
||||||
|
bool previousState;
|
||||||
|
|
||||||
|
lock (_protocolSelectionLock)
|
||||||
|
{
|
||||||
|
previousState = _aborted;
|
||||||
|
_aborted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!previousState)
|
||||||
|
{
|
||||||
|
InnerAbort(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Tick()
|
||||||
|
{
|
||||||
|
if (_aborted)
|
||||||
|
{
|
||||||
|
// It's safe to check for timeouts on a dead connection,
|
||||||
|
// but try not to in order to avoid extraneous logs.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's safe to use UtcNowUnsynchronized since Tick is called by the Heartbeat.
|
||||||
|
var now = _systemClock.UtcNowUnsynchronized;
|
||||||
|
_timeoutControl.Tick(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnTimeout(TimeoutReason reason)
|
||||||
|
{
|
||||||
|
// In the cases that don't log directly here, we expect the setter of the timeout to also be the input
|
||||||
|
// reader, so when the read is canceled or aborted, the reader should write the appropriate log.
|
||||||
|
switch (reason)
|
||||||
|
{
|
||||||
|
case TimeoutReason.KeepAlive:
|
||||||
|
StopProcessingNextRequest();
|
||||||
|
break;
|
||||||
|
case TimeoutReason.RequestHeaders:
|
||||||
|
HandleRequestHeadersTimeout();
|
||||||
|
break;
|
||||||
|
case TimeoutReason.ReadDataRate:
|
||||||
|
HandleReadDataRateTimeout();
|
||||||
|
break;
|
||||||
|
case TimeoutReason.WriteDataRate:
|
||||||
|
Log.ResponseMinimumDataRateNotSatisfied(_context.ConnectionId, "" /*TraceIdentifier*/); // TODO trace identifier.
|
||||||
|
Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied));
|
||||||
|
break;
|
||||||
|
case TimeoutReason.RequestBodyDrain:
|
||||||
|
case TimeoutReason.TimeoutFeature:
|
||||||
|
Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedOutByServer));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Debug.Assert(false, "Invalid TimeoutReason");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task InnerProcessRequestsAsync<TContext>(IHttpApplication<TContext> application)
|
||||||
|
{
|
||||||
// Start other three unidirectional streams here.
|
// Start other three unidirectional streams here.
|
||||||
var controlTask = CreateControlStream(application);
|
var controlTask = CreateControlStream(application);
|
||||||
var encoderTask = CreateEncoderStream(application);
|
var encoderTask = CreateEncoderStream(application);
|
||||||
|
|
@ -66,29 +200,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var connectionContext = await streamListenerFeature.AcceptAsync();
|
var streamContext = await _multiplexedContext.AcceptAsync();
|
||||||
if (connectionContext == null || _haveSentGoAway)
|
if (streamContext == null || _haveSentGoAway)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpConnectionContext = new HttpConnectionContext
|
var quicStreamFeature = streamContext.Features.Get<IStreamDirectionFeature>();
|
||||||
|
var streamIdFeature = streamContext.Features.Get<IStreamIdFeature>();
|
||||||
|
|
||||||
|
Debug.Assert(quicStreamFeature != null);
|
||||||
|
|
||||||
|
var httpConnectionContext = new Http3StreamContext
|
||||||
{
|
{
|
||||||
ConnectionId = connectionContext.ConnectionId,
|
ConnectionId = streamContext.ConnectionId,
|
||||||
ConnectionContext = connectionContext,
|
StreamContext = streamContext,
|
||||||
Protocols = Context.Protocols,
|
// TODO connection context is null here. Should we set it to anything?
|
||||||
ServiceContext = Context.ServiceContext,
|
ServiceContext = _context.ServiceContext,
|
||||||
ConnectionFeatures = connectionContext.Features,
|
ConnectionFeatures = streamContext.Features,
|
||||||
MemoryPool = Context.MemoryPool,
|
MemoryPool = _context.MemoryPool,
|
||||||
Transport = connectionContext.Transport,
|
Transport = streamContext.Transport,
|
||||||
TimeoutControl = Context.TimeoutControl,
|
TimeoutControl = _context.TimeoutControl,
|
||||||
LocalEndPoint = connectionContext.LocalEndPoint as IPEndPoint,
|
LocalEndPoint = streamContext.LocalEndPoint as IPEndPoint,
|
||||||
RemoteEndPoint = connectionContext.RemoteEndPoint as IPEndPoint
|
RemoteEndPoint = streamContext.RemoteEndPoint as IPEndPoint
|
||||||
};
|
};
|
||||||
|
|
||||||
var streamFeature = httpConnectionContext.ConnectionFeatures.Get<IQuicStreamFeature>();
|
if (!quicStreamFeature.CanWrite)
|
||||||
|
|
||||||
if (!streamFeature.CanWrite)
|
|
||||||
{
|
{
|
||||||
// Unidirectional stream
|
// Unidirectional stream
|
||||||
var stream = new Http3ControlStream<TContext>(application, this, httpConnectionContext);
|
var stream = new Http3ControlStream<TContext>(application, this, httpConnectionContext);
|
||||||
|
|
@ -97,13 +234,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Keep track of highest stream id seen for GOAWAY
|
// Keep track of highest stream id seen for GOAWAY
|
||||||
var streamId = streamFeature.StreamId;
|
var streamId = streamIdFeature.StreamId;
|
||||||
|
|
||||||
HighestStreamId = streamId;
|
HighestStreamId = streamId;
|
||||||
|
|
||||||
var http3Stream = new Http3Stream<TContext>(application, this, httpConnectionContext);
|
var http3Stream = new Http3Stream<TContext>(application, this, httpConnectionContext);
|
||||||
var stream = http3Stream;
|
var stream = http3Stream;
|
||||||
_streams[streamId] = http3Stream;
|
lock (_streams)
|
||||||
|
{
|
||||||
|
_streams[streamId] = http3Stream;
|
||||||
|
}
|
||||||
ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false);
|
ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -111,14 +250,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// Abort all streams as connection has shutdown.
|
// Abort all streams as connection has shutdown.
|
||||||
foreach (var stream in _streams.Values)
|
lock (_streams)
|
||||||
{
|
{
|
||||||
stream.Abort(new ConnectionAbortedException("Connection is shutting down."));
|
foreach (var stream in _streams.Values)
|
||||||
|
{
|
||||||
|
stream.Abort(new ConnectionAbortedException("Connection is shutting down."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ControlStream.Abort(new ConnectionAbortedException("Connection is shutting down."));
|
ControlStream?.Abort(new ConnectionAbortedException("Connection is shutting down."));
|
||||||
EncoderStream.Abort(new ConnectionAbortedException("Connection is shutting down."));
|
EncoderStream?.Abort(new ConnectionAbortedException("Connection is shutting down."));
|
||||||
DecoderStream.Abort(new ConnectionAbortedException("Connection is shutting down."));
|
DecoderStream?.Abort(new ConnectionAbortedException("Connection is shutting down."));
|
||||||
|
|
||||||
await controlTask;
|
await controlTask;
|
||||||
await encoderTask;
|
await encoderTask;
|
||||||
|
|
@ -150,28 +292,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
|
|
||||||
private async ValueTask<Http3ControlStream> CreateNewUnidirectionalStreamAsync<TContext>(IHttpApplication<TContext> application)
|
private async ValueTask<Http3ControlStream> CreateNewUnidirectionalStreamAsync<TContext>(IHttpApplication<TContext> application)
|
||||||
{
|
{
|
||||||
var connectionContext = await Context.ConnectionFeatures.Get<IQuicCreateStreamFeature>().StartUnidirectionalStreamAsync();
|
var features = new FeatureCollection();
|
||||||
var httpConnectionContext = new HttpConnectionContext
|
features.Set<IStreamDirectionFeature>(new DefaultStreamDirectionFeature(canRead: false, canWrite: true));
|
||||||
|
var streamContext = await _multiplexedContext.ConnectAsync(features);
|
||||||
|
var httpConnectionContext = new Http3StreamContext
|
||||||
{
|
{
|
||||||
//ConnectionId = "", TODO getting stream ID from stream that isn't started throws an exception.
|
//ConnectionId = "", TODO getting stream ID from stream that isn't started throws an exception.
|
||||||
ConnectionContext = connectionContext,
|
StreamContext = streamContext,
|
||||||
Protocols = Context.Protocols,
|
Protocols = HttpProtocols.Http3,
|
||||||
ServiceContext = Context.ServiceContext,
|
ServiceContext = _context.ServiceContext,
|
||||||
ConnectionFeatures = connectionContext.Features,
|
ConnectionFeatures = streamContext.Features,
|
||||||
MemoryPool = Context.MemoryPool,
|
MemoryPool = _context.MemoryPool,
|
||||||
Transport = connectionContext.Transport,
|
Transport = streamContext.Transport,
|
||||||
TimeoutControl = Context.TimeoutControl,
|
TimeoutControl = _context.TimeoutControl,
|
||||||
LocalEndPoint = connectionContext.LocalEndPoint as IPEndPoint,
|
LocalEndPoint = streamContext.LocalEndPoint as IPEndPoint,
|
||||||
RemoteEndPoint = connectionContext.RemoteEndPoint as IPEndPoint
|
RemoteEndPoint = streamContext.RemoteEndPoint as IPEndPoint
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Http3ControlStream<TContext>(application, this, httpConnectionContext);
|
return new Http3ControlStream<TContext>(application, this, httpConnectionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StopProcessingNextRequest()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleRequestHeadersTimeout()
|
public void HandleRequestHeadersTimeout()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +328,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Abort(ConnectionAbortedException ex)
|
private void InnerAbort(ConnectionAbortedException ex)
|
||||||
{
|
{
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
{
|
{
|
||||||
|
|
@ -202,10 +342,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
_haveSentGoAway = true;
|
_haveSentGoAway = true;
|
||||||
|
|
||||||
// Abort currently active streams
|
// Abort currently active streams
|
||||||
foreach (var stream in _streams.Values)
|
lock (_streams)
|
||||||
{
|
{
|
||||||
stream.Abort(new ConnectionAbortedException("The Http3Connection has been aborted"), Http3ErrorCode.UnexpectedFrame);
|
foreach (var stream in _streams.Values)
|
||||||
|
{
|
||||||
|
stream.Abort(new ConnectionAbortedException("The Http3Connection has been aborted"), Http3ErrorCode.UnexpectedFrame);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO need to figure out if there is server initiated connection close rather than stream close?
|
// TODO need to figure out if there is server initiated connection close rather than stream close?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,5 +367,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
// TODO make sure this works
|
// TODO make sure this works
|
||||||
//_maxDynamicTableSize = value;
|
//_maxDynamicTableSize = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void RemoveStream(long streamId)
|
||||||
|
{
|
||||||
|
lock(_streams)
|
||||||
|
{
|
||||||
|
_streams.Remove(streamId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ using System.Net.Http.QPack;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
|
|
@ -21,29 +22,52 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
{
|
{
|
||||||
internal abstract class Http3Stream : HttpProtocol, IHttpHeadersHandler, IThreadPoolWorkItem
|
internal abstract class Http3Stream : HttpProtocol, IHttpHeadersHandler, IThreadPoolWorkItem
|
||||||
{
|
{
|
||||||
|
private static ReadOnlySpan<byte> AuthorityBytes => new byte[10] { (byte)':', (byte)'a', (byte)'u', (byte)'t', (byte)'h', (byte)'o', (byte)'r', (byte)'i', (byte)'t', (byte)'y' };
|
||||||
|
private static ReadOnlySpan<byte> MethodBytes => new byte[7] { (byte)':', (byte)'m', (byte)'e', (byte)'t', (byte)'h', (byte)'o', (byte)'d' };
|
||||||
|
private static ReadOnlySpan<byte> PathBytes => new byte[5] { (byte)':', (byte)'p', (byte)'a', (byte)'t', (byte)'h' };
|
||||||
|
private static ReadOnlySpan<byte> SchemeBytes => new byte[7] { (byte)':', (byte)'s', (byte)'c', (byte)'h', (byte)'e', (byte)'m', (byte)'e' };
|
||||||
|
private static ReadOnlySpan<byte> StatusBytes => new byte[7] { (byte)':', (byte)'s', (byte)'t', (byte)'a', (byte)'t', (byte)'u', (byte)'s' };
|
||||||
|
private static ReadOnlySpan<byte> ConnectionBytes => new byte[10] { (byte)'c', (byte)'o', (byte)'n', (byte)'n', (byte)'e', (byte)'c', (byte)'t', (byte)'i', (byte)'o', (byte)'n' };
|
||||||
|
private static ReadOnlySpan<byte> TeBytes => new byte[2] { (byte)'t', (byte)'e' };
|
||||||
|
private static ReadOnlySpan<byte> TrailersBytes => new byte[8] { (byte)'t', (byte)'r', (byte)'a', (byte)'i', (byte)'l', (byte)'e', (byte)'r', (byte)'s' };
|
||||||
|
private static ReadOnlySpan<byte> ConnectBytes => new byte[7] { (byte)'C', (byte)'O', (byte)'N', (byte)'N', (byte)'E', (byte)'C', (byte)'T' };
|
||||||
|
|
||||||
private Http3FrameWriter _frameWriter;
|
private Http3FrameWriter _frameWriter;
|
||||||
private Http3OutputProducer _http3Output;
|
private Http3OutputProducer _http3Output;
|
||||||
private int _isClosed;
|
private int _isClosed;
|
||||||
private int _gracefulCloseInitiator;
|
private int _gracefulCloseInitiator;
|
||||||
private readonly HttpConnectionContext _context;
|
private readonly Http3StreamContext _context;
|
||||||
|
private readonly IProtocolErrorCodeFeature _errorCodeFeature;
|
||||||
|
private readonly IStreamIdFeature _streamIdFeature;
|
||||||
private readonly Http3RawFrame _incomingFrame = new Http3RawFrame();
|
private readonly Http3RawFrame _incomingFrame = new Http3RawFrame();
|
||||||
|
protected RequestHeaderParsingState _requestHeaderParsingState;
|
||||||
|
private PseudoHeaderFields _parsedPseudoHeaderFields;
|
||||||
|
private bool _isMethodConnect;
|
||||||
|
|
||||||
private readonly Http3Connection _http3Connection;
|
private readonly Http3Connection _http3Connection;
|
||||||
private bool _receivedHeaders;
|
private bool _receivedHeaders;
|
||||||
|
private TaskCompletionSource<object> _appCompleted;
|
||||||
|
|
||||||
public Pipe RequestBodyPipe { get; }
|
public Pipe RequestBodyPipe { get; }
|
||||||
|
|
||||||
public Http3Stream(Http3Connection http3Connection, HttpConnectionContext context)
|
public Http3Stream(Http3Connection http3Connection, Http3StreamContext context)
|
||||||
{
|
{
|
||||||
Initialize(context);
|
Initialize(context);
|
||||||
|
|
||||||
|
InputRemaining = null;
|
||||||
|
|
||||||
// First, determine how we know if an Http3stream is unidirectional or bidirectional
|
// First, determine how we know if an Http3stream is unidirectional or bidirectional
|
||||||
var httpLimits = context.ServiceContext.ServerOptions.Limits;
|
var httpLimits = context.ServiceContext.ServerOptions.Limits;
|
||||||
var http3Limits = httpLimits.Http3;
|
var http3Limits = httpLimits.Http3;
|
||||||
_http3Connection = http3Connection;
|
_http3Connection = http3Connection;
|
||||||
_context = context;
|
_context = context;
|
||||||
|
|
||||||
|
_errorCodeFeature = _context.ConnectionFeatures.Get<IProtocolErrorCodeFeature>();
|
||||||
|
_streamIdFeature = _context.ConnectionFeatures.Get<IStreamIdFeature>();
|
||||||
|
|
||||||
_frameWriter = new Http3FrameWriter(
|
_frameWriter = new Http3FrameWriter(
|
||||||
context.Transport.Output,
|
context.Transport.Output,
|
||||||
context.ConnectionContext,
|
context.StreamContext,
|
||||||
context.TimeoutControl,
|
context.TimeoutControl,
|
||||||
httpLimits.MinResponseDataRate,
|
httpLimits.MinResponseDataRate,
|
||||||
context.ConnectionId,
|
context.ConnectionId,
|
||||||
|
|
@ -63,6 +87,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
QPackDecoder = new QPackDecoder(_context.ServiceContext.ServerOptions.Limits.Http3.MaxRequestHeaderFieldSize);
|
QPackDecoder = new QPackDecoder(_context.ServiceContext.ServerOptions.Limits.Http3.MaxRequestHeaderFieldSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long? InputRemaining { get; internal set; }
|
||||||
|
|
||||||
public QPackDecoder QPackDecoder { get; }
|
public QPackDecoder QPackDecoder { get; }
|
||||||
|
|
||||||
public PipeReader Input => _context.Transport.Input;
|
public PipeReader Input => _context.Transport.Input;
|
||||||
|
|
@ -77,7 +103,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
|
|
||||||
public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode)
|
public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode)
|
||||||
{
|
{
|
||||||
// TODO something with request aborted?
|
_errorCodeFeature.Error = (long)errorCode;
|
||||||
|
// TODO replace with IKestrelTrace log.
|
||||||
|
Log.LogWarning(ex, ex.Message);
|
||||||
|
_frameWriter.Abort(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnHeadersComplete(bool endStream)
|
public void OnHeadersComplete(bool endStream)
|
||||||
|
|
@ -97,6 +126,165 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
OnHeader(knownHeader.Name, value);
|
OnHeader(knownHeader.Name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
|
||||||
|
{
|
||||||
|
// TODO MaxRequestHeadersTotalSize?
|
||||||
|
ValidateHeader(name, value);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
|
||||||
|
{
|
||||||
|
OnTrailer(name, value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Throws BadRequest for header count limit breaches.
|
||||||
|
// Throws InvalidOperation for bad encoding.
|
||||||
|
base.OnHeader(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (BadHttpRequestException bre)
|
||||||
|
{
|
||||||
|
throw new Http3StreamErrorException(bre.Message, Http3ErrorCode.ProtocolError);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
throw new Http3StreamErrorException(CoreStrings.BadRequest_MalformedRequestInvalidHeaders, Http3ErrorCode.ProtocolError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
|
||||||
|
{
|
||||||
|
// http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.1
|
||||||
|
/*
|
||||||
|
Intermediaries that process HTTP requests or responses (i.e., any
|
||||||
|
intermediary not acting as a tunnel) MUST NOT forward a malformed
|
||||||
|
request or response. Malformed requests or responses that are
|
||||||
|
detected MUST be treated as a stream error (Section 5.4.2) of type
|
||||||
|
PROTOCOL_ERROR.
|
||||||
|
|
||||||
|
For malformed requests, a server MAY send an HTTP response prior to
|
||||||
|
closing or resetting the stream. Clients MUST NOT accept a malformed
|
||||||
|
response. Note that these requirements are intended to protect
|
||||||
|
against several types of common attacks against HTTP; they are
|
||||||
|
deliberately strict because being permissive can expose
|
||||||
|
implementations to these vulnerabilities.*/
|
||||||
|
if (IsPseudoHeaderField(name, out var headerField))
|
||||||
|
{
|
||||||
|
if (_requestHeaderParsingState == RequestHeaderParsingState.Headers)
|
||||||
|
{
|
||||||
|
// All pseudo-header fields MUST appear in the header block before regular header fields.
|
||||||
|
// Any request or response that contains a pseudo-header field that appears in a header
|
||||||
|
// block after a regular header field MUST be treated as malformed (Section 8.1.2.6).
|
||||||
|
throw new Http3StreamErrorException(CoreStrings.Http2ErrorPseudoHeaderFieldAfterRegularHeaders, Http3ErrorCode.ProtocolError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
|
||||||
|
{
|
||||||
|
// Pseudo-header fields MUST NOT appear in trailers.
|
||||||
|
throw new Http3StreamErrorException(CoreStrings.Http2ErrorTrailersContainPseudoHeaderField, Http3ErrorCode.ProtocolError);
|
||||||
|
}
|
||||||
|
|
||||||
|
_requestHeaderParsingState = RequestHeaderParsingState.PseudoHeaderFields;
|
||||||
|
|
||||||
|
if (headerField == PseudoHeaderFields.Unknown)
|
||||||
|
{
|
||||||
|
// Endpoints MUST treat a request or response that contains undefined or invalid pseudo-header
|
||||||
|
// fields as malformed (Section 8.1.2.6).
|
||||||
|
throw new Http3StreamErrorException(CoreStrings.Http2ErrorUnknownPseudoHeaderField, Http3ErrorCode.ProtocolError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headerField == PseudoHeaderFields.Status)
|
||||||
|
{
|
||||||
|
// Pseudo-header fields defined for requests MUST NOT appear in responses; pseudo-header fields
|
||||||
|
// defined for responses MUST NOT appear in requests.
|
||||||
|
throw new Http3StreamErrorException(CoreStrings.Http2ErrorResponsePseudoHeaderField, Http3ErrorCode.ProtocolError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((_parsedPseudoHeaderFields & headerField) == headerField)
|
||||||
|
{
|
||||||
|
// http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.3
|
||||||
|
// All HTTP/2 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header fields
|
||||||
|
throw new Http3StreamErrorException(CoreStrings.Http2ErrorDuplicatePseudoHeaderField, Http3ErrorCode.ProtocolError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headerField == PseudoHeaderFields.Method)
|
||||||
|
{
|
||||||
|
_isMethodConnect = value.SequenceEqual(ConnectBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
_parsedPseudoHeaderFields |= headerField;
|
||||||
|
}
|
||||||
|
else if (_requestHeaderParsingState != RequestHeaderParsingState.Trailers)
|
||||||
|
{
|
||||||
|
_requestHeaderParsingState = RequestHeaderParsingState.Headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsConnectionSpecificHeaderField(name, value))
|
||||||
|
{
|
||||||
|
throw new Http3StreamErrorException(CoreStrings.Http2ErrorConnectionSpecificHeaderField, Http3ErrorCode.ProtocolError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2
|
||||||
|
// A request or response containing uppercase header field names MUST be treated as malformed (Section 8.1.2.6).
|
||||||
|
for (var i = 0; i < name.Length; i++)
|
||||||
|
{
|
||||||
|
if (name[i] >= 65 && name[i] <= 90)
|
||||||
|
{
|
||||||
|
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
|
||||||
|
{
|
||||||
|
throw new Http3StreamErrorException(CoreStrings.Http2ErrorTrailerNameUppercase, Http3ErrorCode.ProtocolError);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Http3StreamErrorException(CoreStrings.Http2ErrorHeaderNameUppercase, Http3ErrorCode.ProtocolError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsPseudoHeaderField(ReadOnlySpan<byte> name, out PseudoHeaderFields headerField)
|
||||||
|
{
|
||||||
|
headerField = PseudoHeaderFields.None;
|
||||||
|
|
||||||
|
if (name.IsEmpty || name[0] != (byte)':')
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.SequenceEqual(PathBytes))
|
||||||
|
{
|
||||||
|
headerField = PseudoHeaderFields.Path;
|
||||||
|
}
|
||||||
|
else if (name.SequenceEqual(MethodBytes))
|
||||||
|
{
|
||||||
|
headerField = PseudoHeaderFields.Method;
|
||||||
|
}
|
||||||
|
else if (name.SequenceEqual(SchemeBytes))
|
||||||
|
{
|
||||||
|
headerField = PseudoHeaderFields.Scheme;
|
||||||
|
}
|
||||||
|
else if (name.SequenceEqual(StatusBytes))
|
||||||
|
{
|
||||||
|
headerField = PseudoHeaderFields.Status;
|
||||||
|
}
|
||||||
|
else if (name.SequenceEqual(AuthorityBytes))
|
||||||
|
{
|
||||||
|
headerField = PseudoHeaderFields.Authority;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
headerField = PseudoHeaderFields.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsConnectionSpecificHeaderField(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
|
||||||
|
{
|
||||||
|
return name.SequenceEqual(ConnectionBytes) || (name.SequenceEqual(TeBytes) && !value.SequenceEqual(TrailersBytes));
|
||||||
|
}
|
||||||
|
|
||||||
public void HandleReadDataRateTimeout()
|
public void HandleReadDataRateTimeout()
|
||||||
{
|
{
|
||||||
Log.RequestBodyMinimumDataRateNotSatisfied(ConnectionId, null, Limits.MinRequestBodyDataRate.BytesPerSecond);
|
Log.RequestBodyMinimumDataRateNotSatisfied(ConnectionId, null, Limits.MinRequestBodyDataRate.BytesPerSecond);
|
||||||
|
|
@ -112,7 +300,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
public void OnInputOrOutputCompleted()
|
public void OnInputOrOutputCompleted()
|
||||||
{
|
{
|
||||||
TryClose();
|
TryClose();
|
||||||
_frameWriter.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient));
|
Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), Http3ErrorCode.NoError);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnRequestProcessingEnded()
|
||||||
|
{
|
||||||
|
Debug.Assert(_appCompleted != null);
|
||||||
|
|
||||||
|
_appCompleted.SetResult(new object());
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryClose()
|
private bool TryClose()
|
||||||
|
|
@ -152,6 +347,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
|
|
||||||
if (result.IsCompleted)
|
if (result.IsCompleted)
|
||||||
{
|
{
|
||||||
|
OnEndStreamReceived();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -162,33 +358,61 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Http3StreamErrorException ex)
|
||||||
|
{
|
||||||
|
error = ex;
|
||||||
|
Abort(new ConnectionAbortedException(ex.Message, ex), ex.ErrorCode);
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
error = ex;
|
error = ex;
|
||||||
Log.LogWarning(0, ex, "Stream threw an exception.");
|
Log.LogWarning(0, ex, "Stream threw an unexpected exception.");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
var streamError = error as ConnectionAbortedException
|
var streamError = error as ConnectionAbortedException
|
||||||
?? new ConnectionAbortedException("The stream has completed.", error);
|
?? new ConnectionAbortedException("The stream has completed.", error);
|
||||||
|
|
||||||
|
Input.Complete();
|
||||||
|
|
||||||
|
await RequestBodyPipe.Writer.CompleteAsync();
|
||||||
|
|
||||||
|
// Make sure application func is completed before completing writer.
|
||||||
|
if (_appCompleted != null)
|
||||||
|
{
|
||||||
|
await _appCompleted.Task;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_frameWriter.Complete();
|
_frameWriter.Complete();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
_frameWriter.Abort(streamError);
|
Abort(streamError, Http3ErrorCode.ProtocolError);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Input.Complete();
|
_http3Connection.RemoveStream(_streamIdFeature.StreamId);
|
||||||
_context.Transport.Input.CancelPendingRead();
|
|
||||||
await RequestBodyPipe.Writer.CompleteAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnEndStreamReceived()
|
||||||
|
{
|
||||||
|
if (InputRemaining.HasValue)
|
||||||
|
{
|
||||||
|
// https://tools.ietf.org/html/rfc7540#section-8.1.2.6
|
||||||
|
if (InputRemaining.Value != 0)
|
||||||
|
{
|
||||||
|
throw new Http3StreamErrorException(CoreStrings.Http3StreamErrorLessDataThanLength, Http3ErrorCode.ProtocolError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OnTrailersComplete();
|
||||||
|
RequestBodyPipe.Writer.Complete();
|
||||||
|
}
|
||||||
|
|
||||||
private Task ProcessHttp3Stream<TContext>(IHttpApplication<TContext> application, in ReadOnlySequence<byte> payload)
|
private Task ProcessHttp3Stream<TContext>(IHttpApplication<TContext> application, in ReadOnlySequence<byte> payload)
|
||||||
{
|
{
|
||||||
|
|
@ -231,13 +455,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
}
|
}
|
||||||
|
|
||||||
_receivedHeaders = true;
|
_receivedHeaders = true;
|
||||||
|
InputRemaining = HttpRequestHeaders.ContentLength;
|
||||||
|
|
||||||
|
_appCompleted = new TaskCompletionSource<object>();
|
||||||
|
|
||||||
|
ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false);
|
||||||
|
|
||||||
Task.Run(() => base.ProcessRequestsAsync(application));
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task ProcessDataFrameAsync(in ReadOnlySequence<byte> payload)
|
private Task ProcessDataFrameAsync(in ReadOnlySequence<byte> payload)
|
||||||
{
|
{
|
||||||
|
if (InputRemaining.HasValue)
|
||||||
|
{
|
||||||
|
// https://tools.ietf.org/html/rfc7540#section-8.1.2.6
|
||||||
|
if (payload.Length > InputRemaining.Value)
|
||||||
|
{
|
||||||
|
throw new Http3StreamErrorException(CoreStrings.Http3StreamErrorMoreDataThanLength, Http3ErrorCode.ProtocolError);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputRemaining -= payload.Length;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var segment in payload)
|
foreach (var segment in payload)
|
||||||
{
|
{
|
||||||
RequestBodyPipe.Writer.Write(segment.Span);
|
RequestBodyPipe.Writer.Write(segment.Span);
|
||||||
|
|
@ -270,6 +509,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
|
|
||||||
protected override void ApplicationAbort()
|
protected override void ApplicationAbort()
|
||||||
{
|
{
|
||||||
|
var abortReason = new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication);
|
||||||
|
Abort(abortReason, Http3ErrorCode.InternalError);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string CreateRequestId()
|
protected override string CreateRequestId()
|
||||||
|
|
@ -307,7 +548,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(RequestHeaders[HeaderNames.Scheme]) || !string.IsNullOrEmpty(RequestHeaders[HeaderNames.Path]))
|
if (!string.IsNullOrEmpty(RequestHeaders[HeaderNames.Scheme]) || !string.IsNullOrEmpty(RequestHeaders[HeaderNames.Path]))
|
||||||
{
|
{
|
||||||
Abort(new ConnectionAbortedException(CoreStrings.Http2ErrorConnectMustNotSendSchemeOrPath), Http3ErrorCode.ProtocolError);
|
Abort(new ConnectionAbortedException(CoreStrings.Http3ErrorConnectMustNotSendSchemeOrPath), Http3ErrorCode.ProtocolError);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -326,8 +567,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
// - We'll need to find some concrete scenarios to warrant unblocking this.
|
// - We'll need to find some concrete scenarios to warrant unblocking this.
|
||||||
if (!string.Equals(RequestHeaders[HeaderNames.Scheme], Scheme, StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(RequestHeaders[HeaderNames.Scheme], Scheme, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Abort(new ConnectionAbortedException(
|
var str = CoreStrings.FormatHttp3StreamErrorSchemeMismatch(RequestHeaders[HeaderNames.Scheme], Scheme);
|
||||||
CoreStrings.FormatHttp2StreamErrorSchemeMismatch(RequestHeaders[HeaderNames.Scheme], Scheme)), Http3ErrorCode.ProtocolError);
|
Abort(new ConnectionAbortedException(str), Http3ErrorCode.ProtocolError);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -376,7 +617,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
|
|
||||||
if (Method == Http.HttpMethod.None)
|
if (Method == Http.HttpMethod.None)
|
||||||
{
|
{
|
||||||
Abort(new ConnectionAbortedException(CoreStrings.FormatHttp2ErrorMethodInvalid(_methodText)), Http3ErrorCode.ProtocolError);
|
Abort(new ConnectionAbortedException(CoreStrings.FormatHttp3ErrorMethodInvalid(_methodText)), Http3ErrorCode.ProtocolError);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -384,7 +625,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
{
|
{
|
||||||
if (HttpCharacters.IndexOfInvalidTokenChar(_methodText) >= 0)
|
if (HttpCharacters.IndexOfInvalidTokenChar(_methodText) >= 0)
|
||||||
{
|
{
|
||||||
Abort(new ConnectionAbortedException(CoreStrings.FormatHttp2ErrorMethodInvalid(_methodText)), Http3ErrorCode.ProtocolError);
|
Abort(new ConnectionAbortedException(CoreStrings.FormatHttp3ErrorMethodInvalid(_methodText)), Http3ErrorCode.ProtocolError);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -436,7 +677,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
// Must start with a leading slash
|
// Must start with a leading slash
|
||||||
if (pathSegment.Length == 0 || pathSegment[0] != '/')
|
if (pathSegment.Length == 0 || pathSegment[0] != '/')
|
||||||
{
|
{
|
||||||
Abort(new ConnectionAbortedException(CoreStrings.FormatHttp2StreamErrorPathInvalid(RawTarget)), Http3ErrorCode.ProtocolError);
|
Abort(new ConnectionAbortedException(CoreStrings.FormatHttp3StreamErrorPathInvalid(RawTarget)), Http3ErrorCode.ProtocolError);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -466,8 +707,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException)
|
catch (InvalidOperationException)
|
||||||
{
|
{
|
||||||
// TODO change HTTP/2 specific messages to include HTTP/3
|
Abort(new ConnectionAbortedException(CoreStrings.FormatHttp3StreamErrorPathInvalid(RawTarget)), Http3ErrorCode.ProtocolError);
|
||||||
Abort(new ConnectionAbortedException(CoreStrings.FormatHttp2StreamErrorPathInvalid(RawTarget)), Http3ErrorCode.ProtocolError);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -491,6 +731,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract void Execute();
|
public abstract void Execute();
|
||||||
|
|
||||||
|
protected enum RequestHeaderParsingState
|
||||||
|
{
|
||||||
|
Ready,
|
||||||
|
PseudoHeaderFields,
|
||||||
|
Headers,
|
||||||
|
Trailers
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
private enum PseudoHeaderFields
|
||||||
|
{
|
||||||
|
None = 0x0,
|
||||||
|
Authority = 0x1,
|
||||||
|
Method = 0x2,
|
||||||
|
Path = 0x4,
|
||||||
|
Scheme = 0x8,
|
||||||
|
Status = 0x10,
|
||||||
|
Unknown = 0x40000000
|
||||||
|
}
|
||||||
|
|
||||||
private static class GracefulCloseInitiator
|
private static class GracefulCloseInitiator
|
||||||
{
|
{
|
||||||
public const int None = 0;
|
public const int None = 0;
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
||||||
{
|
{
|
||||||
private readonly IHttpApplication<TContext> _application;
|
private readonly IHttpApplication<TContext> _application;
|
||||||
|
|
||||||
public Http3Stream(IHttpApplication<TContext> application, Http3Connection connection, HttpConnectionContext context) : base(connection, context)
|
public Http3Stream(IHttpApplication<TContext> application, Http3Connection connection, Http3StreamContext context) : base(connection, context)
|
||||||
{
|
{
|
||||||
_application = application;
|
_application = application;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Execute()
|
public override void Execute()
|
||||||
{
|
{
|
||||||
_ = ProcessRequestAsync(_application);
|
if (_requestHeaderParsingState == Http3Stream.RequestHeaderParsingState.Ready)
|
||||||
|
{
|
||||||
|
_ = ProcessRequestAsync(_application);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_ = base.ProcessRequestsAsync(_application);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pooled Host context
|
// Pooled Host context
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
// 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.Buffers;
|
||||||
|
using System.Net;
|
||||||
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
{
|
||||||
|
internal class Http3ConnectionContext
|
||||||
|
{
|
||||||
|
public string ConnectionId { get; set; }
|
||||||
|
public MultiplexedConnectionContext ConnectionContext { get; set; }
|
||||||
|
public ServiceContext ServiceContext { get; set; }
|
||||||
|
public IFeatureCollection ConnectionFeatures { get; set; }
|
||||||
|
public MemoryPool<byte> MemoryPool { get; set; }
|
||||||
|
public IPEndPoint LocalEndPoint { get; set; }
|
||||||
|
public IPEndPoint RemoteEndPoint { get; set; }
|
||||||
|
public ITimeoutControl TimeoutControl { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Connections;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
{
|
||||||
|
internal class Http3StreamContext : HttpConnectionContext
|
||||||
|
{
|
||||||
|
public ConnectionContext StreamContext { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -68,10 +68,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
requestProcessor = new Http2Connection(_context);
|
requestProcessor = new Http2Connection(_context);
|
||||||
_protocolSelectionState = ProtocolSelectionState.Selected;
|
_protocolSelectionState = ProtocolSelectionState.Selected;
|
||||||
break;
|
break;
|
||||||
case HttpProtocols.Http3:
|
|
||||||
requestProcessor = new Http3Connection(_context);
|
|
||||||
_protocolSelectionState = ProtocolSelectionState.Selected;
|
|
||||||
break;
|
|
||||||
case HttpProtocols.None:
|
case HttpProtocols.None:
|
||||||
// An error was already logged in SelectProtocol(), but we should close the connection.
|
// An error was already logged in SelectProtocol(), but we should close the connection.
|
||||||
break;
|
break;
|
||||||
|
|
@ -204,11 +200,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
|
||||||
private HttpProtocols SelectProtocol()
|
private HttpProtocols SelectProtocol()
|
||||||
{
|
{
|
||||||
if (_context.Protocols == HttpProtocols.Http3)
|
|
||||||
{
|
|
||||||
return HttpProtocols.Http3;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasTls = _context.ConnectionFeatures.Get<ITlsConnectionFeature>() != null;
|
var hasTls = _context.ConnectionFeatures.Get<ITlsConnectionFeature>() != null;
|
||||||
var applicationProtocol = _context.ConnectionFeatures.Get<ITlsApplicationProtocolFeature>()?.ApplicationProtocol
|
var applicationProtocol = _context.ConnectionFeatures.Get<ITlsApplicationProtocolFeature>()?.ApplicationProtocol
|
||||||
?? new ReadOnlyMemory<byte>();
|
?? new ReadOnlyMemory<byte>();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
|
|
||||||
Walk(connection =>
|
Walk(connection =>
|
||||||
{
|
{
|
||||||
connection.TransportConnection.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedDuringServerShutdown));
|
connection.GetTransport().Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedDuringServerShutdown));
|
||||||
abortTasks.Add(connection.ExecutionTask);
|
abortTasks.Add(connection.ExecutionTask);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
public ConnectionReference(KestrelConnection connection)
|
public ConnectionReference(KestrelConnection connection)
|
||||||
{
|
{
|
||||||
_weakReference = new WeakReference<KestrelConnection>(connection);
|
_weakReference = new WeakReference<KestrelConnection>(connection);
|
||||||
ConnectionId = connection.TransportConnection.ConnectionId;
|
ConnectionId = connection.GetTransport().ConnectionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ConnectionId { get; }
|
public string ConnectionId { get; }
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
{
|
{
|
||||||
internal class KestrelConnection : IConnectionHeartbeatFeature, IConnectionCompleteFeature, IConnectionLifetimeNotificationFeature, IThreadPoolWorkItem
|
internal abstract class KestrelConnection : IConnectionHeartbeatFeature, IConnectionCompleteFeature, IConnectionLifetimeNotificationFeature
|
||||||
{
|
{
|
||||||
private List<(Action<object> handler, object state)> _heartbeatHandlers;
|
private List<(Action<object> handler, object state)> _heartbeatHandlers;
|
||||||
private readonly object _heartbeatLock = new object();
|
private readonly object _heartbeatLock = new object();
|
||||||
|
|
@ -21,31 +21,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
|
|
||||||
private readonly CancellationTokenSource _connectionClosingCts = new CancellationTokenSource();
|
private readonly CancellationTokenSource _connectionClosingCts = new CancellationTokenSource();
|
||||||
private readonly TaskCompletionSource<object> _completionTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
private readonly TaskCompletionSource<object> _completionTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
private readonly long _id;
|
protected readonly long _id;
|
||||||
private readonly ServiceContext _serviceContext;
|
protected readonly ServiceContext _serviceContext;
|
||||||
private readonly ConnectionDelegate _connectionDelegate;
|
|
||||||
|
|
||||||
public KestrelConnection(long id,
|
public KestrelConnection(long id,
|
||||||
ServiceContext serviceContext,
|
ServiceContext serviceContext,
|
||||||
ConnectionDelegate connectionDelegate,
|
|
||||||
ConnectionContext connectionContext,
|
|
||||||
IKestrelTrace logger)
|
IKestrelTrace logger)
|
||||||
{
|
{
|
||||||
_id = id;
|
_id = id;
|
||||||
_serviceContext = serviceContext;
|
_serviceContext = serviceContext;
|
||||||
_connectionDelegate = connectionDelegate;
|
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
TransportConnection = connectionContext;
|
|
||||||
|
|
||||||
connectionContext.Features.Set<IConnectionHeartbeatFeature>(this);
|
|
||||||
connectionContext.Features.Set<IConnectionCompleteFeature>(this);
|
|
||||||
connectionContext.Features.Set<IConnectionLifetimeNotificationFeature>(this);
|
|
||||||
ConnectionClosedRequested = _connectionClosingCts.Token;
|
ConnectionClosedRequested = _connectionClosingCts.Token;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IKestrelTrace Logger { get; }
|
protected IKestrelTrace Logger { get; }
|
||||||
|
|
||||||
public ConnectionContext TransportConnection { get; set; }
|
|
||||||
public CancellationToken ConnectionClosedRequested { get; set; }
|
public CancellationToken ConnectionClosedRequested { get; set; }
|
||||||
public Task ExecutionTask => _completionTcs.Task;
|
public Task ExecutionTask => _completionTcs.Task;
|
||||||
|
|
||||||
|
|
@ -65,6 +56,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract BaseConnectionContext GetTransport();
|
||||||
|
|
||||||
public void OnHeartbeat(Action<object> action, object state)
|
public void OnHeartbeat(Action<object> action, object state)
|
||||||
{
|
{
|
||||||
lock (_heartbeatLock)
|
lock (_heartbeatLock)
|
||||||
|
|
@ -175,49 +168,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
_connectionClosingCts.Dispose();
|
_connectionClosingCts.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void IThreadPoolWorkItem.Execute()
|
protected IDisposable BeginConnectionScope(BaseConnectionContext connectionContext)
|
||||||
{
|
|
||||||
_ = ExecuteAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal async Task ExecuteAsync()
|
|
||||||
{
|
|
||||||
var connectionContext = TransportConnection;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Logger.ConnectionStart(connectionContext.ConnectionId);
|
|
||||||
KestrelEventSource.Log.ConnectionStart(connectionContext);
|
|
||||||
|
|
||||||
using (BeginConnectionScope(connectionContext))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _connectionDelegate(connectionContext);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(0, ex, "Unhandled exception while processing {ConnectionId}.", connectionContext.ConnectionId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
await FireOnCompletedAsync();
|
|
||||||
|
|
||||||
Logger.ConnectionStop(connectionContext.ConnectionId);
|
|
||||||
KestrelEventSource.Log.ConnectionStop(connectionContext);
|
|
||||||
|
|
||||||
// Dispose the transport connection, this needs to happen before removing it from the
|
|
||||||
// connection manager so that we only signal completion of this connection after the transport
|
|
||||||
// is properly torn down.
|
|
||||||
await TransportConnection.DisposeAsync();
|
|
||||||
|
|
||||||
_serviceContext.ConnectionManager.RemoveConnection(_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IDisposable BeginConnectionScope(ConnectionContext connectionContext)
|
|
||||||
{
|
{
|
||||||
if (Logger.IsEnabled(LogLevel.Critical))
|
if (Logger.IsEnabled(LogLevel.Critical))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
|
{
|
||||||
|
internal class KestrelConnection<T> : KestrelConnection, IThreadPoolWorkItem where T : BaseConnectionContext
|
||||||
|
{
|
||||||
|
private readonly Func<T, Task> _connectionDelegate;
|
||||||
|
|
||||||
|
public T TransportConnection { get; set; }
|
||||||
|
|
||||||
|
public KestrelConnection(long id,
|
||||||
|
ServiceContext serviceContext,
|
||||||
|
Func<T, Task> connectionDelegate,
|
||||||
|
T connectionContext,
|
||||||
|
IKestrelTrace logger)
|
||||||
|
: base(id, serviceContext, logger)
|
||||||
|
{
|
||||||
|
_connectionDelegate = connectionDelegate;
|
||||||
|
TransportConnection = connectionContext;
|
||||||
|
connectionContext.Features.Set<IConnectionHeartbeatFeature>(this);
|
||||||
|
connectionContext.Features.Set<IConnectionCompleteFeature>(this);
|
||||||
|
connectionContext.Features.Set<IConnectionLifetimeNotificationFeature>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IThreadPoolWorkItem.Execute()
|
||||||
|
{
|
||||||
|
_ = ExecuteAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
var connectionContext = TransportConnection;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger.ConnectionStart(connectionContext.ConnectionId);
|
||||||
|
KestrelEventSource.Log.ConnectionStart(connectionContext);
|
||||||
|
|
||||||
|
using (BeginConnectionScope(connectionContext))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _connectionDelegate(connectionContext);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(0, ex, "Unhandled exception while processing {ConnectionId}.", connectionContext.ConnectionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await FireOnCompletedAsync();
|
||||||
|
|
||||||
|
Logger.ConnectionStop(connectionContext.ConnectionId);
|
||||||
|
KestrelEventSource.Log.ConnectionStop(connectionContext);
|
||||||
|
|
||||||
|
// Dispose the transport connection, this needs to happen before removing it from the
|
||||||
|
// connection manager so that we only signal completion of this connection after the transport
|
||||||
|
// is properly torn down.
|
||||||
|
await TransportConnection.DisposeAsync();
|
||||||
|
|
||||||
|
_serviceContext.ConnectionManager.RemoveConnection(_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override BaseConnectionContext GetTransport()
|
||||||
|
{
|
||||||
|
return TransportConnection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
// - Avoid renaming methods or parameters marked with EventAttribute. EventSource uses these to form the event object.
|
// - Avoid renaming methods or parameters marked with EventAttribute. EventSource uses these to form the event object.
|
||||||
|
|
||||||
[NonEvent]
|
[NonEvent]
|
||||||
public void ConnectionStart(ConnectionContext connection)
|
public void ConnectionStart(BaseConnectionContext connection)
|
||||||
{
|
{
|
||||||
// avoid allocating strings unless this event source is enabled
|
// avoid allocating strings unless this event source is enabled
|
||||||
if (IsEnabled())
|
if (IsEnabled())
|
||||||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
[NonEvent]
|
[NonEvent]
|
||||||
public void ConnectionStop(ConnectionContext connection)
|
public void ConnectionStop(BaseConnectionContext connection)
|
||||||
{
|
{
|
||||||
if (IsEnabled())
|
if (IsEnabled())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
// 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.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
{
|
||||||
|
internal class MultiplexedConnectionDispatcher
|
||||||
|
{
|
||||||
|
private static long _lastConnectionId = long.MinValue;
|
||||||
|
|
||||||
|
private readonly ServiceContext _serviceContext;
|
||||||
|
private readonly MultiplexedConnectionDelegate _connectionDelegate;
|
||||||
|
private readonly TaskCompletionSource<object> _acceptLoopTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
public MultiplexedConnectionDispatcher(ServiceContext serviceContext, MultiplexedConnectionDelegate connectionDelegate)
|
||||||
|
{
|
||||||
|
_serviceContext = serviceContext;
|
||||||
|
_connectionDelegate = connectionDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IKestrelTrace Log => _serviceContext.Log;
|
||||||
|
|
||||||
|
public Task StartAcceptingConnections(IMultiplexedConnectionListener listener)
|
||||||
|
{
|
||||||
|
ThreadPool.UnsafeQueueUserWorkItem(StartAcceptingConnectionsCore, listener, preferLocal: false);
|
||||||
|
return _acceptLoopTcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartAcceptingConnectionsCore(IMultiplexedConnectionListener listener)
|
||||||
|
{
|
||||||
|
// REVIEW: Multiple accept loops in parallel?
|
||||||
|
_ = AcceptConnectionsAsync();
|
||||||
|
|
||||||
|
async Task AcceptConnectionsAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var connection = await listener.AcceptAsync();
|
||||||
|
|
||||||
|
if (connection == null)
|
||||||
|
{
|
||||||
|
// We're done listening
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the connection to the connection manager before we queue it for execution
|
||||||
|
var id = Interlocked.Increment(ref _lastConnectionId);
|
||||||
|
var kestrelConnection = new KestrelConnection<MultiplexedConnectionContext>(id, _serviceContext, c => _connectionDelegate(c), connection, Log);
|
||||||
|
|
||||||
|
_serviceContext.ConnectionManager.AddConnection(id, kestrelConnection);
|
||||||
|
|
||||||
|
Log.ConnectionAccepted(connection.ConnectionId);
|
||||||
|
|
||||||
|
ThreadPool.UnsafeQueueUserWorkItem(kestrelConnection, preferLocal: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// REVIEW: If the accept loop ends should this trigger a server shutdown? It will manifest as a hang
|
||||||
|
Log.LogCritical(0, ex, "The connection listener failed to accept any new connections.");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_acceptLoopTcs.TrySetResult(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
|
@ -22,20 +23,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
public class KestrelServer : IServer
|
public class KestrelServer : IServer
|
||||||
{
|
{
|
||||||
private readonly List<(IConnectionListener, Task)> _transports = new List<(IConnectionListener, Task)>();
|
private readonly List<(IConnectionListener, Task)> _transports = new List<(IConnectionListener, Task)>();
|
||||||
|
private readonly List<(IMultiplexedConnectionListener, Task)> _multiplexedTransports = new List<(IMultiplexedConnectionListener, Task)>();
|
||||||
private readonly IServerAddressesFeature _serverAddresses;
|
private readonly IServerAddressesFeature _serverAddresses;
|
||||||
private readonly List<IConnectionListenerFactory> _transportFactories;
|
private readonly List<IConnectionListenerFactory> _transportFactories;
|
||||||
|
private readonly List<IMultiplexedConnectionListenerFactory> _multiplexedTransportFactories;
|
||||||
|
|
||||||
private bool _hasStarted;
|
private bool _hasStarted;
|
||||||
private int _stopping;
|
private int _stopping;
|
||||||
private readonly TaskCompletionSource<object> _stoppedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
private readonly TaskCompletionSource<object> _stoppedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
public KestrelServer(IOptions<KestrelServerOptions> options, IEnumerable<IConnectionListenerFactory> transportFactories, ILoggerFactory loggerFactory)
|
public KestrelServer(IOptions<KestrelServerOptions> options, IEnumerable<IConnectionListenerFactory> transportFactories, ILoggerFactory loggerFactory)
|
||||||
: this(transportFactories, CreateServiceContext(options, loggerFactory))
|
: this(transportFactories, null, CreateServiceContext(options, loggerFactory))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public KestrelServer(IOptions<KestrelServerOptions> options, IEnumerable<IConnectionListenerFactory> transportFactories, IEnumerable<IMultiplexedConnectionListenerFactory> multiplexedFactories, ILoggerFactory loggerFactory)
|
||||||
|
: this(transportFactories, multiplexedFactories, CreateServiceContext(options, loggerFactory))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
// For testing
|
// For testing
|
||||||
internal KestrelServer(IEnumerable<IConnectionListenerFactory> transportFactories, ServiceContext serviceContext)
|
internal KestrelServer(IEnumerable<IConnectionListenerFactory> transportFactories, ServiceContext serviceContext)
|
||||||
|
: this(transportFactories, null, serviceContext)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// For testing
|
||||||
|
internal KestrelServer(IEnumerable<IConnectionListenerFactory> transportFactories, IEnumerable<IMultiplexedConnectionListenerFactory> multiplexedFactories, ServiceContext serviceContext)
|
||||||
{
|
{
|
||||||
if (transportFactories == null)
|
if (transportFactories == null)
|
||||||
{
|
{
|
||||||
|
|
@ -43,8 +56,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
_transportFactories = transportFactories.ToList();
|
_transportFactories = transportFactories.ToList();
|
||||||
|
_multiplexedTransportFactories = multiplexedFactories?.ToList();
|
||||||
|
|
||||||
if (_transportFactories.Count == 0)
|
if (_transportFactories.Count == 0 && (_multiplexedTransportFactories == null || _multiplexedTransportFactories.Count == 0))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(CoreStrings.TransportNotFound);
|
throw new InvalidOperationException(CoreStrings.TransportNotFound);
|
||||||
}
|
}
|
||||||
|
|
@ -78,6 +92,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
|
|
||||||
var heartbeatManager = new HeartbeatManager(connectionManager);
|
var heartbeatManager = new HeartbeatManager(connectionManager);
|
||||||
var dateHeaderValueManager = new DateHeaderValueManager();
|
var dateHeaderValueManager = new DateHeaderValueManager();
|
||||||
|
|
||||||
var heartbeat = new Heartbeat(
|
var heartbeat = new Heartbeat(
|
||||||
new IHeartbeatHandler[] { dateHeaderValueManager, heartbeatManager },
|
new IHeartbeatHandler[] { dateHeaderValueManager, heartbeatManager },
|
||||||
new SystemClock(),
|
new SystemClock(),
|
||||||
|
|
@ -129,54 +144,52 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
|
|
||||||
async Task OnBind(ListenOptions options)
|
async Task OnBind(ListenOptions options)
|
||||||
{
|
{
|
||||||
|
// INVESTIGATE: For some reason, MsQuic needs to bind before
|
||||||
|
// sockets for it to successfully listen. It also seems racy.
|
||||||
|
if ((options.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3)
|
||||||
|
{
|
||||||
|
if (_multiplexedTransportFactories == null || _multiplexedTransportFactories.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cannot start HTTP/3 server if no MultiplexedTransportFactories are registered.");
|
||||||
|
}
|
||||||
|
|
||||||
|
options.UseHttp3Server(ServiceContext, application, options.Protocols);
|
||||||
|
var multiplxedConnectionDelegate = ((IMultiplexedConnectionBuilder)options).Build();
|
||||||
|
|
||||||
|
var multiplexedConnectionDispatcher = new MultiplexedConnectionDispatcher(ServiceContext, multiplxedConnectionDelegate);
|
||||||
|
var multiplexedFactory = _multiplexedTransportFactories.Last();
|
||||||
|
var multiplexedTransport = await multiplexedFactory.BindAsync(options.EndPoint).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var acceptLoopTask = multiplexedConnectionDispatcher.StartAcceptingConnections(multiplexedTransport);
|
||||||
|
_multiplexedTransports.Add((multiplexedTransport, acceptLoopTask));
|
||||||
|
|
||||||
|
options.EndPoint = multiplexedTransport.EndPoint;
|
||||||
|
}
|
||||||
|
|
||||||
// Add the HTTP middleware as the terminal connection middleware
|
// Add the HTTP middleware as the terminal connection middleware
|
||||||
options.UseHttpServer(ServiceContext, application, options.Protocols);
|
if ((options.Protocols & HttpProtocols.Http1) == HttpProtocols.Http1
|
||||||
|
|| (options.Protocols & HttpProtocols.Http2) == HttpProtocols.Http2
|
||||||
var connectionDelegate = options.Build();
|
|| options.Protocols == HttpProtocols.None) // TODO a test fails because it doesn't throw an exception in the right place
|
||||||
|
// when there is no HttpProtocols in KestrelServer, can we remove/change the test?
|
||||||
// Add the connection limit middleware
|
|
||||||
if (Options.Limits.MaxConcurrentConnections.HasValue)
|
|
||||||
{
|
{
|
||||||
connectionDelegate = new ConnectionLimitMiddleware(connectionDelegate, Options.Limits.MaxConcurrentConnections.Value, Trace).OnConnectionAsync;
|
options.UseHttpServer(ServiceContext, application, options.Protocols);
|
||||||
}
|
var connectionDelegate = options.Build();
|
||||||
|
|
||||||
var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate);
|
// Add the connection limit middleware
|
||||||
|
if (Options.Limits.MaxConcurrentConnections.HasValue)
|
||||||
IConnectionListenerFactory factory = null;
|
|
||||||
if (options.Protocols >= HttpProtocols.Http3)
|
|
||||||
{
|
|
||||||
foreach (var transportFactory in _transportFactories)
|
|
||||||
{
|
{
|
||||||
if (transportFactory is IMultiplexedConnectionListenerFactory)
|
connectionDelegate = new ConnectionLimitMiddleware(connectionDelegate, Options.Limits.MaxConcurrentConnections.Value, Trace).OnConnectionAsync;
|
||||||
{
|
|
||||||
// Don't break early. Always use the last registered factory.
|
|
||||||
factory = transportFactory;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (factory == null)
|
var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate);
|
||||||
{
|
var factory = _transportFactories.Last();
|
||||||
throw new InvalidOperationException(CoreStrings.QuicTransportNotFound);
|
var transport = await factory.BindAsync(options.EndPoint).ConfigureAwait(false);
|
||||||
}
|
|
||||||
|
var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(transport);
|
||||||
|
|
||||||
|
_transports.Add((transport, acceptLoopTask));
|
||||||
|
options.EndPoint = transport.EndPoint;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var transportFactory in _transportFactories)
|
|
||||||
{
|
|
||||||
if (!(transportFactory is IMultiplexedConnectionListenerFactory))
|
|
||||||
{
|
|
||||||
factory = transportFactory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var transport = await factory.BindAsync(options.EndPoint).ConfigureAwait(false);
|
|
||||||
|
|
||||||
// Update the endpoint
|
|
||||||
options.EndPoint = transport.EndPoint;
|
|
||||||
var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(transport);
|
|
||||||
|
|
||||||
_transports.Add((transport, acceptLoopTask));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await AddressBinder.BindAsync(_serverAddresses, Options, Trace, OnBind).ConfigureAwait(false);
|
await AddressBinder.BindAsync(_serverAddresses, Options, Trace, OnBind).ConfigureAwait(false);
|
||||||
|
|
@ -200,13 +213,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tasks = new Task[_transports.Count];
|
var connectionTransportCount = _transports.Count;
|
||||||
for (int i = 0; i < _transports.Count; i++)
|
var totalTransportCount = _transports.Count + _multiplexedTransports.Count;
|
||||||
|
var tasks = new Task[totalTransportCount];
|
||||||
|
|
||||||
|
for (int i = 0; i < connectionTransportCount; i++)
|
||||||
{
|
{
|
||||||
(IConnectionListener listener, Task acceptLoop) = _transports[i];
|
(IConnectionListener listener, Task acceptLoop) = _transports[i];
|
||||||
tasks[i] = Task.WhenAll(listener.UnbindAsync(cancellationToken).AsTask(), acceptLoop);
|
tasks[i] = Task.WhenAll(listener.UnbindAsync(cancellationToken).AsTask(), acceptLoop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = connectionTransportCount; i < totalTransportCount; i++)
|
||||||
|
{
|
||||||
|
(IMultiplexedConnectionListener listener, Task acceptLoop) = _multiplexedTransports[i - connectionTransportCount];
|
||||||
|
tasks[i] = Task.WhenAll(listener.UnbindAsync(cancellationToken).AsTask(), acceptLoop);
|
||||||
|
}
|
||||||
|
|
||||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!await ConnectionManager.CloseAllConnectionsAsync(cancellationToken).ConfigureAwait(false))
|
if (!await ConnectionManager.CloseAllConnectionsAsync(cancellationToken).ConfigureAwait(false))
|
||||||
|
|
@ -219,12 +241,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < _transports.Count; i++)
|
for (int i = 0; i < connectionTransportCount; i++)
|
||||||
{
|
{
|
||||||
(IConnectionListener listener, Task acceptLoop) = _transports[i];
|
(IConnectionListener listener, Task acceptLoop) = _transports[i];
|
||||||
tasks[i] = listener.DisposeAsync().AsTask();
|
tasks[i] = listener.DisposeAsync().AsTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = connectionTransportCount; i < totalTransportCount; i++)
|
||||||
|
{
|
||||||
|
(IMultiplexedConnectionListener listener, Task acceptLoop) = _multiplexedTransports[i - connectionTransportCount];
|
||||||
|
tasks[i] = listener.DisposeAsync().AsTask();
|
||||||
|
}
|
||||||
|
|
||||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
|
|
||||||
ServiceContext.Heartbeat?.Dispose();
|
ServiceContext.Heartbeat?.Dispose();
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
/// Describes either an <see cref="IPEndPoint"/>, Unix domain socket path, or a file descriptor for an already open
|
/// Describes either an <see cref="IPEndPoint"/>, Unix domain socket path, or a file descriptor for an already open
|
||||||
/// socket that Kestrel should bind to or open.
|
/// socket that Kestrel should bind to or open.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ListenOptions : IConnectionBuilder
|
public class ListenOptions : IConnectionBuilder, IMultiplexedConnectionBuilder
|
||||||
{
|
{
|
||||||
internal readonly List<Func<ConnectionDelegate, ConnectionDelegate>> _middleware = new List<Func<ConnectionDelegate, ConnectionDelegate>>();
|
internal readonly List<Func<ConnectionDelegate, ConnectionDelegate>> _middleware = new List<Func<ConnectionDelegate, ConnectionDelegate>>();
|
||||||
|
internal readonly List<Func<MultiplexedConnectionDelegate, MultiplexedConnectionDelegate>> _multiplexedMiddleware = new List<Func<MultiplexedConnectionDelegate, MultiplexedConnectionDelegate>>();
|
||||||
|
|
||||||
internal ListenOptions(IPEndPoint endPoint)
|
internal ListenOptions(IPEndPoint endPoint)
|
||||||
{
|
{
|
||||||
|
|
@ -123,6 +124,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IMultiplexedConnectionBuilder IMultiplexedConnectionBuilder.Use(Func<MultiplexedConnectionDelegate, MultiplexedConnectionDelegate> middleware)
|
||||||
|
{
|
||||||
|
_multiplexedMiddleware.Add(middleware);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public ConnectionDelegate Build()
|
public ConnectionDelegate Build()
|
||||||
{
|
{
|
||||||
ConnectionDelegate app = context =>
|
ConnectionDelegate app = context =>
|
||||||
|
|
@ -139,6 +146,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MultiplexedConnectionDelegate IMultiplexedConnectionBuilder.Build()
|
||||||
|
{
|
||||||
|
MultiplexedConnectionDelegate app = context =>
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = _multiplexedMiddleware.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var component = _multiplexedMiddleware[i];
|
||||||
|
app = component(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
internal virtual async Task BindAsync(AddressBindContext context)
|
internal virtual async Task BindAsync(AddressBindContext context)
|
||||||
{
|
{
|
||||||
await AddressBinder.BindEndpointAsync(this, context).ConfigureAwait(false);
|
await AddressBinder.BindEndpointAsync(this, context).ConfigureAwait(false);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// 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.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
{
|
||||||
|
internal class Http3ConnectionMiddleware<TContext>
|
||||||
|
{
|
||||||
|
private readonly ServiceContext _serviceContext;
|
||||||
|
private readonly IHttpApplication<TContext> _application;
|
||||||
|
|
||||||
|
public Http3ConnectionMiddleware(ServiceContext serviceContext, IHttpApplication<TContext> application)
|
||||||
|
{
|
||||||
|
_serviceContext = serviceContext;
|
||||||
|
_application = application;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task OnConnectionAsync(MultiplexedConnectionContext connectionContext)
|
||||||
|
{
|
||||||
|
var memoryPoolFeature = connectionContext.Features.Get<IMemoryPoolFeature>();
|
||||||
|
|
||||||
|
var http3ConnectionContext = new Http3ConnectionContext
|
||||||
|
{
|
||||||
|
ConnectionId = connectionContext.ConnectionId,
|
||||||
|
ConnectionContext = connectionContext,
|
||||||
|
ServiceContext = _serviceContext,
|
||||||
|
ConnectionFeatures = connectionContext.Features,
|
||||||
|
MemoryPool = memoryPoolFeature.MemoryPool,
|
||||||
|
LocalEndPoint = connectionContext.LocalEndPoint as IPEndPoint,
|
||||||
|
RemoteEndPoint = connectionContext.RemoteEndPoint as IPEndPoint
|
||||||
|
};
|
||||||
|
|
||||||
|
var connection = new Http3Connection(http3ConnectionContext);
|
||||||
|
|
||||||
|
return connection.ProcessRequestsAsync(_application);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
|
||||||
|
|
@ -18,5 +16,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
return middleware.OnConnectionAsync;
|
return middleware.OnConnectionAsync;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IMultiplexedConnectionBuilder UseHttp3Server<TContext>(this IMultiplexedConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication<TContext> application, HttpProtocols protocols)
|
||||||
|
{
|
||||||
|
var middleware = new Http3ConnectionMiddleware<TContext>(serviceContext, application);
|
||||||
|
return builder.Use(next =>
|
||||||
|
{
|
||||||
|
return middleware.OnConnectionAsync;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
||||||
var connection = new Mock<DefaultConnectionContext> { CallBase = true }.Object;
|
var connection = new Mock<DefaultConnectionContext> { CallBase = true }.Object;
|
||||||
connection.ConnectionClosed = new CancellationToken(canceled: true);
|
connection.ConnectionClosed = new CancellationToken(canceled: true);
|
||||||
var kestrelConnection = new KestrelConnection(0, serviceContext, _ => tcs.Task, connection, serviceContext.Log);
|
var kestrelConnection = new KestrelConnection<ConnectionContext>(0, serviceContext, _ => tcs.Task, connection, serviceContext.Log);
|
||||||
serviceContext.ConnectionManager.AddConnection(0, kestrelConnection);
|
serviceContext.ConnectionManager.AddConnection(0, kestrelConnection);
|
||||||
|
|
||||||
var task = kestrelConnection.ExecuteAsync();
|
var task = kestrelConnection.ExecuteAsync();
|
||||||
|
|
@ -79,9 +79,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
||||||
var connection = new Mock<DefaultConnectionContext> { CallBase = true }.Object;
|
var connection = new Mock<DefaultConnectionContext> { CallBase = true }.Object;
|
||||||
connection.ConnectionClosed = new CancellationToken(canceled: true);
|
connection.ConnectionClosed = new CancellationToken(canceled: true);
|
||||||
var kestrelConnection = new KestrelConnection(0, serviceContext, _ => Task.CompletedTask, connection, serviceContext.Log);
|
var kestrelConnection = new KestrelConnection<ConnectionContext>(0, serviceContext, _ => Task.CompletedTask, connection, serviceContext.Log);
|
||||||
serviceContext.ConnectionManager.AddConnection(0, kestrelConnection);
|
serviceContext.ConnectionManager.AddConnection(0, kestrelConnection);
|
||||||
var completeFeature = kestrelConnection.TransportConnection.Features.Get<IConnectionCompleteFeature>();
|
var completeFeature = kestrelConnection.GetTransport().Features.Get<IConnectionCompleteFeature>();
|
||||||
|
|
||||||
Assert.NotNull(completeFeature);
|
Assert.NotNull(completeFeature);
|
||||||
object stateObject = new object();
|
object stateObject = new object();
|
||||||
|
|
@ -100,9 +100,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
var logger = ((TestKestrelTrace)serviceContext.Log).Logger;
|
var logger = ((TestKestrelTrace)serviceContext.Log).Logger;
|
||||||
var connection = new Mock<DefaultConnectionContext> { CallBase = true }.Object;
|
var connection = new Mock<DefaultConnectionContext> { CallBase = true }.Object;
|
||||||
connection.ConnectionClosed = new CancellationToken(canceled: true);
|
connection.ConnectionClosed = new CancellationToken(canceled: true);
|
||||||
var kestrelConnection = new KestrelConnection(0, serviceContext, _ => Task.CompletedTask, connection, serviceContext.Log);
|
var kestrelConnection = new KestrelConnection<ConnectionContext>(0, serviceContext, _ => Task.CompletedTask, connection, serviceContext.Log);
|
||||||
serviceContext.ConnectionManager.AddConnection(0, kestrelConnection);
|
serviceContext.ConnectionManager.AddConnection(0, kestrelConnection);
|
||||||
var completeFeature = kestrelConnection.TransportConnection.Features.Get<IConnectionCompleteFeature>();
|
var completeFeature = kestrelConnection.GetTransport().Features.Get<IConnectionCompleteFeature>();
|
||||||
|
|
||||||
Assert.NotNull(completeFeature);
|
Assert.NotNull(completeFeature);
|
||||||
object stateObject = new object();
|
object stateObject = new object();
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
var serviceContext = new TestServiceContext();
|
var serviceContext = new TestServiceContext();
|
||||||
var mock = new Mock<DefaultConnectionContext>() { CallBase = true };
|
var mock = new Mock<DefaultConnectionContext>() { CallBase = true };
|
||||||
mock.Setup(m => m.ConnectionId).Returns(connectionId);
|
mock.Setup(m => m.ConnectionId).Returns(connectionId);
|
||||||
var httpConnection = new KestrelConnection(0, serviceContext, _ => Task.CompletedTask, mock.Object, Mock.Of<IKestrelTrace>());
|
var httpConnection = new KestrelConnection<ConnectionContext>(0, serviceContext, _ => Task.CompletedTask, mock.Object, Mock.Of<IKestrelTrace>());
|
||||||
|
|
||||||
httpConnectionManager.AddConnection(0, httpConnection);
|
httpConnectionManager.AddConnection(0, httpConnection);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
var httpProtocolGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "HttpProtocol.Generated.cs");
|
var httpProtocolGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "HttpProtocol.Generated.cs");
|
||||||
var httpUtilitiesGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "HttpUtilities.Generated.cs");
|
var httpUtilitiesGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "HttpUtilities.Generated.cs");
|
||||||
var http2ConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "Http2Connection.Generated.cs");
|
var http2ConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "Http2Connection.Generated.cs");
|
||||||
var transportConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "TransportConnection.Generated.cs");
|
var transportMultiplexedConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "TransportMultiplexedConnection.Generated.cs");
|
||||||
|
var transportConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "TransportConnection.Generated.cs");
|
||||||
|
|
||||||
var testHttpHeadersGeneratedPath = Path.GetTempFileName();
|
var testHttpHeadersGeneratedPath = Path.GetTempFileName();
|
||||||
var testHttpProtocolGeneratedPath = Path.GetTempFileName();
|
var testHttpProtocolGeneratedPath = Path.GetTempFileName();
|
||||||
var testHttpUtilitiesGeneratedPath = Path.GetTempFileName();
|
var testHttpUtilitiesGeneratedPath = Path.GetTempFileName();
|
||||||
var testHttp2ConnectionGeneratedPath = Path.GetTempFileName();
|
var testHttp2ConnectionGeneratedPath = Path.GetTempFileName();
|
||||||
|
var testTransportMultiplexedConnectionGeneratedPath = Path.GetTempFileName();
|
||||||
var testTransportConnectionGeneratedPath = Path.GetTempFileName();
|
var testTransportConnectionGeneratedPath = Path.GetTempFileName();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -34,20 +36,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
var currentHttpProtocolGenerated = File.ReadAllText(httpProtocolGeneratedPath);
|
var currentHttpProtocolGenerated = File.ReadAllText(httpProtocolGeneratedPath);
|
||||||
var currentHttpUtilitiesGenerated = File.ReadAllText(httpUtilitiesGeneratedPath);
|
var currentHttpUtilitiesGenerated = File.ReadAllText(httpUtilitiesGeneratedPath);
|
||||||
var currentHttp2ConnectionGenerated = File.ReadAllText(http2ConnectionGeneratedPath);
|
var currentHttp2ConnectionGenerated = File.ReadAllText(http2ConnectionGeneratedPath);
|
||||||
|
var currentTransportConnectionBaseGenerated = File.ReadAllText(transportMultiplexedConnectionGeneratedPath);
|
||||||
var currentTransportConnectionGenerated = File.ReadAllText(transportConnectionGeneratedPath);
|
var currentTransportConnectionGenerated = File.ReadAllText(transportConnectionGeneratedPath);
|
||||||
|
|
||||||
CodeGenerator.Program.Run(testHttpHeadersGeneratedPath, testHttpProtocolGeneratedPath, testHttpUtilitiesGeneratedPath, testTransportConnectionGeneratedPath, testHttp2ConnectionGeneratedPath);
|
CodeGenerator.Program.Run(testHttpHeadersGeneratedPath,
|
||||||
|
testHttpProtocolGeneratedPath,
|
||||||
|
testHttpUtilitiesGeneratedPath,
|
||||||
|
testHttp2ConnectionGeneratedPath,
|
||||||
|
testTransportMultiplexedConnectionGeneratedPath,
|
||||||
|
testTransportConnectionGeneratedPath);
|
||||||
|
|
||||||
var testHttpHeadersGenerated = File.ReadAllText(testHttpHeadersGeneratedPath);
|
var testHttpHeadersGenerated = File.ReadAllText(testHttpHeadersGeneratedPath);
|
||||||
var testHttpProtocolGenerated = File.ReadAllText(testHttpProtocolGeneratedPath);
|
var testHttpProtocolGenerated = File.ReadAllText(testHttpProtocolGeneratedPath);
|
||||||
var testHttpUtilitiesGenerated = File.ReadAllText(testHttpUtilitiesGeneratedPath);
|
var testHttpUtilitiesGenerated = File.ReadAllText(testHttpUtilitiesGeneratedPath);
|
||||||
var testHttp2ConnectionGenerated = File.ReadAllText(testHttp2ConnectionGeneratedPath);
|
var testHttp2ConnectionGenerated = File.ReadAllText(testHttp2ConnectionGeneratedPath);
|
||||||
|
var testTransportMultiplxedConnectionGenerated = File.ReadAllText(testTransportMultiplexedConnectionGeneratedPath);
|
||||||
var testTransportConnectionGenerated = File.ReadAllText(testTransportConnectionGeneratedPath);
|
var testTransportConnectionGenerated = File.ReadAllText(testTransportConnectionGeneratedPath);
|
||||||
|
|
||||||
Assert.Equal(currentHttpHeadersGenerated, testHttpHeadersGenerated, ignoreLineEndingDifferences: true);
|
Assert.Equal(currentHttpHeadersGenerated, testHttpHeadersGenerated, ignoreLineEndingDifferences: true);
|
||||||
Assert.Equal(currentHttpProtocolGenerated, testHttpProtocolGenerated, ignoreLineEndingDifferences: true);
|
Assert.Equal(currentHttpProtocolGenerated, testHttpProtocolGenerated, ignoreLineEndingDifferences: true);
|
||||||
Assert.Equal(currentHttpUtilitiesGenerated, testHttpUtilitiesGenerated, ignoreLineEndingDifferences: true);
|
Assert.Equal(currentHttpUtilitiesGenerated, testHttpUtilitiesGenerated, ignoreLineEndingDifferences: true);
|
||||||
Assert.Equal(currentHttp2ConnectionGenerated, testHttp2ConnectionGenerated, ignoreLineEndingDifferences: true);
|
Assert.Equal(currentHttp2ConnectionGenerated, testHttp2ConnectionGenerated, ignoreLineEndingDifferences: true);
|
||||||
|
Assert.Equal(currentTransportConnectionBaseGenerated, testTransportMultiplxedConnectionGenerated, ignoreLineEndingDifferences: true);
|
||||||
Assert.Equal(currentTransportConnectionGenerated, testTransportConnectionGenerated, ignoreLineEndingDifferences: true);
|
Assert.Equal(currentTransportConnectionGenerated, testTransportConnectionGenerated, ignoreLineEndingDifferences: true);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
@ -56,6 +66,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
File.Delete(testHttpProtocolGeneratedPath);
|
File.Delete(testHttpProtocolGeneratedPath);
|
||||||
File.Delete(testHttpUtilitiesGeneratedPath);
|
File.Delete(testHttpUtilitiesGeneratedPath);
|
||||||
File.Delete(testHttp2ConnectionGeneratedPath);
|
File.Delete(testHttp2ConnectionGeneratedPath);
|
||||||
|
File.Delete(testTransportMultiplexedConnectionGeneratedPath);
|
||||||
File.Delete(testTransportConnectionGeneratedPath);
|
File.Delete(testTransportConnectionGeneratedPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
<Content Include="$(KestrelRoot)Core\src\Internal\Infrastructure\HttpUtilities.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
|
<Content Include="$(KestrelRoot)Core\src\Internal\Infrastructure\HttpUtilities.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
|
||||||
<Content Include="$(KestrelRoot)Core\src\Internal\Http2\Http2Connection.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
|
<Content Include="$(KestrelRoot)Core\src\Internal\Http2\Http2Connection.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
|
||||||
<Content Include="$(KestrelSharedSourceRoot)\TransportConnection.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
|
<Content Include="$(KestrelSharedSourceRoot)\TransportConnection.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
|
||||||
|
<Content Include="$(KestrelSharedSourceRoot)\TransportMultiplexedConnection.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETFramework'">
|
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETFramework'">
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
||||||
void StreamPause(string streamId);
|
void StreamPause(string streamId);
|
||||||
void StreamResume(string streamId);
|
void StreamResume(string streamId);
|
||||||
void StreamShutdownWrite(string streamId, Exception ex);
|
void StreamShutdownWrite(string streamId, Exception ex);
|
||||||
|
void StreamAbort(string streamId, Exception ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,16 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Quic;
|
using System.Net.Quic;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Connections.Abstractions.Features;
|
|
||||||
using Microsoft.AspNetCore.Connections.Features;
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
||||||
{
|
{
|
||||||
internal class QuicConnectionContext : TransportConnection, IQuicStreamListenerFeature, IQuicCreateStreamFeature
|
internal class QuicConnectionContext : TransportMultiplexedConnection, IProtocolErrorCodeFeature
|
||||||
{
|
{
|
||||||
private QuicConnection _connection;
|
private QuicConnection _connection;
|
||||||
private readonly QuicTransportContext _context;
|
private readonly QuicTransportContext _context;
|
||||||
|
|
@ -20,14 +20,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
||||||
|
|
||||||
private ValueTask _closeTask;
|
private ValueTask _closeTask;
|
||||||
|
|
||||||
|
public long Error { get; set; }
|
||||||
|
|
||||||
public QuicConnectionContext(QuicConnection connection, QuicTransportContext context)
|
public QuicConnectionContext(QuicConnection connection, QuicTransportContext context)
|
||||||
{
|
{
|
||||||
_log = context.Log;
|
_log = context.Log;
|
||||||
_context = context;
|
_context = context;
|
||||||
_connection = connection;
|
_connection = connection;
|
||||||
Features.Set<ITlsConnectionFeature>(new FakeTlsConnectionFeature());
|
Features.Set<ITlsConnectionFeature>(new FakeTlsConnectionFeature());
|
||||||
Features.Set<IQuicStreamListenerFeature>(this);
|
Features.Set<IProtocolErrorCodeFeature>(this);
|
||||||
Features.Set<IQuicCreateStreamFeature>(this);
|
|
||||||
|
|
||||||
_log.NewConnection(ConnectionId);
|
_log.NewConnection(ConnectionId);
|
||||||
}
|
}
|
||||||
|
|
@ -66,27 +67,51 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
||||||
_connection.Dispose();
|
_connection.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via MultiplexedConnectionContext.Abort()."));
|
||||||
|
|
||||||
public override void Abort(ConnectionAbortedException abortReason)
|
public override void Abort(ConnectionAbortedException abortReason)
|
||||||
{
|
{
|
||||||
_closeTask = _connection.CloseAsync(errorCode: _context.Options.AbortErrorCode);
|
_closeTask = _connection.CloseAsync(errorCode: Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<ConnectionContext> AcceptAsync()
|
public override async ValueTask<ConnectionContext> AcceptAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var stream = await _connection.AcceptStreamAsync();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Because the stream is wrapped with a quic connection provider,
|
var stream = await _connection.AcceptStreamAsync(cancellationToken);
|
||||||
// we need to check a property to check if this is null
|
return new QuicStreamContext(stream, this, _context);
|
||||||
// Will be removed once the provider abstraction is removed.
|
|
||||||
_ = stream.CanRead;
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (QuicException ex)
|
||||||
{
|
{
|
||||||
return null;
|
// Accept on graceful close throws an aborted exception rather than returning null.
|
||||||
|
_log.LogDebug($"Accept loop ended with exception: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new QuicStreamContext(stream, this, _context);
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ValueTask<ConnectionContext> ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
QuicStream quicStream;
|
||||||
|
|
||||||
|
if (features != null)
|
||||||
|
{
|
||||||
|
var streamDirectionFeature = features.Get<IStreamDirectionFeature>();
|
||||||
|
if (streamDirectionFeature.CanRead)
|
||||||
|
{
|
||||||
|
quicStream = _connection.OpenBidirectionalStream();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
quicStream = _connection.OpenUnidirectionalStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
quicStream = _connection.OpenBidirectionalStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ValueTask<ConnectionContext>(new QuicStreamContext(quicStream, this, _context));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,15 @@ using System.Net.Security;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Listens for new Quic Connections.
|
/// Listens for new Quic Connections.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class QuicConnectionListener : IConnectionListener, IAsyncDisposable
|
internal class QuicConnectionListener : IMultiplexedConnectionListener, IAsyncDisposable
|
||||||
{
|
{
|
||||||
private readonly IQuicTrace _log;
|
private readonly IQuicTrace _log;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
@ -36,22 +38,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
||||||
|
|
||||||
public EndPoint EndPoint { get; set; }
|
public EndPoint EndPoint { get; set; }
|
||||||
|
|
||||||
public async ValueTask<ConnectionContext> AcceptAsync(CancellationToken cancellationToken = default)
|
public async ValueTask<MultiplexedConnectionContext> AcceptAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var quicConnection = await _listener.AcceptConnectionAsync(cancellationToken);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Because the stream is wrapped with a quic connection provider,
|
var quicConnection = await _listener.AcceptConnectionAsync(cancellationToken);
|
||||||
// we need to check a property to check if this is null
|
return new QuicConnectionContext(quicConnection, _context);
|
||||||
// Will be removed once the provider abstraction is removed.
|
|
||||||
_ = quicConnection.LocalEndPoint;
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (QuicOperationAbortedException ex)
|
||||||
{
|
{
|
||||||
return null;
|
_log.LogDebug($"Listener has aborted with exception: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
return new QuicConnectionContext(quicConnection, _context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask UnbindAsync(CancellationToken cancellationToken = default)
|
public async ValueTask UnbindAsync(CancellationToken cancellationToken = default)
|
||||||
|
|
@ -68,6 +66,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
|
||||||
|
_listener.Close();
|
||||||
_listener.Dispose();
|
_listener.Dispose();
|
||||||
|
|
||||||
return new ValueTask();
|
return new ValueTask();
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
||||||
{
|
{
|
||||||
internal class QuicStreamContext : TransportConnection, IQuicStreamFeature
|
internal class QuicStreamContext : TransportConnection, IStreamDirectionFeature, IProtocolErrorCodeFeature, IStreamIdFeature
|
||||||
{
|
{
|
||||||
private readonly Task _processingTask;
|
private readonly Task _processingTask;
|
||||||
private readonly QuicStream _stream;
|
private readonly QuicStream _stream;
|
||||||
|
|
@ -46,7 +46,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
||||||
|
|
||||||
var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions);
|
var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions);
|
||||||
|
|
||||||
Features.Set<IQuicStreamFeature>(this);
|
Features.Set<IStreamDirectionFeature>(this);
|
||||||
|
Features.Set<IProtocolErrorCodeFeature>(this);
|
||||||
|
Features.Set<IStreamIdFeature>(this);
|
||||||
|
|
||||||
// TODO populate the ITlsConnectionFeature (requires client certs).
|
// TODO populate the ITlsConnectionFeature (requires client certs).
|
||||||
Features.Set<ITlsConnectionFeature>(new FakeTlsConnectionFeature());
|
Features.Set<ITlsConnectionFeature>(new FakeTlsConnectionFeature());
|
||||||
|
|
@ -90,6 +92,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long Error { get; set; }
|
||||||
|
|
||||||
private async Task StartAsync()
|
private async Task StartAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -281,10 +285,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
||||||
public override void Abort(ConnectionAbortedException abortReason)
|
public override void Abort(ConnectionAbortedException abortReason)
|
||||||
{
|
{
|
||||||
// Don't call _stream.Shutdown and _stream.Abort at the same time.
|
// Don't call _stream.Shutdown and _stream.Abort at the same time.
|
||||||
|
_log.StreamAbort(ConnectionId, abortReason);
|
||||||
|
|
||||||
lock (_shutdownLock)
|
lock (_shutdownLock)
|
||||||
{
|
{
|
||||||
_stream.AbortRead(_context.Options.AbortErrorCode);
|
_stream.AbortRead(Error);
|
||||||
_stream.AbortWrite(_context.Options.AbortErrorCode);
|
_stream.AbortWrite(Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel ProcessSends loop after calling shutdown to ensure the correct _shutdownReason gets set.
|
// Cancel ProcessSends loop after calling shutdown to ensure the correct _shutdownReason gets set.
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
||||||
private static readonly Action<ILogger, string, Exception> _streamResume =
|
private static readonly Action<ILogger, string, Exception> _streamResume =
|
||||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, nameof(StreamResume)), @"Stream id ""{ConnectionId}"" resumed.");
|
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, nameof(StreamResume)), @"Stream id ""{ConnectionId}"" resumed.");
|
||||||
private static readonly Action<ILogger, string, string, Exception> _streamShutdownWrite =
|
private static readonly Action<ILogger, string, string, Exception> _streamShutdownWrite =
|
||||||
LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(7, nameof(StreamShutdownWrite)), @"Connection id ""{ConnectionId}"" shutting down writes, exception: ""{Reason}"".");
|
LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(7, nameof(StreamShutdownWrite)), @"Stream id ""{ConnectionId}"" shutting down writes, exception: ""{Reason}"".");
|
||||||
|
private static readonly Action<ILogger, string, string, Exception> _streamAborted =
|
||||||
|
LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(7, nameof(StreamShutdownWrite)), @"Stream id ""{ConnectionId}"" aborted by application, exception: ""{Reason}"".");
|
||||||
|
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
|
|
||||||
|
|
@ -70,5 +72,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
||||||
{
|
{
|
||||||
_streamShutdownWrite(_logger, streamId, ex.Message, ex);
|
_streamShutdownWrite(_logger, streamId, ex.Message, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void StreamAbort(string streamId, Exception ex)
|
||||||
|
{
|
||||||
|
_streamAborted(_logger, streamId, ex.Message, ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Description>Quic transport for the ASP.NET Core Kestrel cross-platform web server.</Description>
|
<Description>Quic transport for the ASP.NET Core Kestrel cross-platform web server.</Description>
|
||||||
|
|
@ -20,6 +20,9 @@
|
||||||
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.cs" Link="Internal\TransportConnection.cs" />
|
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.cs" Link="Internal\TransportConnection.cs" />
|
||||||
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.Generated.cs" Link="Internal\TransportConnection.Generated.cs" />
|
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.Generated.cs" Link="Internal\TransportConnection.Generated.cs" />
|
||||||
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.FeatureCollection.cs" Link="Internal\TransportConnection.FeatureCollection.cs" />
|
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.FeatureCollection.cs" Link="Internal\TransportConnection.FeatureCollection.cs" />
|
||||||
|
<Compile Include="$(KestrelSharedSourceRoot)\TransportMultiplexedConnection.cs" Link="Internal\TransportMultiplexedConnection.cs" />
|
||||||
|
<Compile Include="$(KestrelSharedSourceRoot)\TransportMultiplexedConnection.Generated.cs" Link="Internal\TransportMultiplexedConnection.Generated.cs" />
|
||||||
|
<Compile Include="$(KestrelSharedSourceRoot)\TransportMultiplexedConnection.FeatureCollection.cs" Link="Internal\TransportMultiplexedConnection.FeatureCollection.cs" />
|
||||||
<Compile Include="$(RepoRoot)src\Shared\TaskToApm.cs" Link="Internal\TaskToApm.cs" />
|
<Compile Include="$(RepoRoot)src\Shared\TaskToApm.cs" Link="Internal\TaskToApm.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,14 @@ using System.Net.Security;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic
|
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic
|
||||||
{
|
{
|
||||||
public class QuicConnectionFactory : IConnectionFactory
|
public class QuicConnectionFactory : IMultiplexedConnectionFactory
|
||||||
{
|
{
|
||||||
private QuicTransportContext _transportContext;
|
private QuicTransportContext _transportContext;
|
||||||
|
|
||||||
|
|
@ -32,7 +33,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic
|
||||||
_transportContext = new QuicTransportContext(trace, options.Value);
|
_transportContext = new QuicTransportContext(trace, options.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<ConnectionContext> ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken = default)
|
public async ValueTask<MultiplexedConnectionContext> ConnectAsync(EndPoint endPoint, IFeatureCollection features = null, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (!(endPoint is IPEndPoint ipEndPoint))
|
if (!(endPoint is IPEndPoint ipEndPoint))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal;
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
|
@ -35,10 +35,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic
|
||||||
_options = options.Value;
|
_options = options.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask<IConnectionListener> BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default)
|
public ValueTask<IMultiplexedConnectionListener> BindAsync(EndPoint endpoint, IFeatureCollection features = null, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var transport = new QuicConnectionListener(_options, _log, endpoint);
|
var transport = new QuicConnectionListener(_options, _log, endpoint);
|
||||||
return new ValueTask<IConnectionListener>(transport);
|
return new ValueTask<IMultiplexedConnectionListener>(transport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Alpn { get; set; }
|
public string Alpn { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The registration name to use in Quic.
|
|
||||||
/// </summary>
|
|
||||||
public string RegistrationName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The certificate that MsQuic will use.
|
/// The certificate that MsQuic will use.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -49,11 +44,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long? MaxWriteBufferSize { get; set; } = 64 * 1024;
|
public long? MaxWriteBufferSize { get; set; } = 64 * 1024;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The error code to abort with
|
|
||||||
/// </summary>
|
|
||||||
public long AbortErrorCode { get; set; } = 0;
|
|
||||||
|
|
||||||
internal Func<MemoryPool<byte>> MemoryPoolFactory { get; set; } = System.Buffers.SlabMemoryPoolFactory.Create;
|
internal Func<MemoryPool<byte>> MemoryPoolFactory { get; set; } = System.Buffers.SlabMemoryPoolFactory.Create;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Hosting
|
||||||
{
|
{
|
||||||
return hostBuilder.ConfigureServices(services =>
|
return hostBuilder.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
services.AddSingleton<IConnectionListenerFactory, QuicTransportFactory>();
|
services.AddSingleton<IMultiplexedConnectionListenerFactory, QuicTransportFactory>();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using System.Net.Sockets;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
|
@ -15,47 +14,39 @@ namespace Http3SampleApp
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, true);
|
var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, false);
|
||||||
|
|
||||||
var hostBuilder = new HostBuilder()
|
var hostBuilder = new HostBuilder()
|
||||||
.ConfigureLogging((_, factory) =>
|
.ConfigureLogging((_, factory) =>
|
||||||
{
|
{
|
||||||
factory.SetMinimumLevel(LogLevel.Trace);
|
factory.SetMinimumLevel(LogLevel.Trace);
|
||||||
factory.AddConsole();
|
factory.AddConsole();
|
||||||
})
|
})
|
||||||
.ConfigureWebHost(webHost =>
|
.ConfigureWebHost(webHost =>
|
||||||
{
|
{
|
||||||
webHost.UseKestrel()
|
webHost.UseKestrel()
|
||||||
// Things like APLN and cert should be able to be passed from corefx into bedrock
|
.UseQuic(options =>
|
||||||
.UseQuic(options =>
|
{
|
||||||
{
|
options.Certificate = cert; // Shouldn't need this either here.
|
||||||
options.Certificate = cert;
|
options.Alpn = "h3-25"; // Shouldn't need to populate this as well.
|
||||||
options.RegistrationName = "Quic";
|
options.IdleTimeout = TimeSpan.FromHours(1);
|
||||||
options.Alpn = "h3-25";
|
})
|
||||||
options.IdleTimeout = TimeSpan.FromHours(1);
|
.ConfigureKestrel((context, options) =>
|
||||||
})
|
{
|
||||||
.ConfigureKestrel((context, options) =>
|
var basePort = 5557;
|
||||||
{
|
options.EnableAltSvc = true;
|
||||||
var basePort = 443;
|
|
||||||
options.EnableAltSvc = true;
|
options.Listen(IPAddress.Any, basePort, listenOptions =>
|
||||||
options.Listen(IPAddress.Any, basePort, listenOptions =>
|
{
|
||||||
{
|
|
||||||
listenOptions.UseHttps(httpsOptions =>
|
|
||||||
{
|
|
||||||
httpsOptions.ServerCertificate = cert;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
options.Listen(IPAddress.Any, basePort, listenOptions =>
|
|
||||||
{
|
|
||||||
listenOptions.UseHttps(httpsOptions =>
|
listenOptions.UseHttps(httpsOptions =>
|
||||||
{
|
{
|
||||||
httpsOptions.ServerCertificate = cert;
|
httpsOptions.ServerCertificate = cert;
|
||||||
});
|
});
|
||||||
listenOptions.Protocols = HttpProtocols.Http3;
|
listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.UseStartup<Startup>();
|
.UseStartup<Startup>();
|
||||||
});
|
});
|
||||||
|
|
||||||
hostBuilder.Build().Run();
|
hostBuilder.Build().Run();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,12 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Connections.Features;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace QuicSampleApp
|
namespace QuicSampleApp
|
||||||
{
|
{
|
||||||
|
|
@ -35,7 +32,6 @@ namespace QuicSampleApp
|
||||||
.UseQuic(options =>
|
.UseQuic(options =>
|
||||||
{
|
{
|
||||||
options.Certificate = null;
|
options.Certificate = null;
|
||||||
options.RegistrationName = "AspNetCore-MsQuic";
|
|
||||||
options.Alpn = "QuicTest";
|
options.Alpn = "QuicTest";
|
||||||
options.IdleTimeout = TimeSpan.FromHours(1);
|
options.IdleTimeout = TimeSpan.FromHours(1);
|
||||||
})
|
})
|
||||||
|
|
@ -46,54 +42,37 @@ namespace QuicSampleApp
|
||||||
options.Listen(IPAddress.Any, basePort, listenOptions =>
|
options.Listen(IPAddress.Any, basePort, listenOptions =>
|
||||||
{
|
{
|
||||||
listenOptions.Protocols = HttpProtocols.Http3;
|
listenOptions.Protocols = HttpProtocols.Http3;
|
||||||
listenOptions.Use((next) =>
|
|
||||||
{
|
|
||||||
return async connection =>
|
|
||||||
{
|
|
||||||
var streamFeature = connection.Features.Get<IQuicStreamListenerFeature>();
|
|
||||||
if (streamFeature != null)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var connectionContext = await streamFeature.AcceptAsync();
|
|
||||||
if (connectionContext == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_ = next(connectionContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await next(connection);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
async Task EchoServer(ConnectionContext connection)
|
async Task EchoServer(MultiplexedConnectionContext connection)
|
||||||
{
|
{
|
||||||
// For graceful shutdown
|
// For graceful shutdown
|
||||||
try
|
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
|
var stream = await connection.AcceptAsync();
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var result = await connection.Transport.Input.ReadAsync();
|
var result = await stream.Transport.Input.ReadAsync();
|
||||||
|
|
||||||
if (result.IsCompleted)
|
if (result.IsCompleted)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
await connection.Transport.Output.WriteAsync(result.Buffer.ToArray());
|
await stream.Transport.Output.WriteAsync(result.Buffer.ToArray());
|
||||||
|
|
||||||
connection.Transport.Input.AdvanceTo(result.Buffer.End);
|
stream.Transport.Input.AdvanceTo(result.Buffer.End);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
listenOptions.Run(EchoServer);
|
|
||||||
|
((IMultiplexedConnectionBuilder)listenOptions).Use(next =>
|
||||||
|
{
|
||||||
|
return context =>
|
||||||
|
{
|
||||||
|
return EchoServer(context);
|
||||||
|
};
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.UseStartup<Startup>();
|
.UseStartup<Startup>();
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic;
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic;
|
||||||
using Microsoft.AspNetCore.Connections.Abstractions.Features;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -26,13 +23,12 @@ namespace QuicSampleClient
|
||||||
})
|
})
|
||||||
.ConfigureServices(services =>
|
.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
services.AddSingleton<IConnectionFactory, QuicConnectionFactory>();
|
services.AddSingleton<IMultiplexedConnectionFactory, QuicConnectionFactory>();
|
||||||
services.AddSingleton<QuicClientService>();
|
services.AddSingleton<QuicClientService>();
|
||||||
services.AddOptions<QuicTransportOptions>();
|
services.AddOptions<QuicTransportOptions>();
|
||||||
services.Configure<QuicTransportOptions>((options) =>
|
services.Configure<QuicTransportOptions>((options) =>
|
||||||
{
|
{
|
||||||
options.Alpn = "QuicTest";
|
options.Alpn = "QuicTest";
|
||||||
options.RegistrationName = "Quic-AspNetCore-client";
|
|
||||||
options.Certificate = null;
|
options.Certificate = null;
|
||||||
options.IdleTimeout = TimeSpan.FromHours(1);
|
options.IdleTimeout = TimeSpan.FromHours(1);
|
||||||
});
|
});
|
||||||
|
|
@ -43,9 +39,9 @@ namespace QuicSampleClient
|
||||||
|
|
||||||
private class QuicClientService
|
private class QuicClientService
|
||||||
{
|
{
|
||||||
private readonly IConnectionFactory _connectionFactory;
|
private readonly IMultiplexedConnectionFactory _connectionFactory;
|
||||||
private readonly ILogger<QuicClientService> _logger;
|
private readonly ILogger<QuicClientService> _logger;
|
||||||
public QuicClientService(IConnectionFactory connectionFactory, ILogger<QuicClientService> logger)
|
public QuicClientService(IMultiplexedConnectionFactory connectionFactory, ILogger<QuicClientService> logger)
|
||||||
{
|
{
|
||||||
_connectionFactory = connectionFactory;
|
_connectionFactory = connectionFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
@ -55,8 +51,7 @@ namespace QuicSampleClient
|
||||||
{
|
{
|
||||||
Console.WriteLine("Starting");
|
Console.WriteLine("Starting");
|
||||||
var connectionContext = await _connectionFactory.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 5555));
|
var connectionContext = await _connectionFactory.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 5555));
|
||||||
var createStreamFeature = connectionContext.Features.Get<IQuicCreateStreamFeature>();
|
var streamContext = await connectionContext.ConnectAsync();
|
||||||
var streamContext = await createStreamFeature.StartBidirectionalStreamAsync();
|
|
||||||
|
|
||||||
Console.CancelKeyPress += new ConsoleCancelEventHandler((sender, args) =>
|
Console.CancelKeyPress += new ConsoleCancelEventHandler((sender, args) =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
// 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.Buffers;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Threading;
|
||||||
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Connections
|
||||||
|
{
|
||||||
|
internal partial class TransportMultiplexedConnection : IConnectionIdFeature,
|
||||||
|
IConnectionItemsFeature,
|
||||||
|
IMemoryPoolFeature,
|
||||||
|
IConnectionLifetimeFeature
|
||||||
|
{
|
||||||
|
// NOTE: When feature interfaces are added to or removed from this TransportConnection class implementation,
|
||||||
|
// then the list of `features` in the generated code project MUST also be updated.
|
||||||
|
// See also: tools/CodeGenerator/TransportConnectionFeatureCollection.cs
|
||||||
|
|
||||||
|
MemoryPool<byte> IMemoryPoolFeature.MemoryPool => MemoryPool;
|
||||||
|
|
||||||
|
IDictionary<object, object> IConnectionItemsFeature.Items
|
||||||
|
{
|
||||||
|
get => Items;
|
||||||
|
set => Items = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
CancellationToken IConnectionLifetimeFeature.ConnectionClosed
|
||||||
|
{
|
||||||
|
get => ConnectionClosed;
|
||||||
|
set => ConnectionClosed = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IConnectionLifetimeFeature.Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via IConnectionLifetimeFeature.Abort()."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,242 @@
|
||||||
|
// 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.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Connections
|
||||||
|
{
|
||||||
|
internal partial class TransportMultiplexedConnection : IFeatureCollection
|
||||||
|
{
|
||||||
|
private object _currentIConnectionIdFeature;
|
||||||
|
private object _currentIConnectionTransportFeature;
|
||||||
|
private object _currentIConnectionItemsFeature;
|
||||||
|
private object _currentIMemoryPoolFeature;
|
||||||
|
private object _currentIConnectionLifetimeFeature;
|
||||||
|
|
||||||
|
private int _featureRevision;
|
||||||
|
|
||||||
|
private List<KeyValuePair<Type, object>> MaybeExtra;
|
||||||
|
|
||||||
|
private void FastReset()
|
||||||
|
{
|
||||||
|
_currentIConnectionIdFeature = this;
|
||||||
|
_currentIConnectionTransportFeature = this;
|
||||||
|
_currentIConnectionItemsFeature = this;
|
||||||
|
_currentIMemoryPoolFeature = this;
|
||||||
|
_currentIConnectionLifetimeFeature = this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal for testing
|
||||||
|
internal void ResetFeatureCollection()
|
||||||
|
{
|
||||||
|
FastReset();
|
||||||
|
MaybeExtra?.Clear();
|
||||||
|
_featureRevision++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private object ExtraFeatureGet(Type key)
|
||||||
|
{
|
||||||
|
if (MaybeExtra == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < MaybeExtra.Count; i++)
|
||||||
|
{
|
||||||
|
var kv = MaybeExtra[i];
|
||||||
|
if (kv.Key == key)
|
||||||
|
{
|
||||||
|
return kv.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExtraFeatureSet(Type key, object value)
|
||||||
|
{
|
||||||
|
if (MaybeExtra == null)
|
||||||
|
{
|
||||||
|
MaybeExtra = new List<KeyValuePair<Type, object>>(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < MaybeExtra.Count; i++)
|
||||||
|
{
|
||||||
|
if (MaybeExtra[i].Key == key)
|
||||||
|
{
|
||||||
|
MaybeExtra[i] = new KeyValuePair<Type, object>(key, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MaybeExtra.Add(new KeyValuePair<Type, object>(key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IFeatureCollection.IsReadOnly => false;
|
||||||
|
|
||||||
|
int IFeatureCollection.Revision => _featureRevision;
|
||||||
|
|
||||||
|
object IFeatureCollection.this[Type key]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
object feature = null;
|
||||||
|
if (key == typeof(IConnectionIdFeature))
|
||||||
|
{
|
||||||
|
feature = _currentIConnectionIdFeature;
|
||||||
|
}
|
||||||
|
else if (key == typeof(IConnectionTransportFeature))
|
||||||
|
{
|
||||||
|
feature = _currentIConnectionTransportFeature;
|
||||||
|
}
|
||||||
|
else if (key == typeof(IConnectionItemsFeature))
|
||||||
|
{
|
||||||
|
feature = _currentIConnectionItemsFeature;
|
||||||
|
}
|
||||||
|
else if (key == typeof(IMemoryPoolFeature))
|
||||||
|
{
|
||||||
|
feature = _currentIMemoryPoolFeature;
|
||||||
|
}
|
||||||
|
else if (key == typeof(IConnectionLifetimeFeature))
|
||||||
|
{
|
||||||
|
feature = _currentIConnectionLifetimeFeature;
|
||||||
|
}
|
||||||
|
else if (MaybeExtra != null)
|
||||||
|
{
|
||||||
|
feature = ExtraFeatureGet(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return feature;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_featureRevision++;
|
||||||
|
|
||||||
|
if (key == typeof(IConnectionIdFeature))
|
||||||
|
{
|
||||||
|
_currentIConnectionIdFeature = value;
|
||||||
|
}
|
||||||
|
else if (key == typeof(IConnectionTransportFeature))
|
||||||
|
{
|
||||||
|
_currentIConnectionTransportFeature = value;
|
||||||
|
}
|
||||||
|
else if (key == typeof(IConnectionItemsFeature))
|
||||||
|
{
|
||||||
|
_currentIConnectionItemsFeature = value;
|
||||||
|
}
|
||||||
|
else if (key == typeof(IMemoryPoolFeature))
|
||||||
|
{
|
||||||
|
_currentIMemoryPoolFeature = value;
|
||||||
|
}
|
||||||
|
else if (key == typeof(IConnectionLifetimeFeature))
|
||||||
|
{
|
||||||
|
_currentIConnectionLifetimeFeature = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExtraFeatureSet(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TFeature IFeatureCollection.Get<TFeature>()
|
||||||
|
{
|
||||||
|
TFeature feature = default;
|
||||||
|
if (typeof(TFeature) == typeof(IConnectionIdFeature))
|
||||||
|
{
|
||||||
|
feature = (TFeature)_currentIConnectionIdFeature;
|
||||||
|
}
|
||||||
|
else if (typeof(TFeature) == typeof(IConnectionTransportFeature))
|
||||||
|
{
|
||||||
|
feature = (TFeature)_currentIConnectionTransportFeature;
|
||||||
|
}
|
||||||
|
else if (typeof(TFeature) == typeof(IConnectionItemsFeature))
|
||||||
|
{
|
||||||
|
feature = (TFeature)_currentIConnectionItemsFeature;
|
||||||
|
}
|
||||||
|
else if (typeof(TFeature) == typeof(IMemoryPoolFeature))
|
||||||
|
{
|
||||||
|
feature = (TFeature)_currentIMemoryPoolFeature;
|
||||||
|
}
|
||||||
|
else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature))
|
||||||
|
{
|
||||||
|
feature = (TFeature)_currentIConnectionLifetimeFeature;
|
||||||
|
}
|
||||||
|
else if (MaybeExtra != null)
|
||||||
|
{
|
||||||
|
feature = (TFeature)(ExtraFeatureGet(typeof(TFeature)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return feature;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IFeatureCollection.Set<TFeature>(TFeature feature)
|
||||||
|
{
|
||||||
|
_featureRevision++;
|
||||||
|
if (typeof(TFeature) == typeof(IConnectionIdFeature))
|
||||||
|
{
|
||||||
|
_currentIConnectionIdFeature = feature;
|
||||||
|
}
|
||||||
|
else if (typeof(TFeature) == typeof(IConnectionTransportFeature))
|
||||||
|
{
|
||||||
|
_currentIConnectionTransportFeature = feature;
|
||||||
|
}
|
||||||
|
else if (typeof(TFeature) == typeof(IConnectionItemsFeature))
|
||||||
|
{
|
||||||
|
_currentIConnectionItemsFeature = feature;
|
||||||
|
}
|
||||||
|
else if (typeof(TFeature) == typeof(IMemoryPoolFeature))
|
||||||
|
{
|
||||||
|
_currentIMemoryPoolFeature = feature;
|
||||||
|
}
|
||||||
|
else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature))
|
||||||
|
{
|
||||||
|
_currentIConnectionLifetimeFeature = feature;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExtraFeatureSet(typeof(TFeature), feature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<KeyValuePair<Type, object>> FastEnumerable()
|
||||||
|
{
|
||||||
|
if (_currentIConnectionIdFeature != null)
|
||||||
|
{
|
||||||
|
yield return new KeyValuePair<Type, object>(typeof(IConnectionIdFeature), _currentIConnectionIdFeature);
|
||||||
|
}
|
||||||
|
if (_currentIConnectionTransportFeature != null)
|
||||||
|
{
|
||||||
|
yield return new KeyValuePair<Type, object>(typeof(IConnectionTransportFeature), _currentIConnectionTransportFeature);
|
||||||
|
}
|
||||||
|
if (_currentIConnectionItemsFeature != null)
|
||||||
|
{
|
||||||
|
yield return new KeyValuePair<Type, object>(typeof(IConnectionItemsFeature), _currentIConnectionItemsFeature);
|
||||||
|
}
|
||||||
|
if (_currentIMemoryPoolFeature != null)
|
||||||
|
{
|
||||||
|
yield return new KeyValuePair<Type, object>(typeof(IMemoryPoolFeature), _currentIMemoryPoolFeature);
|
||||||
|
}
|
||||||
|
if (_currentIConnectionLifetimeFeature != null)
|
||||||
|
{
|
||||||
|
yield return new KeyValuePair<Type, object>(typeof(IConnectionLifetimeFeature), _currentIConnectionLifetimeFeature);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MaybeExtra != null)
|
||||||
|
{
|
||||||
|
foreach (var item in MaybeExtra)
|
||||||
|
{
|
||||||
|
yield return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator<KeyValuePair<Type, object>> IEnumerable<KeyValuePair<Type, object>>.GetEnumerator() => FastEnumerable().GetEnumerator();
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
// 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.Buffers;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Connections
|
||||||
|
{
|
||||||
|
internal abstract partial class TransportMultiplexedConnection : MultiplexedConnectionContext
|
||||||
|
{
|
||||||
|
private IDictionary<object, object> _items;
|
||||||
|
private string _connectionId;
|
||||||
|
|
||||||
|
public TransportMultiplexedConnection()
|
||||||
|
{
|
||||||
|
FastReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override EndPoint LocalEndPoint { get; set; }
|
||||||
|
public override EndPoint RemoteEndPoint { get; set; }
|
||||||
|
|
||||||
|
public override string ConnectionId
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_connectionId == null)
|
||||||
|
{
|
||||||
|
_connectionId = CorrelationIdGenerator.GetNextId();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _connectionId;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_connectionId = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IFeatureCollection Features => this;
|
||||||
|
|
||||||
|
public virtual MemoryPool<byte> MemoryPool { get; }
|
||||||
|
|
||||||
|
public IDuplexPipe Application { get; set; }
|
||||||
|
|
||||||
|
public override IDictionary<object, object> Items
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// Lazily allocate connection metadata
|
||||||
|
return _items ?? (_items = new ConnectionItems());
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_items = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CancellationToken ConnectionClosed { get; set; }
|
||||||
|
|
||||||
|
// DO NOT remove this override to ConnectionContext.Abort. Doing so would cause
|
||||||
|
// any TransportConnection that does not override Abort or calls base.Abort
|
||||||
|
// to stack overflow when IConnectionLifetimeFeature.Abort() is called.
|
||||||
|
// That said, all derived types should override this method should override
|
||||||
|
// this implementation of Abort because canceling pending output reads is not
|
||||||
|
// sufficient to abort the connection if there is backpressure.
|
||||||
|
public override void Abort(ConnectionAbortedException abortReason)
|
||||||
|
{
|
||||||
|
Application.Input.CancelPendingRead();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -24,13 +26,68 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
var requestStream = await InitializeConnectionAndStreamsAsync(_echoApplication);
|
var requestStream = await InitializeConnectionAndStreamsAsync(_echoApplication);
|
||||||
|
|
||||||
var doneWithHeaders = await requestStream.SendHeadersAsync(headers);
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers);
|
||||||
await requestStream.SendDataAsync(Encoding.ASCII.GetBytes("Hello world"));
|
await requestStream.SendDataAsync(Encoding.ASCII.GetBytes("Hello world"), endStream: true);
|
||||||
|
|
||||||
var responseHeaders = await requestStream.ExpectHeadersAsync();
|
var responseHeaders = await requestStream.ExpectHeadersAsync();
|
||||||
var responseData = await requestStream.ExpectDataAsync();
|
var responseData = await requestStream.ExpectDataAsync();
|
||||||
Assert.Equal("Hello world", Encoding.ASCII.GetString(responseData.ToArray()));
|
Assert.Equal("Hello world", Encoding.ASCII.GetString(responseData.ToArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EmptyMethod_Reset()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, ""),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_echoApplication);
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers);
|
||||||
|
await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, CoreStrings.FormatHttp3ErrorMethodInvalid(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvalidCustomMethod_Reset()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "Hello,World"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_echoApplication);
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers);
|
||||||
|
await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, CoreStrings.FormatHttp3ErrorMethodInvalid("Hello,World"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CustomMethod_Accepted()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "Custom"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_echoMethod);
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
var responseHeaders = await requestStream.ExpectHeadersAsync();
|
||||||
|
|
||||||
|
Assert.Equal(4, responseHeaders.Count);
|
||||||
|
Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", responseHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("Custom", responseHeaders["Method"]);
|
||||||
|
Assert.Equal("0", responseHeaders["content-length"]);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task RequestHeadersMaxRequestHeaderFieldSize_EndsStream()
|
public async Task RequestHeadersMaxRequestHeaderFieldSize_EndsStream()
|
||||||
{
|
{
|
||||||
|
|
@ -51,5 +108,498 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
// TODO figure out how to test errors for request streams that would be set on the Quic Stream.
|
// TODO figure out how to test errors for request streams that would be set on the Quic Stream.
|
||||||
await requestStream.ExpectReceiveEndOfStream();
|
await requestStream.ExpectReceiveEndOfStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ConnectMethod_Accepted()
|
||||||
|
{
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_echoMethod);
|
||||||
|
|
||||||
|
// :path and :scheme are not allowed, :authority is optional
|
||||||
|
var headers = new[] { new KeyValuePair<string, string>(HeaderNames.Method, "CONNECT") };
|
||||||
|
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
var responseHeaders = await requestStream.ExpectHeadersAsync();
|
||||||
|
|
||||||
|
Assert.Equal(4, responseHeaders.Count);
|
||||||
|
Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", responseHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("CONNECT", responseHeaders["Method"]);
|
||||||
|
Assert.Equal("0", responseHeaders["content-length"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task OptionsStar_LeftOutOfPath()
|
||||||
|
{
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_echoPath);
|
||||||
|
var headers = new[] { new KeyValuePair<string, string>(HeaderNames.Method, "OPTIONS"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "*")};
|
||||||
|
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
var responseHeaders = await requestStream.ExpectHeadersAsync();
|
||||||
|
|
||||||
|
Assert.Equal(5, responseHeaders.Count);
|
||||||
|
Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", responseHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("", responseHeaders["path"]);
|
||||||
|
Assert.Equal("*", responseHeaders["rawtarget"]);
|
||||||
|
Assert.Equal("0", responseHeaders["content-length"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task OptionsSlash_Accepted()
|
||||||
|
{
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_echoPath);
|
||||||
|
|
||||||
|
var headers = new[] { new KeyValuePair<string, string>(HeaderNames.Method, "OPTIONS"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/")};
|
||||||
|
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
var responseHeaders = await requestStream.ExpectHeadersAsync();
|
||||||
|
|
||||||
|
Assert.Equal(5, responseHeaders.Count);
|
||||||
|
Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", responseHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("/", responseHeaders["path"]);
|
||||||
|
Assert.Equal("/", responseHeaders["rawtarget"]);
|
||||||
|
Assert.Equal("0", responseHeaders["content-length"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PathAndQuery_Separated()
|
||||||
|
{
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(context =>
|
||||||
|
{
|
||||||
|
context.Response.Headers["path"] = context.Request.Path.Value;
|
||||||
|
context.Response.Headers["query"] = context.Request.QueryString.Value;
|
||||||
|
context.Response.Headers["rawtarget"] = context.Features.Get<IHttpRequestFeature>().RawTarget;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
|
||||||
|
// :path and :scheme are not allowed, :authority is optional
|
||||||
|
var headers = new[] { new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/a/path?a&que%35ry")};
|
||||||
|
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
var responseHeaders = await requestStream.ExpectHeadersAsync();
|
||||||
|
|
||||||
|
Assert.Equal(6, responseHeaders.Count);
|
||||||
|
Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", responseHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("/a/path", responseHeaders["path"]);
|
||||||
|
Assert.Equal("?a&que%35ry", responseHeaders["query"]);
|
||||||
|
Assert.Equal("/a/path?a&que%35ry", responseHeaders["rawtarget"]);
|
||||||
|
Assert.Equal("0", responseHeaders["content-length"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("/", "/")]
|
||||||
|
[InlineData("/a%5E", "/a^")]
|
||||||
|
[InlineData("/a%E2%82%AC", "/a€")]
|
||||||
|
[InlineData("/a%2Fb", "/a%2Fb")] // Forward slash, not decoded
|
||||||
|
[InlineData("/a%b", "/a%b")] // Incomplete encoding, not decoded
|
||||||
|
[InlineData("/a/b/c/../d", "/a/b/d")] // Navigation processed
|
||||||
|
[InlineData("/a/b/c/../../../../d", "/d")] // Navigation escape prevented
|
||||||
|
[InlineData("/a/b/c/.%2E/d", "/a/b/d")] // Decode before navigation processing
|
||||||
|
public async Task Path_DecodedAndNormalized(string input, string expected)
|
||||||
|
{
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(context =>
|
||||||
|
{
|
||||||
|
Assert.Equal(expected, context.Request.Path.Value);
|
||||||
|
Assert.Equal(input, context.Features.Get<IHttpRequestFeature>().RawTarget);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
|
||||||
|
// :path and :scheme are not allowed, :authority is optional
|
||||||
|
var headers = new[] { new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, input)};
|
||||||
|
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
var responseHeaders = await requestStream.ExpectHeadersAsync();
|
||||||
|
|
||||||
|
Assert.Equal(3, responseHeaders.Count);
|
||||||
|
Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", responseHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("0", responseHeaders["content-length"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(":path", "/")]
|
||||||
|
[InlineData(":scheme", "http")]
|
||||||
|
public async Task ConnectMethod_WithSchemeOrPath_Reset(string headerName, string value)
|
||||||
|
{
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication);
|
||||||
|
|
||||||
|
// :path and :scheme are not allowed, :authority is optional
|
||||||
|
var headers = new[] { new KeyValuePair<string, string>(HeaderNames.Method, "CONNECT"),
|
||||||
|
new KeyValuePair<string, string>(headerName, value) };
|
||||||
|
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, CoreStrings.Http3ErrorConnectMustNotSendSchemeOrPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SchemeMismatch_Reset()
|
||||||
|
{
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication);
|
||||||
|
|
||||||
|
// :path and :scheme are not allowed, :authority is optional
|
||||||
|
var headers = new[] { new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "https") }; // Not the expected "http"
|
||||||
|
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, CoreStrings.FormatHttp3StreamErrorSchemeMismatch("https", "http"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MissingAuthority_200Status()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
};
|
||||||
|
await InitializeConnectionAsync(_noopApplication);
|
||||||
|
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication);
|
||||||
|
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
var responseHeaders = await requestStream.ExpectHeadersAsync();
|
||||||
|
|
||||||
|
Assert.Equal(3, responseHeaders.Count);
|
||||||
|
Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", responseHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("0", responseHeaders["content-length"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EmptyAuthority_200Status()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Authority, ""),
|
||||||
|
};
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication);
|
||||||
|
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
var responseHeaders = await requestStream.ExpectHeadersAsync();
|
||||||
|
|
||||||
|
Assert.Equal(3, responseHeaders.Count);
|
||||||
|
Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", responseHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("0", responseHeaders["content-length"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MissingAuthorityFallsBackToHost_200Status()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>("Host", "abc"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_echoHost);
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
var responseHeaders = await requestStream.ExpectHeadersAsync();
|
||||||
|
|
||||||
|
Assert.Equal(4, responseHeaders.Count);
|
||||||
|
Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", responseHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]);
|
||||||
|
Assert.Equal("abc", responseHeaders[HeaderNames.Host]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EmptyAuthorityIgnoredOverHost_200Status()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Authority, ""),
|
||||||
|
new KeyValuePair<string, string>("Host", "abc"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_echoHost);
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
var responseHeaders = await requestStream.ExpectHeadersAsync();
|
||||||
|
|
||||||
|
Assert.Equal(4, responseHeaders.Count);
|
||||||
|
Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", responseHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]);
|
||||||
|
Assert.Equal("abc", responseHeaders[HeaderNames.Host]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AuthorityOverridesHost_200Status()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Authority, "def"),
|
||||||
|
new KeyValuePair<string, string>("Host", "abc"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_echoHost);
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
var responseHeaders = await requestStream.ExpectHeadersAsync();
|
||||||
|
|
||||||
|
Assert.Equal(4, responseHeaders.Count);
|
||||||
|
Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", responseHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]);
|
||||||
|
Assert.Equal("def", responseHeaders[HeaderNames.Host]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AuthorityOverridesInvalidHost_200Status()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Authority, "def"),
|
||||||
|
new KeyValuePair<string, string>("Host", "a=bc"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_echoHost);
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
var responseHeaders = await requestStream.ExpectHeadersAsync();
|
||||||
|
|
||||||
|
Assert.Equal(4, responseHeaders.Count);
|
||||||
|
Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", responseHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]);
|
||||||
|
Assert.Equal("def", responseHeaders[HeaderNames.Host]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvalidAuthority_Reset()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Authority, "local=host:80"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication);
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError,
|
||||||
|
CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("local=host:80"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvalidAuthorityWithValidHost_Reset()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Authority, "d=ef"),
|
||||||
|
new KeyValuePair<string, string>("Host", "abc"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication);
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError,
|
||||||
|
CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("d=ef"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task TwoHosts_StreamReset()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>("Host", "host1"),
|
||||||
|
new KeyValuePair<string, string>("Host", "host2"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication);
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError,
|
||||||
|
CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("host1,host2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MaxRequestLineSize_Reset()
|
||||||
|
{
|
||||||
|
// Default 8kb limit
|
||||||
|
// This test has to work around the HPack parser limit for incoming field sizes over 4kb. That's going to be a problem for people with long urls.
|
||||||
|
// https://github.com/aspnet/KestrelHttpServer/issues/2872
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "GET" + new string('a', 1024 * 3)),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/Hello/How/Are/You/" + new string('a', 1024 * 3)),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost" + new string('a', 1024 * 3) + ":80"),
|
||||||
|
};
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication);
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError,
|
||||||
|
CoreStrings.BadRequest_RequestLineTooLong);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ContentLength_Received_SingleDataFrame_Verified()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(async context =>
|
||||||
|
{
|
||||||
|
var buffer = new byte[100];
|
||||||
|
var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
|
||||||
|
Assert.Equal(12, read);
|
||||||
|
read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
|
||||||
|
Assert.Equal(0, read);
|
||||||
|
});
|
||||||
|
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: false);
|
||||||
|
await requestStream.SendDataAsync(new byte[12], endStream: true);
|
||||||
|
|
||||||
|
var responseHeaders = await requestStream.ExpectHeadersAsync();
|
||||||
|
|
||||||
|
Assert.Equal(3, responseHeaders.Count);
|
||||||
|
Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", responseHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ContentLength_Received_MultipleDataFrame_Verified()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(async context =>
|
||||||
|
{
|
||||||
|
var buffer = new byte[100];
|
||||||
|
var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
|
||||||
|
var total = read;
|
||||||
|
while (read > 0)
|
||||||
|
{
|
||||||
|
read = await context.Request.Body.ReadAsync(buffer, total, buffer.Length - total);
|
||||||
|
total += read;
|
||||||
|
}
|
||||||
|
Assert.Equal(12, total);
|
||||||
|
});
|
||||||
|
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: false);
|
||||||
|
|
||||||
|
await requestStream.SendDataAsync(new byte[1], endStream: false);
|
||||||
|
await requestStream.SendDataAsync(new byte[3], endStream: false);
|
||||||
|
await requestStream.SendDataAsync(new byte[8], endStream: true);
|
||||||
|
|
||||||
|
var responseHeaders = await requestStream.ExpectHeadersAsync();
|
||||||
|
|
||||||
|
Assert.Equal(3, responseHeaders.Count);
|
||||||
|
Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", responseHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ContentLength_Received_MultipleDataFrame_ReadViaPipe_Verified()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
|
||||||
|
};
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(async context =>
|
||||||
|
{
|
||||||
|
var readResult = await context.Request.BodyReader.ReadAsync();
|
||||||
|
while (!readResult.IsCompleted)
|
||||||
|
{
|
||||||
|
context.Request.BodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
|
||||||
|
readResult = await context.Request.BodyReader.ReadAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(12, readResult.Buffer.Length);
|
||||||
|
context.Request.BodyReader.AdvanceTo(readResult.Buffer.End);
|
||||||
|
});
|
||||||
|
|
||||||
|
var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: false);
|
||||||
|
|
||||||
|
await requestStream.SendDataAsync(new byte[1], endStream: false);
|
||||||
|
await requestStream.SendDataAsync(new byte[3], endStream: false);
|
||||||
|
await requestStream.SendDataAsync(new byte[8], endStream: true);
|
||||||
|
|
||||||
|
var responseHeaders = await requestStream.ExpectHeadersAsync();
|
||||||
|
|
||||||
|
Assert.Equal(3, responseHeaders.Count);
|
||||||
|
Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", responseHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact(Skip = "Http3OutputProducer.Complete is called before input recognizes there is an error. Why is this different than HTTP/2?")]
|
||||||
|
public async Task ContentLength_Received_NoDataFrames_Reset()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication);
|
||||||
|
|
||||||
|
await requestStream.SendHeadersAsync(headers, endStream: true);
|
||||||
|
|
||||||
|
await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, CoreStrings.Http3StreamErrorLessDataThanLength);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,50 +6,55 @@ using System.IO.Pipelines;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.QPack;
|
using System.Net.Http.QPack;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Connections.Abstractions.Features;
|
|
||||||
using Microsoft.AspNetCore.Connections.Features;
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
using static System.IO.Pipelines.DuplexPipe;
|
||||||
using static Microsoft.AspNetCore.Server.Kestrel.Core.Tests.Http2TestBase;
|
using static Microsoft.AspNetCore.Server.Kestrel.Core.Tests.Http2TestBase;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
public class Http3TestBase : TestApplicationErrorLoggerLoggedTest, IDisposable, IQuicCreateStreamFeature, IQuicStreamListenerFeature
|
public class Http3TestBase : TestApplicationErrorLoggerLoggedTest, IDisposable
|
||||||
{
|
{
|
||||||
internal TestServiceContext _serviceContext;
|
internal TestServiceContext _serviceContext;
|
||||||
internal Http3Connection _connection;
|
internal Http3Connection _connection;
|
||||||
internal readonly TimeoutControl _timeoutControl;
|
internal readonly TimeoutControl _timeoutControl;
|
||||||
internal readonly Mock<IKestrelTrace> _mockKestrelTrace = new Mock<IKestrelTrace>();
|
internal readonly Mock<IKestrelTrace> _mockKestrelTrace = new Mock<IKestrelTrace>();
|
||||||
protected readonly Mock<ConnectionContext> _mockConnectionContext = new Mock<ConnectionContext>();
|
|
||||||
internal readonly Mock<ITimeoutHandler> _mockTimeoutHandler = new Mock<ITimeoutHandler>();
|
internal readonly Mock<ITimeoutHandler> _mockTimeoutHandler = new Mock<ITimeoutHandler>();
|
||||||
internal readonly Mock<MockTimeoutControlBase> _mockTimeoutControl;
|
internal readonly Mock<MockTimeoutControlBase> _mockTimeoutControl;
|
||||||
internal readonly MemoryPool<byte> _memoryPool = SlabMemoryPoolFactory.Create();
|
internal readonly MemoryPool<byte> _memoryPool = SlabMemoryPoolFactory.Create();
|
||||||
protected Task _connectionTask;
|
protected Task _connectionTask;
|
||||||
protected readonly RequestDelegate _echoApplication;
|
private TestMultiplexedConnectionContext _multiplexedContext;
|
||||||
|
private readonly CancellationTokenSource _connectionClosingCts = new CancellationTokenSource();
|
||||||
|
|
||||||
private readonly Channel<ConnectionContext> _acceptConnectionQueue = Channel.CreateUnbounded<ConnectionContext>(new UnboundedChannelOptions
|
protected readonly RequestDelegate _noopApplication;
|
||||||
{
|
protected readonly RequestDelegate _echoApplication;
|
||||||
SingleReader = true,
|
protected readonly RequestDelegate _echoMethod;
|
||||||
SingleWriter = true
|
protected readonly RequestDelegate _echoPath;
|
||||||
});
|
protected readonly RequestDelegate _echoHost;
|
||||||
|
|
||||||
public Http3TestBase()
|
public Http3TestBase()
|
||||||
{
|
{
|
||||||
_timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object);
|
_timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object);
|
||||||
_mockTimeoutControl = new Mock<MockTimeoutControlBase>(_timeoutControl) { CallBase = true };
|
_mockTimeoutControl = new Mock<MockTimeoutControlBase>(_timeoutControl) { CallBase = true };
|
||||||
_timeoutControl.Debugger = Mock.Of<IDebugger>();
|
_timeoutControl.Debugger = Mock.Of<IDebugger>();
|
||||||
|
|
||||||
|
_noopApplication = context => Task.CompletedTask;
|
||||||
|
|
||||||
_echoApplication = async context =>
|
_echoApplication = async context =>
|
||||||
{
|
{
|
||||||
var buffer = new byte[Http3PeerSettings.MinAllowedMaxFrameSize];
|
var buffer = new byte[Http3PeerSettings.MinAllowedMaxFrameSize];
|
||||||
|
|
@ -60,6 +65,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await context.Response.Body.WriteAsync(buffer, 0, received);
|
await context.Response.Body.WriteAsync(buffer, 0, received);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_echoMethod = context =>
|
||||||
|
{
|
||||||
|
context.Response.Headers["Method"] = context.Request.Method;
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
_echoPath = context =>
|
||||||
|
{
|
||||||
|
context.Response.Headers["path"] = context.Request.Path.ToString();
|
||||||
|
context.Response.Headers["rawtarget"] = context.Features.Get<IHttpRequestFeature>().RawTarget;
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
_echoHost = context =>
|
||||||
|
{
|
||||||
|
context.Response.Headers[HeaderNames.Host] = context.Request.Headers[HeaderNames.Host];
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Initialize(TestContext context, MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper)
|
public override void Initialize(TestContext context, MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper)
|
||||||
|
|
@ -79,7 +106,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
CreateConnection();
|
CreateConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
_connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(application));
|
// Skip all heartbeat and lifetime notification feature registrations.
|
||||||
|
_connectionTask = _connection.InnerProcessRequestsAsync(new DummyApplication(application));
|
||||||
|
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
@ -100,24 +128,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
var limits = _serviceContext.ServerOptions.Limits;
|
var limits = _serviceContext.ServerOptions.Limits;
|
||||||
|
|
||||||
var features = new FeatureCollection();
|
var features = new FeatureCollection();
|
||||||
features.Set<IQuicCreateStreamFeature>(this);
|
|
||||||
features.Set<IQuicStreamListenerFeature>(this);
|
|
||||||
|
|
||||||
var httpConnectionContext = new HttpConnectionContext
|
_multiplexedContext = new TestMultiplexedConnectionContext(this);
|
||||||
|
|
||||||
|
var httpConnectionContext = new Http3ConnectionContext
|
||||||
{
|
{
|
||||||
ConnectionContext = _mockConnectionContext.Object,
|
ConnectionContext = _multiplexedContext,
|
||||||
ConnectionFeatures = features,
|
ConnectionFeatures = features,
|
||||||
ServiceContext = _serviceContext,
|
ServiceContext = _serviceContext,
|
||||||
MemoryPool = _memoryPool,
|
MemoryPool = _memoryPool,
|
||||||
Transport = null, // Make sure it's null
|
|
||||||
TimeoutControl = _mockTimeoutControl.Object
|
TimeoutControl = _mockTimeoutControl.Object
|
||||||
};
|
};
|
||||||
|
|
||||||
_connection = new Http3Connection(httpConnectionContext);
|
_connection = new Http3Connection(httpConnectionContext);
|
||||||
var httpConnection = new HttpConnection(httpConnectionContext);
|
|
||||||
httpConnection.Initialize(_connection);
|
|
||||||
_mockTimeoutHandler.Setup(h => h.OnTimeout(It.IsAny<TimeoutReason>()))
|
_mockTimeoutHandler.Setup(h => h.OnTimeout(It.IsAny<TimeoutReason>()))
|
||||||
.Callback<TimeoutReason>(r => httpConnection.OnTimeout(r));
|
.Callback<TimeoutReason>(r => _connection.OnTimeout(r));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PipeOptions GetInputPipeOptions(ServiceContext serviceContext, MemoryPool<byte> memoryPool, PipeScheduler writerScheduler) => new PipeOptions
|
private static PipeOptions GetInputPipeOptions(ServiceContext serviceContext, MemoryPool<byte> memoryPool, PipeScheduler writerScheduler) => new PipeOptions
|
||||||
|
|
@ -155,30 +180,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
return bufferSize ?? 0;
|
return bufferSize ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask<ConnectionContext> StartUnidirectionalStreamAsync()
|
|
||||||
{
|
|
||||||
var stream = new Http3ControlStream(this, _connection);
|
|
||||||
// TODO put these somewhere to be read.
|
|
||||||
return new ValueTask<ConnectionContext>(stream.ConnectionContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<ConnectionContext> AcceptAsync()
|
|
||||||
{
|
|
||||||
while (await _acceptConnectionQueue.Reader.WaitToReadAsync())
|
|
||||||
{
|
|
||||||
while (_acceptConnectionQueue.Reader.TryRead(out var connection))
|
|
||||||
{
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal async ValueTask<Http3ControlStream> CreateControlStream(int id)
|
internal async ValueTask<Http3ControlStream> CreateControlStream(int id)
|
||||||
{
|
{
|
||||||
var stream = new Http3ControlStream(this, _connection);
|
var stream = new Http3ControlStream(this);
|
||||||
_acceptConnectionQueue.Writer.TryWrite(stream.ConnectionContext);
|
_multiplexedContext.AcceptQueue.Writer.TryWrite(stream.StreamContext);
|
||||||
await stream.WriteStreamIdAsync(id);
|
await stream.WriteStreamIdAsync(id);
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
@ -186,7 +191,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
internal ValueTask<Http3RequestStream> CreateRequestStream()
|
internal ValueTask<Http3RequestStream> CreateRequestStream()
|
||||||
{
|
{
|
||||||
var stream = new Http3RequestStream(this, _connection);
|
var stream = new Http3RequestStream(this, _connection);
|
||||||
_acceptConnectionQueue.Writer.TryWrite(stream.ConnectionContext);
|
_multiplexedContext.AcceptQueue.Writer.TryWrite(stream.StreamContext);
|
||||||
return new ValueTask<Http3RequestStream>(stream);
|
return new ValueTask<Http3RequestStream>(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,7 +199,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
var stream = new Http3RequestStream(this, _connection);
|
var stream = new Http3RequestStream(this, _connection);
|
||||||
// TODO put these somewhere to be read.
|
// TODO put these somewhere to be read.
|
||||||
return new ValueTask<ConnectionContext>(stream.ConnectionContext);
|
return new ValueTask<ConnectionContext>(stream.StreamContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class Http3StreamBase
|
internal class Http3StreamBase
|
||||||
|
|
@ -216,15 +221,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class Http3RequestStream : Http3StreamBase, IHttpHeadersHandler, IQuicStreamFeature
|
internal class Http3RequestStream : Http3StreamBase, IHttpHeadersHandler, IProtocolErrorCodeFeature
|
||||||
{
|
{
|
||||||
internal ConnectionContext ConnectionContext { get; }
|
internal ConnectionContext StreamContext { get; }
|
||||||
|
|
||||||
public bool CanRead => true;
|
public bool CanRead => true;
|
||||||
public bool CanWrite => true;
|
public bool CanWrite => true;
|
||||||
|
|
||||||
public long StreamId => 0;
|
public long StreamId => 0;
|
||||||
|
|
||||||
|
public long Error { get; set; }
|
||||||
|
|
||||||
private readonly byte[] _headerEncodingBuffer = new byte[Http3PeerSettings.MinAllowedMaxFrameSize];
|
private readonly byte[] _headerEncodingBuffer = new byte[Http3PeerSettings.MinAllowedMaxFrameSize];
|
||||||
private QPackEncoder _qpackEncoder = new QPackEncoder();
|
private QPackEncoder _qpackEncoder = new QPackEncoder();
|
||||||
private QPackDecoder _qpackDecoder = new QPackDecoder(8192);
|
private QPackDecoder _qpackDecoder = new QPackDecoder(8192);
|
||||||
|
|
@ -239,13 +246,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
var outputPipeOptions = GetOutputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool);
|
var outputPipeOptions = GetOutputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool);
|
||||||
|
|
||||||
_pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions);
|
_pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions);
|
||||||
|
|
||||||
ConnectionContext = new DefaultConnectionContext();
|
StreamContext = new TestStreamContext(canRead: true, canWrite: true, _pair, this);
|
||||||
ConnectionContext.Transport = _pair.Transport;
|
|
||||||
ConnectionContext.Features.Set<IQuicStreamFeature>(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> SendHeadersAsync(IEnumerable<KeyValuePair<string, string>> headers)
|
public async Task<bool> SendHeadersAsync(IEnumerable<KeyValuePair<string, string>> headers, bool endStream = false)
|
||||||
{
|
{
|
||||||
var outputWriter = _pair.Application.Output;
|
var outputWriter = _pair.Application.Output;
|
||||||
var frame = new Http3RawFrame();
|
var frame = new Http3RawFrame();
|
||||||
|
|
@ -256,10 +261,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
// TODO may want to modify behavior of input frames to mock different client behavior (client can send anything).
|
// TODO may want to modify behavior of input frames to mock different client behavior (client can send anything).
|
||||||
Http3FrameWriter.WriteHeader(frame, outputWriter);
|
Http3FrameWriter.WriteHeader(frame, outputWriter);
|
||||||
await SendAsync(buffer.Span.Slice(0, length));
|
await SendAsync(buffer.Span.Slice(0, length));
|
||||||
|
|
||||||
|
if (endStream)
|
||||||
|
{
|
||||||
|
await _pair.Application.Output.CompleteAsync();
|
||||||
|
}
|
||||||
|
|
||||||
return done;
|
return done;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task SendDataAsync(Memory<byte> data)
|
internal async Task SendDataAsync(Memory<byte> data, bool endStream = false)
|
||||||
{
|
{
|
||||||
var outputWriter = _pair.Application.Output;
|
var outputWriter = _pair.Application.Output;
|
||||||
var frame = new Http3RawFrame();
|
var frame = new Http3RawFrame();
|
||||||
|
|
@ -267,9 +278,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
frame.Length = data.Length;
|
frame.Length = data.Length;
|
||||||
Http3FrameWriter.WriteHeader(frame, outputWriter);
|
Http3FrameWriter.WriteHeader(frame, outputWriter);
|
||||||
await SendAsync(data.Span);
|
await SendAsync(data.Span);
|
||||||
|
|
||||||
|
if (endStream)
|
||||||
|
{
|
||||||
|
await _pair.Application.Output.CompleteAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<IEnumerable<KeyValuePair<string, string>>> ExpectHeadersAsync()
|
internal async Task<Dictionary<string, string>> ExpectHeadersAsync()
|
||||||
{
|
{
|
||||||
var http3WithPayload = await ReceiveFrameAsync();
|
var http3WithPayload = await ReceiveFrameAsync();
|
||||||
_qpackDecoder.Decode(http3WithPayload.PayloadSequence, this);
|
_qpackDecoder.Decode(http3WithPayload.PayloadSequence, this);
|
||||||
|
|
@ -347,6 +363,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
_decodedHeaders[((Span<byte>)H3StaticTable.Instance[index].Name).GetAsciiStringNonNullCharacters()] = value.GetAsciiOrUTF8StringNonNullCharacters();
|
_decodedHeaders[((Span<byte>)H3StaticTable.Instance[index].Name).GetAsciiStringNonNullCharacters()] = value.GetAsciiOrUTF8StringNonNullCharacters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal async Task WaitForStreamErrorAsync(Http3ErrorCode protocolError, string expectedErrorMessage)
|
||||||
|
{
|
||||||
|
var readResult = await _pair.Application.Input.ReadAsync();
|
||||||
|
_testBase.Logger.LogTrace("Input is completed");
|
||||||
|
|
||||||
|
Assert.True(readResult.IsCompleted);
|
||||||
|
Assert.Equal((long)protocolError, Error);
|
||||||
|
|
||||||
|
if (expectedErrorMessage != null)
|
||||||
|
{
|
||||||
|
Assert.Contains(_testBase.TestApplicationErrorLogger.Messages, m => m.Exception?.Message.Contains(expectedErrorMessage) ?? false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class Http3FrameWithPayload : Http3RawFrame
|
internal class Http3FrameWithPayload : Http3RawFrame
|
||||||
|
|
@ -362,28 +392,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal class Http3ControlStream : Http3StreamBase, IQuicStreamFeature
|
internal class Http3ControlStream : Http3StreamBase, IProtocolErrorCodeFeature
|
||||||
{
|
{
|
||||||
internal ConnectionContext ConnectionContext { get; }
|
internal ConnectionContext StreamContext { get; }
|
||||||
|
|
||||||
public bool CanRead => true;
|
public bool CanRead => true;
|
||||||
public bool CanWrite => false;
|
public bool CanWrite => false;
|
||||||
|
|
||||||
// TODO
|
|
||||||
public long StreamId => 0;
|
public long StreamId => 0;
|
||||||
|
|
||||||
public Http3ControlStream(Http3TestBase testBase, Http3Connection connection)
|
public long Error { get; set; }
|
||||||
|
|
||||||
|
public Http3ControlStream(Http3TestBase testBase)
|
||||||
{
|
{
|
||||||
_testBase = testBase;
|
_testBase = testBase;
|
||||||
_connection = connection;
|
|
||||||
var inputPipeOptions = GetInputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool);
|
var inputPipeOptions = GetInputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool);
|
||||||
var outputPipeOptions = GetOutputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool);
|
var outputPipeOptions = GetOutputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool);
|
||||||
|
|
||||||
_pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions);
|
_pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions);
|
||||||
|
StreamContext = new TestStreamContext(canRead: false, canWrite: true, _pair, this);
|
||||||
ConnectionContext = new DefaultConnectionContext();
|
|
||||||
ConnectionContext.Transport = _pair.Transport;
|
|
||||||
ConnectionContext.Features.Set<IQuicStreamFeature>(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task WriteStreamIdAsync(int id)
|
public async Task WriteStreamIdAsync(int id)
|
||||||
|
|
@ -402,5 +428,100 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await FlushAsync(writableBuffer);
|
await FlushAsync(writableBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TestMultiplexedConnectionContext : MultiplexedConnectionContext
|
||||||
|
{
|
||||||
|
public readonly Channel<ConnectionContext> AcceptQueue = Channel.CreateUnbounded<ConnectionContext>(new UnboundedChannelOptions
|
||||||
|
{
|
||||||
|
SingleReader = true,
|
||||||
|
SingleWriter = true
|
||||||
|
});
|
||||||
|
|
||||||
|
private readonly Http3TestBase _testBase;
|
||||||
|
|
||||||
|
public TestMultiplexedConnectionContext(Http3TestBase testBase)
|
||||||
|
{
|
||||||
|
_testBase = testBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ConnectionId { get; set; }
|
||||||
|
|
||||||
|
public override IFeatureCollection Features { get; }
|
||||||
|
|
||||||
|
public override IDictionary<object, object> Items { get; set; }
|
||||||
|
|
||||||
|
public override void Abort()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Abort(ConnectionAbortedException abortReason)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async ValueTask<ConnectionContext> AcceptAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
while (await AcceptQueue.Reader.WaitToReadAsync())
|
||||||
|
{
|
||||||
|
while (AcceptQueue.Reader.TryRead(out var connection))
|
||||||
|
{
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ValueTask<ConnectionContext> ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var stream = new Http3ControlStream(_testBase);
|
||||||
|
// TODO put these somewhere to be read.
|
||||||
|
return new ValueTask<ConnectionContext>(stream.StreamContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestStreamContext : ConnectionContext, IStreamDirectionFeature, IStreamIdFeature
|
||||||
|
{
|
||||||
|
private DuplexPipePair _pair;
|
||||||
|
public TestStreamContext(bool canRead, bool canWrite, DuplexPipePair pair, IProtocolErrorCodeFeature feature)
|
||||||
|
{
|
||||||
|
_pair = pair;
|
||||||
|
Features = new FeatureCollection();
|
||||||
|
Features.Set<IStreamDirectionFeature>(this);
|
||||||
|
Features.Set<IStreamIdFeature>(this);
|
||||||
|
Features.Set(feature);
|
||||||
|
|
||||||
|
CanRead = canRead;
|
||||||
|
CanWrite = canWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ConnectionId { get; set; }
|
||||||
|
|
||||||
|
public long StreamId { get; }
|
||||||
|
|
||||||
|
public override IFeatureCollection Features { get; }
|
||||||
|
|
||||||
|
public override IDictionary<object, object> Items { get; set; }
|
||||||
|
|
||||||
|
public override IDuplexPipe Transport
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _pair.Transport;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanRead { get; }
|
||||||
|
|
||||||
|
public bool CanWrite { get; }
|
||||||
|
|
||||||
|
public override void Abort(ConnectionAbortedException abortReason)
|
||||||
|
{
|
||||||
|
_pair.Application.Output.Complete(abortReason);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||||
|
|
@ -27,4 +27,4 @@
|
||||||
<Reference Include="Newtonsoft.Json" />
|
<Reference Include="Newtonsoft.Json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<StartWorkingDirectory>$(MSBuildThisFileDirectory)..\..\</StartWorkingDirectory>
|
<StartWorkingDirectory>$(MSBuildThisFileDirectory)..\..\</StartWorkingDirectory>
|
||||||
<StartArguments>Core/src/Internal/Http/HttpHeaders.Generated.cs Core/src/Internal/Http/HttpProtocol.Generated.cs Core/src/Internal/Infrastructure/HttpUtilities.Generated.cs shared/TransportConnection.Generated.cs Core/src/Internal/Http2/Http2Connection.Generated.cs</StartArguments>
|
<StartArguments>Core/src/Internal/Http/HttpHeaders.Generated.cs Core/src/Internal/Http/HttpProtocol.Generated.cs Core/src/Internal/Infrastructure/HttpUtilities.Generated.cs Core/src/Internal/Http2/Http2Connection.Generated.cs shared/TransportMultiplexedConnection.Generated.cs shared/TransportConnection.Generated.cs</StartArguments>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -27,16 +27,21 @@ namespace CodeGenerator
|
||||||
}
|
}
|
||||||
else if (args.Length < 4)
|
else if (args.Length < 4)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine("Missing path to TransportConnection.Generated.cs");
|
Console.Error.WriteLine("Missing path to Http2Connection.Generated.cs");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
else if (args.Length < 5)
|
else if (args.Length < 5)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine("Missing path to Http2Connection.Generated.cs");
|
Console.Error.WriteLine("Missing path to TransportMultiplexedConnection.Generated.cs");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else if (args.Length < 6)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("Missing path to TransportConnection.Generated.cs");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Run(args[0], args[1], args[2], args[3], args[4]);
|
Run(args[0], args[1], args[2], args[3], args[4], args[5]);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -45,43 +50,37 @@ namespace CodeGenerator
|
||||||
string knownHeadersPath,
|
string knownHeadersPath,
|
||||||
string httpProtocolFeatureCollectionPath,
|
string httpProtocolFeatureCollectionPath,
|
||||||
string httpUtilitiesPath,
|
string httpUtilitiesPath,
|
||||||
string transportConnectionFeatureCollectionPath,
|
string http2ConnectionPath,
|
||||||
string http2ConnectionPath)
|
string transportMultiplexedConnectionFeatureCollectionPath,
|
||||||
|
string transportConnectionFeatureCollectionPath)
|
||||||
{
|
{
|
||||||
var knownHeadersContent = KnownHeaders.GeneratedFile();
|
var knownHeadersContent = KnownHeaders.GeneratedFile();
|
||||||
var httpProtocolFeatureCollectionContent = HttpProtocolFeatureCollection.GenerateFile();
|
var httpProtocolFeatureCollectionContent = HttpProtocolFeatureCollection.GenerateFile();
|
||||||
var httpUtilitiesContent = HttpUtilities.HttpUtilities.GeneratedFile();
|
var httpUtilitiesContent = HttpUtilities.HttpUtilities.GeneratedFile();
|
||||||
|
var transportMultiplexedConnectionFeatureCollectionContent = TransportMultiplexedConnectionFeatureCollection.GenerateFile();
|
||||||
var transportConnectionFeatureCollectionContent = TransportConnectionFeatureCollection.GenerateFile();
|
var transportConnectionFeatureCollectionContent = TransportConnectionFeatureCollection.GenerateFile();
|
||||||
var http2ConnectionContent = Http2Connection.GenerateFile();
|
var http2ConnectionContent = Http2Connection.GenerateFile();
|
||||||
|
|
||||||
var existingKnownHeaders = File.Exists(knownHeadersPath) ? File.ReadAllText(knownHeadersPath) : "";
|
UpdateFile(knownHeadersPath, knownHeadersContent);
|
||||||
if (!string.Equals(knownHeadersContent, existingKnownHeaders))
|
UpdateFile(httpProtocolFeatureCollectionPath, httpProtocolFeatureCollectionContent);
|
||||||
|
UpdateFile(httpUtilitiesPath, httpUtilitiesContent);
|
||||||
|
UpdateFile(http2ConnectionPath, http2ConnectionContent);
|
||||||
|
UpdateFile(transportMultiplexedConnectionFeatureCollectionPath, transportMultiplexedConnectionFeatureCollectionContent);
|
||||||
|
UpdateFile(transportConnectionFeatureCollectionPath, transportConnectionFeatureCollectionContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UpdateFile(string path, string content)
|
||||||
|
{
|
||||||
|
var existingContent = File.Exists(path) ? File.ReadAllText(path) : "";
|
||||||
|
if (!string.Equals(content, existingContent))
|
||||||
{
|
{
|
||||||
File.WriteAllText(knownHeadersPath, knownHeadersContent);
|
File.WriteAllText(path, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
var existingHttpProtocolFeatureCollection = File.Exists(httpProtocolFeatureCollectionPath) ? File.ReadAllText(httpProtocolFeatureCollectionPath) : "";
|
var existingHttp2Connection = File.Exists(path) ? File.ReadAllText(path) : "";
|
||||||
if (!string.Equals(httpProtocolFeatureCollectionContent, existingHttpProtocolFeatureCollection))
|
if (!string.Equals(content, existingHttp2Connection))
|
||||||
{
|
{
|
||||||
File.WriteAllText(httpProtocolFeatureCollectionPath, httpProtocolFeatureCollectionContent);
|
File.WriteAllText(path, content);
|
||||||
}
|
|
||||||
|
|
||||||
var existingHttpUtilities = File.Exists(httpUtilitiesPath) ? File.ReadAllText(httpUtilitiesPath) : "";
|
|
||||||
if (!string.Equals(httpUtilitiesContent, existingHttpUtilities))
|
|
||||||
{
|
|
||||||
File.WriteAllText(httpUtilitiesPath, httpUtilitiesContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
var existingTransportConnectionFeatureCollection = File.Exists(transportConnectionFeatureCollectionPath) ? File.ReadAllText(transportConnectionFeatureCollectionPath) : "";
|
|
||||||
if (!string.Equals(transportConnectionFeatureCollectionContent, existingTransportConnectionFeatureCollection))
|
|
||||||
{
|
|
||||||
File.WriteAllText(transportConnectionFeatureCollectionPath, transportConnectionFeatureCollectionContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
var existingHttp2Connection = File.Exists(http2ConnectionPath) ? File.ReadAllText(http2ConnectionPath) : "";
|
|
||||||
if (!string.Equals(http2ConnectionContent, existingHttp2Connection))
|
|
||||||
{
|
|
||||||
File.WriteAllText(http2ConnectionPath, http2ConnectionContent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace CodeGenerator
|
||||||
|
{
|
||||||
|
public class TransportMultiplexedConnectionFeatureCollection
|
||||||
|
{
|
||||||
|
public static string GenerateFile()
|
||||||
|
{
|
||||||
|
// NOTE: This list MUST always match the set of feature interfaces implemented by TransportConnectionBase.
|
||||||
|
// See also: shared/TransportConnectionBase.FeatureCollection.cs
|
||||||
|
var features = new[]
|
||||||
|
{
|
||||||
|
"IConnectionIdFeature",
|
||||||
|
"IConnectionTransportFeature",
|
||||||
|
"IConnectionItemsFeature",
|
||||||
|
"IMemoryPoolFeature",
|
||||||
|
"IConnectionLifetimeFeature"
|
||||||
|
};
|
||||||
|
|
||||||
|
var usings = $@"
|
||||||
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;";
|
||||||
|
|
||||||
|
return FeatureCollectionGenerator.GenerateFile(
|
||||||
|
namespaceName: "Microsoft.AspNetCore.Connections",
|
||||||
|
className: "TransportMultiplexedConnection",
|
||||||
|
allFeatures: features,
|
||||||
|
implementedFeatures: features,
|
||||||
|
extraUsings: usings,
|
||||||
|
fallbackFeatures: null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -340,7 +340,6 @@ namespace System.Net.Http.QPack
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO these are fairly hard coded for the first two bytes to be zero.
|
|
||||||
public bool BeginEncode(IEnumerable<KeyValuePair<string, string>> headers, Span<byte> buffer, out int length)
|
public bool BeginEncode(IEnumerable<KeyValuePair<string, string>> headers, Span<byte> buffer, out int length)
|
||||||
{
|
{
|
||||||
_enumerator = headers.GetEnumerator();
|
_enumerator = headers.GetEnumerator();
|
||||||
|
|
@ -351,7 +350,11 @@ namespace System.Net.Http.QPack
|
||||||
buffer[0] = 0;
|
buffer[0] = 0;
|
||||||
buffer[1] = 0;
|
buffer[1] = 0;
|
||||||
|
|
||||||
return Encode(buffer.Slice(2), out length);
|
bool doneEncode = Encode(buffer.Slice(2), out length);
|
||||||
|
|
||||||
|
// Add two for the first two bytes.
|
||||||
|
length += 2;
|
||||||
|
return doneEncode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool BeginEncode(int statusCode, IEnumerable<KeyValuePair<string, string>> headers, Span<byte> buffer, out int length)
|
public bool BeginEncode(int statusCode, IEnumerable<KeyValuePair<string, string>> headers, Span<byte> buffer, out int length)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Licensed to the .NET Foundation under one or more agreements.
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
|
@ -143,12 +143,6 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
|
||||||
|
|
||||||
OperatingSystem ver = Environment.OSVersion;
|
OperatingSystem ver = Environment.OSVersion;
|
||||||
|
|
||||||
if (ver.Platform == PlatformID.Win32NT && ver.Version < new Version(10, 0, 19041, 0))
|
|
||||||
{
|
|
||||||
IsQuicSupported = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: try to initialize TLS 1.3 in SslStream.
|
// TODO: try to initialize TLS 1.3 in SslStream.
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue