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, 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 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 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() { }
|
||||
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 virtual void Abort() { }
|
||||
public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { }
|
||||
public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
|
||||
public override void Abort() { }
|
||||
public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { }
|
||||
}
|
||||
public delegate System.Threading.Tasks.Task ConnectionDelegate(Microsoft.AspNetCore.Connections.ConnectionContext connection);
|
||||
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));
|
||||
}
|
||||
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]
|
||||
public enum TransferFormat
|
||||
{
|
||||
|
|
@ -139,14 +176,6 @@ namespace Microsoft.AspNetCore.Connections
|
|||
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
|
||||
{
|
||||
public partial interface IConnectionCompleteFeature
|
||||
|
|
@ -196,15 +225,18 @@ namespace Microsoft.AspNetCore.Connections.Features
|
|||
{
|
||||
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 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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,19 @@ namespace Microsoft.AspNetCore.Connections
|
|||
public AddressInUseException(string message) { }
|
||||
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 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 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() { }
|
||||
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 virtual void Abort() { }
|
||||
public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { }
|
||||
public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
|
||||
public override void Abort() { }
|
||||
public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { }
|
||||
}
|
||||
public delegate System.Threading.Tasks.Task ConnectionDelegate(Microsoft.AspNetCore.Connections.ConnectionContext connection);
|
||||
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));
|
||||
}
|
||||
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]
|
||||
public enum TransferFormat
|
||||
{
|
||||
|
|
@ -139,14 +176,6 @@ namespace Microsoft.AspNetCore.Connections
|
|||
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
|
||||
{
|
||||
public partial interface IConnectionCompleteFeature
|
||||
|
|
@ -196,15 +225,18 @@ namespace Microsoft.AspNetCore.Connections.Features
|
|||
{
|
||||
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 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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,19 @@ namespace Microsoft.AspNetCore.Connections
|
|||
public AddressInUseException(string message) { }
|
||||
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 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 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() { }
|
||||
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 virtual void Abort() { }
|
||||
public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { }
|
||||
public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
|
||||
public override void Abort() { }
|
||||
public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { }
|
||||
}
|
||||
public delegate System.Threading.Tasks.Task ConnectionDelegate(Microsoft.AspNetCore.Connections.ConnectionContext connection);
|
||||
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));
|
||||
}
|
||||
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]
|
||||
public enum TransferFormat
|
||||
{
|
||||
|
|
@ -139,14 +176,6 @@ namespace Microsoft.AspNetCore.Connections
|
|||
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
|
||||
{
|
||||
public partial interface IConnectionCompleteFeature
|
||||
|
|
@ -196,15 +225,18 @@ namespace Microsoft.AspNetCore.Connections.Features
|
|||
{
|
||||
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 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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace Microsoft.AspNetCore.Connections
|
||||
{
|
||||
/// <summary>
|
||||
/// Encapsulates all information about an individual connection.
|
||||
/// </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>
|
||||
/// Gets or sets the <see cref="IDuplexPipe"/> that can be used to read or write data on this connection.
|
||||
/// </summary>
|
||||
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>
|
||||
/// Aborts the underlying connection.
|
||||
/// </summary>
|
||||
/// <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
|
||||
// with implementations of ConnectionContext that predate the addition of
|
||||
|
|
@ -67,15 +32,6 @@ namespace Microsoft.AspNetCore.Connections
|
|||
/// <summary>
|
||||
/// Aborts the underlying connection.
|
||||
/// </summary>
|
||||
public virtual 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;
|
||||
}
|
||||
public override void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via ConnectionContext.Abort()."));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +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.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
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
|
||||
{
|
||||
public interface IQuicStreamFeature
|
||||
public interface IStreamDirectionFeature
|
||||
{
|
||||
bool CanRead { 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.
|
||||
// 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
|
||||
{
|
||||
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 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.Server.Kestrel.Core.KestrelServerOptions Options { get { throw null; } }
|
||||
public void Dispose() { }
|
||||
|
|
@ -149,7 +150,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
public void ListenUnixSocket(string socketPath) { }
|
||||
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() { }
|
||||
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 string SocketPath { get { 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 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">
|
||||
<value>A new stream was refused because this connection has reached its stream limit.</value>
|
||||
</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">
|
||||
<value>A value greater than zero is required.</value>
|
||||
</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
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
if (_requestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount)
|
||||
|
|
@ -1196,7 +1196,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
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";
|
||||
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.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Connections.Abstractions.Features;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
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.Infrastructure;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
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 Http3ControlStream ControlStream { get; set; }
|
||||
public Http3ControlStream EncoderStream { 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 volatile bool _haveSentGoAway;
|
||||
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);
|
||||
_systemClock = context.ServiceContext.SystemClock;
|
||||
_timeoutControl = new TimeoutControl(this);
|
||||
_context.TimeoutControl ??= _timeoutControl;
|
||||
}
|
||||
|
||||
internal long HighestStreamId
|
||||
|
|
@ -53,10 +64,133 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
}
|
||||
}
|
||||
|
||||
public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application)
|
||||
{
|
||||
var streamListenerFeature = Context.ConnectionFeatures.Get<IQuicStreamListenerFeature>();
|
||||
private IKestrelTrace Log => _context.ServiceContext.Log;
|
||||
|
||||
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.
|
||||
var controlTask = CreateControlStream(application);
|
||||
var encoderTask = CreateEncoderStream(application);
|
||||
|
|
@ -66,29 +200,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
{
|
||||
while (true)
|
||||
{
|
||||
var connectionContext = await streamListenerFeature.AcceptAsync();
|
||||
if (connectionContext == null || _haveSentGoAway)
|
||||
var streamContext = await _multiplexedContext.AcceptAsync();
|
||||
if (streamContext == null || _haveSentGoAway)
|
||||
{
|
||||
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,
|
||||
ConnectionContext = connectionContext,
|
||||
Protocols = Context.Protocols,
|
||||
ServiceContext = Context.ServiceContext,
|
||||
ConnectionFeatures = connectionContext.Features,
|
||||
MemoryPool = Context.MemoryPool,
|
||||
Transport = connectionContext.Transport,
|
||||
TimeoutControl = Context.TimeoutControl,
|
||||
LocalEndPoint = connectionContext.LocalEndPoint as IPEndPoint,
|
||||
RemoteEndPoint = connectionContext.RemoteEndPoint as IPEndPoint
|
||||
ConnectionId = streamContext.ConnectionId,
|
||||
StreamContext = streamContext,
|
||||
// TODO connection context is null here. Should we set it to anything?
|
||||
ServiceContext = _context.ServiceContext,
|
||||
ConnectionFeatures = streamContext.Features,
|
||||
MemoryPool = _context.MemoryPool,
|
||||
Transport = streamContext.Transport,
|
||||
TimeoutControl = _context.TimeoutControl,
|
||||
LocalEndPoint = streamContext.LocalEndPoint as IPEndPoint,
|
||||
RemoteEndPoint = streamContext.RemoteEndPoint as IPEndPoint
|
||||
};
|
||||
|
||||
var streamFeature = httpConnectionContext.ConnectionFeatures.Get<IQuicStreamFeature>();
|
||||
|
||||
if (!streamFeature.CanWrite)
|
||||
if (!quicStreamFeature.CanWrite)
|
||||
{
|
||||
// Unidirectional stream
|
||||
var stream = new Http3ControlStream<TContext>(application, this, httpConnectionContext);
|
||||
|
|
@ -97,13 +234,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
else
|
||||
{
|
||||
// Keep track of highest stream id seen for GOAWAY
|
||||
var streamId = streamFeature.StreamId;
|
||||
|
||||
var streamId = streamIdFeature.StreamId;
|
||||
HighestStreamId = streamId;
|
||||
|
||||
var http3Stream = new Http3Stream<TContext>(application, this, httpConnectionContext);
|
||||
var stream = http3Stream;
|
||||
_streams[streamId] = http3Stream;
|
||||
lock (_streams)
|
||||
{
|
||||
_streams[streamId] = http3Stream;
|
||||
}
|
||||
ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false);
|
||||
}
|
||||
}
|
||||
|
|
@ -111,14 +250,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
finally
|
||||
{
|
||||
// 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."));
|
||||
EncoderStream.Abort(new ConnectionAbortedException("Connection is shutting down."));
|
||||
DecoderStream.Abort(new ConnectionAbortedException("Connection is shutting down."));
|
||||
ControlStream?.Abort(new ConnectionAbortedException("Connection is shutting down."));
|
||||
EncoderStream?.Abort(new ConnectionAbortedException("Connection is shutting down."));
|
||||
DecoderStream?.Abort(new ConnectionAbortedException("Connection is shutting down."));
|
||||
|
||||
await controlTask;
|
||||
await encoderTask;
|
||||
|
|
@ -150,28 +292,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
|
||||
private async ValueTask<Http3ControlStream> CreateNewUnidirectionalStreamAsync<TContext>(IHttpApplication<TContext> application)
|
||||
{
|
||||
var connectionContext = await Context.ConnectionFeatures.Get<IQuicCreateStreamFeature>().StartUnidirectionalStreamAsync();
|
||||
var httpConnectionContext = new HttpConnectionContext
|
||||
var features = new FeatureCollection();
|
||||
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.
|
||||
ConnectionContext = connectionContext,
|
||||
Protocols = Context.Protocols,
|
||||
ServiceContext = Context.ServiceContext,
|
||||
ConnectionFeatures = connectionContext.Features,
|
||||
MemoryPool = Context.MemoryPool,
|
||||
Transport = connectionContext.Transport,
|
||||
TimeoutControl = Context.TimeoutControl,
|
||||
LocalEndPoint = connectionContext.LocalEndPoint as IPEndPoint,
|
||||
RemoteEndPoint = connectionContext.RemoteEndPoint as IPEndPoint
|
||||
StreamContext = streamContext,
|
||||
Protocols = HttpProtocols.Http3,
|
||||
ServiceContext = _context.ServiceContext,
|
||||
ConnectionFeatures = streamContext.Features,
|
||||
MemoryPool = _context.MemoryPool,
|
||||
Transport = streamContext.Transport,
|
||||
TimeoutControl = _context.TimeoutControl,
|
||||
LocalEndPoint = streamContext.LocalEndPoint as IPEndPoint,
|
||||
RemoteEndPoint = streamContext.RemoteEndPoint as IPEndPoint
|
||||
};
|
||||
|
||||
return new Http3ControlStream<TContext>(application, this, httpConnectionContext);
|
||||
}
|
||||
|
||||
public void StopProcessingNextRequest()
|
||||
{
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
@ -202,10 +342,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
_haveSentGoAway = true;
|
||||
|
||||
// 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?
|
||||
}
|
||||
|
||||
|
|
@ -223,5 +367,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
// TODO make sure this works
|
||||
//_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.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
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
|
||||
{
|
||||
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 Http3OutputProducer _http3Output;
|
||||
private int _isClosed;
|
||||
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();
|
||||
protected RequestHeaderParsingState _requestHeaderParsingState;
|
||||
private PseudoHeaderFields _parsedPseudoHeaderFields;
|
||||
private bool _isMethodConnect;
|
||||
|
||||
private readonly Http3Connection _http3Connection;
|
||||
private bool _receivedHeaders;
|
||||
private TaskCompletionSource<object> _appCompleted;
|
||||
|
||||
public Pipe RequestBodyPipe { get; }
|
||||
|
||||
public Http3Stream(Http3Connection http3Connection, HttpConnectionContext context)
|
||||
public Http3Stream(Http3Connection http3Connection, Http3StreamContext context)
|
||||
{
|
||||
Initialize(context);
|
||||
|
||||
InputRemaining = null;
|
||||
|
||||
// First, determine how we know if an Http3stream is unidirectional or bidirectional
|
||||
var httpLimits = context.ServiceContext.ServerOptions.Limits;
|
||||
var http3Limits = httpLimits.Http3;
|
||||
_http3Connection = http3Connection;
|
||||
_context = context;
|
||||
|
||||
_errorCodeFeature = _context.ConnectionFeatures.Get<IProtocolErrorCodeFeature>();
|
||||
_streamIdFeature = _context.ConnectionFeatures.Get<IStreamIdFeature>();
|
||||
|
||||
_frameWriter = new Http3FrameWriter(
|
||||
context.Transport.Output,
|
||||
context.ConnectionContext,
|
||||
context.StreamContext,
|
||||
context.TimeoutControl,
|
||||
httpLimits.MinResponseDataRate,
|
||||
context.ConnectionId,
|
||||
|
|
@ -63,6 +87,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
QPackDecoder = new QPackDecoder(_context.ServiceContext.ServerOptions.Limits.Http3.MaxRequestHeaderFieldSize);
|
||||
}
|
||||
|
||||
public long? InputRemaining { get; internal set; }
|
||||
|
||||
public QPackDecoder QPackDecoder { get; }
|
||||
|
||||
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)
|
||||
{
|
||||
// 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)
|
||||
|
|
@ -97,6 +126,165 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
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()
|
||||
{
|
||||
Log.RequestBodyMinimumDataRateNotSatisfied(ConnectionId, null, Limits.MinRequestBodyDataRate.BytesPerSecond);
|
||||
|
|
@ -112,7 +300,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
public void OnInputOrOutputCompleted()
|
||||
{
|
||||
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()
|
||||
|
|
@ -152,6 +347,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
OnEndStreamReceived();
|
||||
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)
|
||||
{
|
||||
error = ex;
|
||||
Log.LogWarning(0, ex, "Stream threw an exception.");
|
||||
Log.LogWarning(0, ex, "Stream threw an unexpected exception.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
var streamError = error as ConnectionAbortedException
|
||||
?? 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
|
||||
{
|
||||
_frameWriter.Complete();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_frameWriter.Abort(streamError);
|
||||
Abort(streamError, Http3ErrorCode.ProtocolError);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Input.Complete();
|
||||
_context.Transport.Input.CancelPendingRead();
|
||||
await RequestBodyPipe.Writer.CompleteAsync();
|
||||
_http3Connection.RemoveStream(_streamIdFeature.StreamId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
@ -231,13 +455,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
}
|
||||
|
||||
_receivedHeaders = true;
|
||||
InputRemaining = HttpRequestHeaders.ContentLength;
|
||||
|
||||
_appCompleted = new TaskCompletionSource<object>();
|
||||
|
||||
ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false);
|
||||
|
||||
Task.Run(() => base.ProcessRequestsAsync(application));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
RequestBodyPipe.Writer.Write(segment.Span);
|
||||
|
|
@ -270,6 +509,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
|
||||
protected override void ApplicationAbort()
|
||||
{
|
||||
var abortReason = new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication);
|
||||
Abort(abortReason, Http3ErrorCode.InternalError);
|
||||
}
|
||||
|
||||
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]))
|
||||
{
|
||||
Abort(new ConnectionAbortedException(CoreStrings.Http2ErrorConnectMustNotSendSchemeOrPath), Http3ErrorCode.ProtocolError);
|
||||
Abort(new ConnectionAbortedException(CoreStrings.Http3ErrorConnectMustNotSendSchemeOrPath), Http3ErrorCode.ProtocolError);
|
||||
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.
|
||||
if (!string.Equals(RequestHeaders[HeaderNames.Scheme], Scheme, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Abort(new ConnectionAbortedException(
|
||||
CoreStrings.FormatHttp2StreamErrorSchemeMismatch(RequestHeaders[HeaderNames.Scheme], Scheme)), Http3ErrorCode.ProtocolError);
|
||||
var str = CoreStrings.FormatHttp3StreamErrorSchemeMismatch(RequestHeaders[HeaderNames.Scheme], Scheme);
|
||||
Abort(new ConnectionAbortedException(str), Http3ErrorCode.ProtocolError);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -376,7 +617,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
|
||||
if (Method == Http.HttpMethod.None)
|
||||
{
|
||||
Abort(new ConnectionAbortedException(CoreStrings.FormatHttp2ErrorMethodInvalid(_methodText)), Http3ErrorCode.ProtocolError);
|
||||
Abort(new ConnectionAbortedException(CoreStrings.FormatHttp3ErrorMethodInvalid(_methodText)), Http3ErrorCode.ProtocolError);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -384,7 +625,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
{
|
||||
if (HttpCharacters.IndexOfInvalidTokenChar(_methodText) >= 0)
|
||||
{
|
||||
Abort(new ConnectionAbortedException(CoreStrings.FormatHttp2ErrorMethodInvalid(_methodText)), Http3ErrorCode.ProtocolError);
|
||||
Abort(new ConnectionAbortedException(CoreStrings.FormatHttp3ErrorMethodInvalid(_methodText)), Http3ErrorCode.ProtocolError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -436,7 +677,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
// Must start with a leading slash
|
||||
if (pathSegment.Length == 0 || pathSegment[0] != '/')
|
||||
{
|
||||
Abort(new ConnectionAbortedException(CoreStrings.FormatHttp2StreamErrorPathInvalid(RawTarget)), Http3ErrorCode.ProtocolError);
|
||||
Abort(new ConnectionAbortedException(CoreStrings.FormatHttp3StreamErrorPathInvalid(RawTarget)), Http3ErrorCode.ProtocolError);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -466,8 +707,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// TODO change HTTP/2 specific messages to include HTTP/3
|
||||
Abort(new ConnectionAbortedException(CoreStrings.FormatHttp2StreamErrorPathInvalid(RawTarget)), Http3ErrorCode.ProtocolError);
|
||||
Abort(new ConnectionAbortedException(CoreStrings.FormatHttp3StreamErrorPathInvalid(RawTarget)), Http3ErrorCode.ProtocolError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -491,6 +731,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
/// </summary>
|
||||
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
|
||||
{
|
||||
public const int None = 0;
|
||||
|
|
|
|||
|
|
@ -10,14 +10,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public override void Execute()
|
||||
{
|
||||
_ = ProcessRequestAsync(_application);
|
||||
if (_requestHeaderParsingState == Http3Stream.RequestHeaderParsingState.Ready)
|
||||
{
|
||||
_ = ProcessRequestAsync(_application);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = base.ProcessRequestsAsync(_application);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
_protocolSelectionState = ProtocolSelectionState.Selected;
|
||||
break;
|
||||
case HttpProtocols.Http3:
|
||||
requestProcessor = new Http3Connection(_context);
|
||||
_protocolSelectionState = ProtocolSelectionState.Selected;
|
||||
break;
|
||||
case HttpProtocols.None:
|
||||
// An error was already logged in SelectProtocol(), but we should close the connection.
|
||||
break;
|
||||
|
|
@ -204,11 +200,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
|
||||
private HttpProtocols SelectProtocol()
|
||||
{
|
||||
if (_context.Protocols == HttpProtocols.Http3)
|
||||
{
|
||||
return HttpProtocols.Http3;
|
||||
}
|
||||
|
||||
var hasTls = _context.ConnectionFeatures.Get<ITlsConnectionFeature>() != null;
|
||||
var applicationProtocol = _context.ConnectionFeatures.Get<ITlsApplicationProtocolFeature>()?.ApplicationProtocol
|
||||
?? 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.
|
||||
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
|
||||
Walk(connection =>
|
||||
{
|
||||
connection.TransportConnection.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedDuringServerShutdown));
|
||||
connection.GetTransport().Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedDuringServerShutdown));
|
||||
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.
|
||||
|
||||
using System;
|
||||
|
|
@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
public ConnectionReference(KestrelConnection connection)
|
||||
{
|
||||
_weakReference = new WeakReference<KestrelConnection>(connection);
|
||||
ConnectionId = connection.TransportConnection.ConnectionId;
|
||||
ConnectionId = connection.GetTransport().ConnectionId;
|
||||
}
|
||||
|
||||
public string ConnectionId { get; }
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
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 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 TaskCompletionSource<object> _completionTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
private readonly long _id;
|
||||
private readonly ServiceContext _serviceContext;
|
||||
private readonly ConnectionDelegate _connectionDelegate;
|
||||
protected readonly long _id;
|
||||
protected readonly ServiceContext _serviceContext;
|
||||
|
||||
public KestrelConnection(long id,
|
||||
ServiceContext serviceContext,
|
||||
ConnectionDelegate connectionDelegate,
|
||||
ConnectionContext connectionContext,
|
||||
IKestrelTrace logger)
|
||||
{
|
||||
_id = id;
|
||||
_serviceContext = serviceContext;
|
||||
_connectionDelegate = connectionDelegate;
|
||||
Logger = logger;
|
||||
TransportConnection = connectionContext;
|
||||
|
||||
connectionContext.Features.Set<IConnectionHeartbeatFeature>(this);
|
||||
connectionContext.Features.Set<IConnectionCompleteFeature>(this);
|
||||
connectionContext.Features.Set<IConnectionLifetimeNotificationFeature>(this);
|
||||
ConnectionClosedRequested = _connectionClosingCts.Token;
|
||||
}
|
||||
|
||||
private IKestrelTrace Logger { get; }
|
||||
protected IKestrelTrace Logger { get; }
|
||||
|
||||
public ConnectionContext TransportConnection { get; set; }
|
||||
public CancellationToken ConnectionClosedRequested { get; set; }
|
||||
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)
|
||||
{
|
||||
lock (_heartbeatLock)
|
||||
|
|
@ -175,49 +168,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
_connectionClosingCts.Dispose();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private IDisposable BeginConnectionScope(ConnectionContext connectionContext)
|
||||
protected IDisposable BeginConnectionScope(BaseConnectionContext connectionContext)
|
||||
{
|
||||
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.
|
||||
|
||||
[NonEvent]
|
||||
public void ConnectionStart(ConnectionContext connection)
|
||||
public void ConnectionStart(BaseConnectionContext connection)
|
||||
{
|
||||
// avoid allocating strings unless this event source is enabled
|
||||
if (IsEnabled())
|
||||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
}
|
||||
|
||||
[NonEvent]
|
||||
public void ConnectionStop(ConnectionContext connection)
|
||||
public void ConnectionStop(BaseConnectionContext connection)
|
||||
{
|
||||
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.IO.Pipelines;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
|
|
@ -22,20 +23,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
public class KestrelServer : IServer
|
||||
{
|
||||
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 List<IConnectionListenerFactory> _transportFactories;
|
||||
private readonly List<IMultiplexedConnectionListenerFactory> _multiplexedTransportFactories;
|
||||
|
||||
private bool _hasStarted;
|
||||
private int _stopping;
|
||||
private readonly TaskCompletionSource<object> _stoppedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
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
|
||||
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)
|
||||
{
|
||||
|
|
@ -43,8 +56,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
}
|
||||
|
||||
_transportFactories = transportFactories.ToList();
|
||||
_multiplexedTransportFactories = multiplexedFactories?.ToList();
|
||||
|
||||
if (_transportFactories.Count == 0)
|
||||
if (_transportFactories.Count == 0 && (_multiplexedTransportFactories == null || _multiplexedTransportFactories.Count == 0))
|
||||
{
|
||||
throw new InvalidOperationException(CoreStrings.TransportNotFound);
|
||||
}
|
||||
|
|
@ -78,6 +92,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
|
||||
var heartbeatManager = new HeartbeatManager(connectionManager);
|
||||
var dateHeaderValueManager = new DateHeaderValueManager();
|
||||
|
||||
var heartbeat = new Heartbeat(
|
||||
new IHeartbeatHandler[] { dateHeaderValueManager, heartbeatManager },
|
||||
new SystemClock(),
|
||||
|
|
@ -129,54 +144,52 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
|
||||
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
|
||||
options.UseHttpServer(ServiceContext, application, options.Protocols);
|
||||
|
||||
var connectionDelegate = options.Build();
|
||||
|
||||
// Add the connection limit middleware
|
||||
if (Options.Limits.MaxConcurrentConnections.HasValue)
|
||||
if ((options.Protocols & HttpProtocols.Http1) == HttpProtocols.Http1
|
||||
|| (options.Protocols & HttpProtocols.Http2) == HttpProtocols.Http2
|
||||
|| 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?
|
||||
{
|
||||
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);
|
||||
|
||||
IConnectionListenerFactory factory = null;
|
||||
if (options.Protocols >= HttpProtocols.Http3)
|
||||
{
|
||||
foreach (var transportFactory in _transportFactories)
|
||||
// Add the connection limit middleware
|
||||
if (Options.Limits.MaxConcurrentConnections.HasValue)
|
||||
{
|
||||
if (transportFactory is IMultiplexedConnectionListenerFactory)
|
||||
{
|
||||
// Don't break early. Always use the last registered factory.
|
||||
factory = transportFactory;
|
||||
}
|
||||
connectionDelegate = new ConnectionLimitMiddleware(connectionDelegate, Options.Limits.MaxConcurrentConnections.Value, Trace).OnConnectionAsync;
|
||||
}
|
||||
|
||||
if (factory == null)
|
||||
{
|
||||
throw new InvalidOperationException(CoreStrings.QuicTransportNotFound);
|
||||
}
|
||||
var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate);
|
||||
var factory = _transportFactories.Last();
|
||||
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);
|
||||
|
|
@ -200,13 +213,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
|
||||
try
|
||||
{
|
||||
var tasks = new Task[_transports.Count];
|
||||
for (int i = 0; i < _transports.Count; i++)
|
||||
var connectionTransportCount = _transports.Count;
|
||||
var totalTransportCount = _transports.Count + _multiplexedTransports.Count;
|
||||
var tasks = new Task[totalTransportCount];
|
||||
|
||||
for (int i = 0; i < connectionTransportCount; i++)
|
||||
{
|
||||
(IConnectionListener listener, Task acceptLoop) = _transports[i];
|
||||
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);
|
||||
|
||||
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];
|
||||
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);
|
||||
|
||||
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
|
||||
/// socket that Kestrel should bind to or open.
|
||||
/// </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<MultiplexedConnectionDelegate, MultiplexedConnectionDelegate>> _multiplexedMiddleware = new List<Func<MultiplexedConnectionDelegate, MultiplexedConnectionDelegate>>();
|
||||
|
||||
internal ListenOptions(IPEndPoint endPoint)
|
||||
{
|
||||
|
|
@ -123,6 +124,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
return this;
|
||||
}
|
||||
|
||||
IMultiplexedConnectionBuilder IMultiplexedConnectionBuilder.Use(Func<MultiplexedConnectionDelegate, MultiplexedConnectionDelegate> middleware)
|
||||
{
|
||||
_multiplexedMiddleware.Add(middleware);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConnectionDelegate Build()
|
||||
{
|
||||
ConnectionDelegate app = context =>
|
||||
|
|
@ -139,6 +146,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
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)
|
||||
{
|
||||
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.
|
||||
// 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.Connections;
|
||||
|
||||
|
|
@ -18,5 +16,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
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;
|
||||
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);
|
||||
|
||||
var task = kestrelConnection.ExecuteAsync();
|
||||
|
|
@ -79,9 +79,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
var connection = new Mock<DefaultConnectionContext> { CallBase = true }.Object;
|
||||
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);
|
||||
var completeFeature = kestrelConnection.TransportConnection.Features.Get<IConnectionCompleteFeature>();
|
||||
var completeFeature = kestrelConnection.GetTransport().Features.Get<IConnectionCompleteFeature>();
|
||||
|
||||
Assert.NotNull(completeFeature);
|
||||
object stateObject = new object();
|
||||
|
|
@ -100,9 +100,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var logger = ((TestKestrelTrace)serviceContext.Log).Logger;
|
||||
var connection = new Mock<DefaultConnectionContext> { CallBase = true }.Object;
|
||||
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);
|
||||
var completeFeature = kestrelConnection.TransportConnection.Features.Get<IConnectionCompleteFeature>();
|
||||
var completeFeature = kestrelConnection.GetTransport().Features.Get<IConnectionCompleteFeature>();
|
||||
|
||||
Assert.NotNull(completeFeature);
|
||||
object stateObject = new object();
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var serviceContext = new TestServiceContext();
|
||||
var mock = new Mock<DefaultConnectionContext>() { CallBase = true };
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -20,12 +20,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
var httpProtocolGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "HttpProtocol.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 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 testHttpProtocolGeneratedPath = Path.GetTempFileName();
|
||||
var testHttpUtilitiesGeneratedPath = Path.GetTempFileName();
|
||||
var testHttp2ConnectionGeneratedPath = Path.GetTempFileName();
|
||||
var testTransportMultiplexedConnectionGeneratedPath = Path.GetTempFileName();
|
||||
var testTransportConnectionGeneratedPath = Path.GetTempFileName();
|
||||
|
||||
try
|
||||
|
|
@ -34,20 +36,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
var currentHttpProtocolGenerated = File.ReadAllText(httpProtocolGeneratedPath);
|
||||
var currentHttpUtilitiesGenerated = File.ReadAllText(httpUtilitiesGeneratedPath);
|
||||
var currentHttp2ConnectionGenerated = File.ReadAllText(http2ConnectionGeneratedPath);
|
||||
var currentTransportConnectionBaseGenerated = File.ReadAllText(transportMultiplexedConnectionGeneratedPath);
|
||||
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 testHttpProtocolGenerated = File.ReadAllText(testHttpProtocolGeneratedPath);
|
||||
var testHttpUtilitiesGenerated = File.ReadAllText(testHttpUtilitiesGeneratedPath);
|
||||
var testHttp2ConnectionGenerated = File.ReadAllText(testHttp2ConnectionGeneratedPath);
|
||||
var testTransportMultiplxedConnectionGenerated = File.ReadAllText(testTransportMultiplexedConnectionGeneratedPath);
|
||||
var testTransportConnectionGenerated = File.ReadAllText(testTransportConnectionGeneratedPath);
|
||||
|
||||
Assert.Equal(currentHttpHeadersGenerated, testHttpHeadersGenerated, ignoreLineEndingDifferences: true);
|
||||
Assert.Equal(currentHttpProtocolGenerated, testHttpProtocolGenerated, ignoreLineEndingDifferences: true);
|
||||
Assert.Equal(currentHttpUtilitiesGenerated, testHttpUtilitiesGenerated, ignoreLineEndingDifferences: true);
|
||||
Assert.Equal(currentHttp2ConnectionGenerated, testHttp2ConnectionGenerated, ignoreLineEndingDifferences: true);
|
||||
Assert.Equal(currentTransportConnectionBaseGenerated, testTransportMultiplxedConnectionGenerated, ignoreLineEndingDifferences: true);
|
||||
Assert.Equal(currentTransportConnectionGenerated, testTransportConnectionGenerated, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
finally
|
||||
|
|
@ -56,6 +66,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
File.Delete(testHttpProtocolGeneratedPath);
|
||||
File.Delete(testHttpUtilitiesGeneratedPath);
|
||||
File.Delete(testHttp2ConnectionGeneratedPath);
|
||||
File.Delete(testTransportMultiplexedConnectionGeneratedPath);
|
||||
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\Http2\Http2Connection.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 Condition="'$(TargetFrameworkIdentifier)' != '.NETFramework'">
|
||||
|
|
|
|||
|
|
@ -15,5 +15,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
|||
void StreamPause(string streamId);
|
||||
void StreamResume(string streamId);
|
||||
void StreamShutdownWrite(string streamId, Exception ex);
|
||||
void StreamAbort(string streamId, Exception ex);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@
|
|||
|
||||
using System;
|
||||
using System.Net.Quic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Connections.Abstractions.Features;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
||||
{
|
||||
internal class QuicConnectionContext : TransportConnection, IQuicStreamListenerFeature, IQuicCreateStreamFeature
|
||||
internal class QuicConnectionContext : TransportMultiplexedConnection, IProtocolErrorCodeFeature
|
||||
{
|
||||
private QuicConnection _connection;
|
||||
private readonly QuicTransportContext _context;
|
||||
|
|
@ -20,14 +20,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
|||
|
||||
private ValueTask _closeTask;
|
||||
|
||||
public long Error { get; set; }
|
||||
|
||||
public QuicConnectionContext(QuicConnection connection, QuicTransportContext context)
|
||||
{
|
||||
_log = context.Log;
|
||||
_context = context;
|
||||
_connection = connection;
|
||||
Features.Set<ITlsConnectionFeature>(new FakeTlsConnectionFeature());
|
||||
Features.Set<IQuicStreamListenerFeature>(this);
|
||||
Features.Set<IQuicCreateStreamFeature>(this);
|
||||
Features.Set<IProtocolErrorCodeFeature>(this);
|
||||
|
||||
_log.NewConnection(ConnectionId);
|
||||
}
|
||||
|
|
@ -66,27 +67,51 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
|||
_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)
|
||||
{
|
||||
_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
|
||||
{
|
||||
// Because the stream is wrapped with a quic connection provider,
|
||||
// we need to check a property to check if this is null
|
||||
// Will be removed once the provider abstraction is removed.
|
||||
_ = stream.CanRead;
|
||||
var stream = await _connection.AcceptStreamAsync(cancellationToken);
|
||||
return new QuicStreamContext(stream, this, _context);
|
||||
}
|
||||
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.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Listens for new Quic Connections.
|
||||
/// </summary>
|
||||
internal class QuicConnectionListener : IConnectionListener, IAsyncDisposable
|
||||
internal class QuicConnectionListener : IMultiplexedConnectionListener, IAsyncDisposable
|
||||
{
|
||||
private readonly IQuicTrace _log;
|
||||
private bool _disposed;
|
||||
|
|
@ -36,22 +38,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
|||
|
||||
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
|
||||
{
|
||||
// Because the stream is wrapped with a quic connection provider,
|
||||
// we need to check a property to check if this is null
|
||||
// Will be removed once the provider abstraction is removed.
|
||||
_ = quicConnection.LocalEndPoint;
|
||||
var quicConnection = await _listener.AcceptConnectionAsync(cancellationToken);
|
||||
return new QuicConnectionContext(quicConnection, _context);
|
||||
}
|
||||
catch (Exception)
|
||||
catch (QuicOperationAbortedException ex)
|
||||
{
|
||||
return null;
|
||||
_log.LogDebug($"Listener has aborted with exception: {ex.Message}");
|
||||
}
|
||||
|
||||
return new QuicConnectionContext(quicConnection, _context);
|
||||
return null;
|
||||
}
|
||||
|
||||
public async ValueTask UnbindAsync(CancellationToken cancellationToken = default)
|
||||
|
|
@ -68,6 +66,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
|||
|
||||
_disposed = true;
|
||||
|
||||
_listener.Close();
|
||||
_listener.Dispose();
|
||||
|
||||
return new ValueTask();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
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 QuicStream _stream;
|
||||
|
|
@ -46,7 +46,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
|||
|
||||
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).
|
||||
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()
|
||||
{
|
||||
try
|
||||
|
|
@ -281,10 +285,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
|||
public override void Abort(ConnectionAbortedException abortReason)
|
||||
{
|
||||
// Don't call _stream.Shutdown and _stream.Abort at the same time.
|
||||
_log.StreamAbort(ConnectionId, abortReason);
|
||||
|
||||
lock (_shutdownLock)
|
||||
{
|
||||
_stream.AbortRead(_context.Options.AbortErrorCode);
|
||||
_stream.AbortWrite(_context.Options.AbortErrorCode);
|
||||
_stream.AbortRead(Error);
|
||||
_stream.AbortWrite(Error);
|
||||
}
|
||||
|
||||
// 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 =
|
||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, nameof(StreamResume)), @"Stream id ""{ConnectionId}"" resumed.");
|
||||
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;
|
||||
|
||||
|
|
@ -70,5 +72,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
|
|||
{
|
||||
_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>
|
||||
<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.Generated.cs" Link="Internal\TransportConnection.Generated.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" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,13 +9,14 @@ using System.Net.Security;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic
|
||||
{
|
||||
public class QuicConnectionFactory : IConnectionFactory
|
||||
public class QuicConnectionFactory : IMultiplexedConnectionFactory
|
||||
{
|
||||
private QuicTransportContext _transportContext;
|
||||
|
||||
|
|
@ -32,7 +33,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic
|
|||
_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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ using System.Net;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
|
@ -35,10 +35,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic
|
|||
_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);
|
||||
return new ValueTask<IConnectionListener>(transport);
|
||||
return new ValueTask<IMultiplexedConnectionListener>(transport);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,11 +24,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic
|
|||
/// </summary>
|
||||
public string Alpn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The registration name to use in Quic.
|
||||
/// </summary>
|
||||
public string RegistrationName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The certificate that MsQuic will use.
|
||||
/// </summary>
|
||||
|
|
@ -49,11 +44,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic
|
|||
/// </summary>
|
||||
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;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Hosting
|
|||
{
|
||||
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.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
|
|
@ -15,47 +14,39 @@ namespace Http3SampleApp
|
|||
{
|
||||
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()
|
||||
.ConfigureLogging((_, factory) =>
|
||||
{
|
||||
factory.SetMinimumLevel(LogLevel.Trace);
|
||||
factory.AddConsole();
|
||||
})
|
||||
.ConfigureWebHost(webHost =>
|
||||
{
|
||||
webHost.UseKestrel()
|
||||
// Things like APLN and cert should be able to be passed from corefx into bedrock
|
||||
.UseQuic(options =>
|
||||
{
|
||||
options.Certificate = cert;
|
||||
options.RegistrationName = "Quic";
|
||||
options.Alpn = "h3-25";
|
||||
options.IdleTimeout = TimeSpan.FromHours(1);
|
||||
})
|
||||
.ConfigureKestrel((context, options) =>
|
||||
{
|
||||
var basePort = 443;
|
||||
options.EnableAltSvc = true;
|
||||
options.Listen(IPAddress.Any, basePort, listenOptions =>
|
||||
{
|
||||
listenOptions.UseHttps(httpsOptions =>
|
||||
{
|
||||
httpsOptions.ServerCertificate = cert;
|
||||
});
|
||||
});
|
||||
options.Listen(IPAddress.Any, basePort, listenOptions =>
|
||||
{
|
||||
.ConfigureLogging((_, factory) =>
|
||||
{
|
||||
factory.SetMinimumLevel(LogLevel.Trace);
|
||||
factory.AddConsole();
|
||||
})
|
||||
.ConfigureWebHost(webHost =>
|
||||
{
|
||||
webHost.UseKestrel()
|
||||
.UseQuic(options =>
|
||||
{
|
||||
options.Certificate = cert; // Shouldn't need this either here.
|
||||
options.Alpn = "h3-25"; // Shouldn't need to populate this as well.
|
||||
options.IdleTimeout = TimeSpan.FromHours(1);
|
||||
})
|
||||
.ConfigureKestrel((context, options) =>
|
||||
{
|
||||
var basePort = 5557;
|
||||
options.EnableAltSvc = true;
|
||||
|
||||
options.Listen(IPAddress.Any, basePort, listenOptions =>
|
||||
{
|
||||
listenOptions.UseHttps(httpsOptions =>
|
||||
{
|
||||
httpsOptions.ServerCertificate = cert;
|
||||
});
|
||||
listenOptions.Protocols = HttpProtocols.Http3;
|
||||
});
|
||||
})
|
||||
.UseStartup<Startup>();
|
||||
});
|
||||
{
|
||||
httpsOptions.ServerCertificate = cert;
|
||||
});
|
||||
listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
|
||||
});
|
||||
})
|
||||
.UseStartup<Startup>();
|
||||
});
|
||||
|
||||
hostBuilder.Build().Run();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace QuicSampleApp
|
||||
{
|
||||
|
|
@ -35,7 +32,6 @@ namespace QuicSampleApp
|
|||
.UseQuic(options =>
|
||||
{
|
||||
options.Certificate = null;
|
||||
options.RegistrationName = "AspNetCore-MsQuic";
|
||||
options.Alpn = "QuicTest";
|
||||
options.IdleTimeout = TimeSpan.FromHours(1);
|
||||
})
|
||||
|
|
@ -46,54 +42,37 @@ namespace QuicSampleApp
|
|||
options.Listen(IPAddress.Any, basePort, listenOptions =>
|
||||
{
|
||||
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
|
||||
try
|
||||
|
||||
while (true)
|
||||
{
|
||||
var stream = await connection.AcceptAsync();
|
||||
while (true)
|
||||
{
|
||||
var result = await connection.Transport.Input.ReadAsync();
|
||||
var result = await stream.Transport.Input.ReadAsync();
|
||||
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
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>();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic;
|
||||
using Microsoft.AspNetCore.Connections.Abstractions.Features;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -26,13 +23,12 @@ namespace QuicSampleClient
|
|||
})
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton<IConnectionFactory, QuicConnectionFactory>();
|
||||
services.AddSingleton<IMultiplexedConnectionFactory, QuicConnectionFactory>();
|
||||
services.AddSingleton<QuicClientService>();
|
||||
services.AddOptions<QuicTransportOptions>();
|
||||
services.Configure<QuicTransportOptions>((options) =>
|
||||
{
|
||||
options.Alpn = "QuicTest";
|
||||
options.RegistrationName = "Quic-AspNetCore-client";
|
||||
options.Certificate = null;
|
||||
options.IdleTimeout = TimeSpan.FromHours(1);
|
||||
});
|
||||
|
|
@ -43,9 +39,9 @@ namespace QuicSampleClient
|
|||
|
||||
private class QuicClientService
|
||||
{
|
||||
private readonly IConnectionFactory _connectionFactory;
|
||||
private readonly IMultiplexedConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<QuicClientService> _logger;
|
||||
public QuicClientService(IConnectionFactory connectionFactory, ILogger<QuicClientService> logger)
|
||||
public QuicClientService(IMultiplexedConnectionFactory connectionFactory, ILogger<QuicClientService> logger)
|
||||
{
|
||||
_connectionFactory = connectionFactory;
|
||||
_logger = logger;
|
||||
|
|
@ -55,8 +51,7 @@ namespace QuicSampleClient
|
|||
{
|
||||
Console.WriteLine("Starting");
|
||||
var connectionContext = await _connectionFactory.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 5555));
|
||||
var createStreamFeature = connectionContext.Features.Get<IQuicCreateStreamFeature>();
|
||||
var streamContext = await createStreamFeature.StartBidirectionalStreamAsync();
|
||||
var streamContext = await connectionContext.ConnectAsync();
|
||||
|
||||
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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -24,13 +26,68 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var requestStream = await InitializeConnectionAndStreamsAsync(_echoApplication);
|
||||
|
||||
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 responseData = await requestStream.ExpectDataAsync();
|
||||
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]
|
||||
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.
|
||||
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.QPack;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Connections.Abstractions.Features;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
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.Testing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using static System.IO.Pipelines.DuplexPipe;
|
||||
using static Microsoft.AspNetCore.Server.Kestrel.Core.Tests.Http2TestBase;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||
{
|
||||
public class Http3TestBase : TestApplicationErrorLoggerLoggedTest, IDisposable, IQuicCreateStreamFeature, IQuicStreamListenerFeature
|
||||
public class Http3TestBase : TestApplicationErrorLoggerLoggedTest, IDisposable
|
||||
{
|
||||
internal TestServiceContext _serviceContext;
|
||||
internal Http3Connection _connection;
|
||||
internal readonly TimeoutControl _timeoutControl;
|
||||
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<MockTimeoutControlBase> _mockTimeoutControl;
|
||||
internal readonly MemoryPool<byte> _memoryPool = SlabMemoryPoolFactory.Create();
|
||||
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
|
||||
{
|
||||
SingleReader = true,
|
||||
SingleWriter = true
|
||||
});
|
||||
protected readonly RequestDelegate _noopApplication;
|
||||
protected readonly RequestDelegate _echoApplication;
|
||||
protected readonly RequestDelegate _echoMethod;
|
||||
protected readonly RequestDelegate _echoPath;
|
||||
protected readonly RequestDelegate _echoHost;
|
||||
|
||||
public Http3TestBase()
|
||||
{
|
||||
_timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object);
|
||||
_mockTimeoutControl = new Mock<MockTimeoutControlBase>(_timeoutControl) { CallBase = true };
|
||||
_timeoutControl.Debugger = Mock.Of<IDebugger>();
|
||||
|
||||
_noopApplication = context => Task.CompletedTask;
|
||||
|
||||
_echoApplication = async context =>
|
||||
{
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
_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)
|
||||
|
|
@ -79,7 +106,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
CreateConnection();
|
||||
}
|
||||
|
||||
_connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(application));
|
||||
// Skip all heartbeat and lifetime notification feature registrations.
|
||||
_connectionTask = _connection.InnerProcessRequestsAsync(new DummyApplication(application));
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
|
@ -100,24 +128,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var limits = _serviceContext.ServerOptions.Limits;
|
||||
|
||||
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,
|
||||
ServiceContext = _serviceContext,
|
||||
MemoryPool = _memoryPool,
|
||||
Transport = null, // Make sure it's null
|
||||
TimeoutControl = _mockTimeoutControl.Object
|
||||
};
|
||||
|
||||
_connection = new Http3Connection(httpConnectionContext);
|
||||
var httpConnection = new HttpConnection(httpConnectionContext);
|
||||
httpConnection.Initialize(_connection);
|
||||
_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
|
||||
|
|
@ -155,30 +180,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
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)
|
||||
{
|
||||
var stream = new Http3ControlStream(this, _connection);
|
||||
_acceptConnectionQueue.Writer.TryWrite(stream.ConnectionContext);
|
||||
var stream = new Http3ControlStream(this);
|
||||
_multiplexedContext.AcceptQueue.Writer.TryWrite(stream.StreamContext);
|
||||
await stream.WriteStreamIdAsync(id);
|
||||
return stream;
|
||||
}
|
||||
|
|
@ -186,7 +191,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
internal ValueTask<Http3RequestStream> CreateRequestStream()
|
||||
{
|
||||
var stream = new Http3RequestStream(this, _connection);
|
||||
_acceptConnectionQueue.Writer.TryWrite(stream.ConnectionContext);
|
||||
_multiplexedContext.AcceptQueue.Writer.TryWrite(stream.StreamContext);
|
||||
return new ValueTask<Http3RequestStream>(stream);
|
||||
}
|
||||
|
||||
|
|
@ -194,7 +199,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
var stream = new Http3RequestStream(this, _connection);
|
||||
// TODO put these somewhere to be read.
|
||||
return new ValueTask<ConnectionContext>(stream.ConnectionContext);
|
||||
return new ValueTask<ConnectionContext>(stream.StreamContext);
|
||||
}
|
||||
|
||||
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 CanWrite => true;
|
||||
|
||||
public long StreamId => 0;
|
||||
|
||||
public long Error { get; set; }
|
||||
|
||||
private readonly byte[] _headerEncodingBuffer = new byte[Http3PeerSettings.MinAllowedMaxFrameSize];
|
||||
private QPackEncoder _qpackEncoder = new QPackEncoder();
|
||||
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);
|
||||
|
||||
_pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions);
|
||||
|
||||
ConnectionContext = new DefaultConnectionContext();
|
||||
ConnectionContext.Transport = _pair.Transport;
|
||||
ConnectionContext.Features.Set<IQuicStreamFeature>(this);
|
||||
|
||||
StreamContext = new TestStreamContext(canRead: true, canWrite: true, _pair, 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 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).
|
||||
Http3FrameWriter.WriteHeader(frame, outputWriter);
|
||||
await SendAsync(buffer.Span.Slice(0, length));
|
||||
|
||||
if (endStream)
|
||||
{
|
||||
await _pair.Application.Output.CompleteAsync();
|
||||
}
|
||||
|
||||
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 frame = new Http3RawFrame();
|
||||
|
|
@ -267,9 +278,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
frame.Length = data.Length;
|
||||
Http3FrameWriter.WriteHeader(frame, outputWriter);
|
||||
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();
|
||||
_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();
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -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 CanWrite => false;
|
||||
|
||||
// TODO
|
||||
public long StreamId => 0;
|
||||
|
||||
public Http3ControlStream(Http3TestBase testBase, Http3Connection connection)
|
||||
public long Error { get; set; }
|
||||
|
||||
public Http3ControlStream(Http3TestBase testBase)
|
||||
{
|
||||
_testBase = testBase;
|
||||
_connection = connection;
|
||||
var inputPipeOptions = GetInputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool);
|
||||
var outputPipeOptions = GetOutputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool);
|
||||
|
||||
_pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions);
|
||||
|
||||
ConnectionContext = new DefaultConnectionContext();
|
||||
ConnectionContext.Transport = _pair.Transport;
|
||||
ConnectionContext.Features.Set<IQuicStreamFeature>(this);
|
||||
StreamContext = new TestStreamContext(canRead: false, canWrite: true, _pair, this);
|
||||
}
|
||||
|
||||
public async Task WriteStreamIdAsync(int id)
|
||||
|
|
@ -402,5 +428,100 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
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>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
|
|
@ -27,4 +27,4 @@
|
|||
<Reference Include="Newtonsoft.Json" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<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>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -27,16 +27,21 @@ namespace CodeGenerator
|
|||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -45,43 +50,37 @@ namespace CodeGenerator
|
|||
string knownHeadersPath,
|
||||
string httpProtocolFeatureCollectionPath,
|
||||
string httpUtilitiesPath,
|
||||
string transportConnectionFeatureCollectionPath,
|
||||
string http2ConnectionPath)
|
||||
string http2ConnectionPath,
|
||||
string transportMultiplexedConnectionFeatureCollectionPath,
|
||||
string transportConnectionFeatureCollectionPath)
|
||||
{
|
||||
var knownHeadersContent = KnownHeaders.GeneratedFile();
|
||||
var httpProtocolFeatureCollectionContent = HttpProtocolFeatureCollection.GenerateFile();
|
||||
var httpUtilitiesContent = HttpUtilities.HttpUtilities.GeneratedFile();
|
||||
var transportMultiplexedConnectionFeatureCollectionContent = TransportMultiplexedConnectionFeatureCollection.GenerateFile();
|
||||
var transportConnectionFeatureCollectionContent = TransportConnectionFeatureCollection.GenerateFile();
|
||||
var http2ConnectionContent = Http2Connection.GenerateFile();
|
||||
|
||||
var existingKnownHeaders = File.Exists(knownHeadersPath) ? File.ReadAllText(knownHeadersPath) : "";
|
||||
if (!string.Equals(knownHeadersContent, existingKnownHeaders))
|
||||
UpdateFile(knownHeadersPath, knownHeadersContent);
|
||||
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) : "";
|
||||
if (!string.Equals(httpProtocolFeatureCollectionContent, existingHttpProtocolFeatureCollection))
|
||||
var existingHttp2Connection = File.Exists(path) ? File.ReadAllText(path) : "";
|
||||
if (!string.Equals(content, existingHttp2Connection))
|
||||
{
|
||||
File.WriteAllText(httpProtocolFeatureCollectionPath, httpProtocolFeatureCollectionContent);
|
||||
}
|
||||
|
||||
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);
|
||||
File.WriteAllText(path, content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
_enumerator = headers.GetEnumerator();
|
||||
|
|
@ -351,7 +350,11 @@ namespace System.Net.Http.QPack
|
|||
buffer[0] = 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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
// 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;
|
||||
|
||||
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.
|
||||
|
||||
try
|
||||
|
|
|
|||
Loading…
Reference in New Issue