Skip to content

Commit 777beb2

Browse files
committed
Almost got things working, need to fix KestrelConnection next. Base class is the best way to move forward here.
1 parent 8b1690f commit 777beb2

15 files changed

+442
-113
lines changed

src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@ public static IConnectionBuilder Run(this IConnectionBuilder connectionBuilder,
4040
});
4141
}
4242
}
43-
}
43+
}

src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ public MultiplexedConnectionBuilder(IServiceProvider applicationServices)
1919
ApplicationServices = applicationServices;
2020
}
2121

22-
public IMultiplexedConnectionBuilder Use(Func<MultiplexedConnectionDelegate, MultiplexedConnectionDelegate> middleware)
22+
public IMultiplexedConnectionBuilder UseMultiplexed(Func<MultiplexedConnectionDelegate, MultiplexedConnectionDelegate> middleware)
2323
{
2424
_components.Add(middleware);
2525
return this;
2626
}
2727

28-
public MultiplexedConnectionDelegate Build()
28+
public MultiplexedConnectionDelegate BuildMultiplexed()
2929
{
3030
MultiplexedConnectionDelegate app = features =>
3131
{
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Threading.Tasks;
6+
7+
namespace Microsoft.AspNetCore.Connections
8+
{
9+
public static class MultiplexedConnectionBuilderExtensions
10+
{
11+
public static IMultiplexedConnectionBuilder UseMultiplexed(this IMultiplexedConnectionBuilder connectionBuilder, Func<MultiplexedConnectionContext, Func<Task>, Task> middleware)
12+
{
13+
return connectionBuilder.UseMultiplexed(next =>
14+
{
15+
return context =>
16+
{
17+
Func<Task> simpleNext = () => next(context);
18+
return middleware(context, simpleNext);
19+
};
20+
});
21+
}
22+
23+
public static IMultiplexedConnectionBuilder RunMultiplexed(this IMultiplexedConnectionBuilder connectionBuilder, Func<MultiplexedConnectionContext, Task> middleware)
24+
{
25+
return connectionBuilder.UseMultiplexed(next =>
26+
{
27+
return context =>
28+
{
29+
return middleware(context);
30+
};
31+
});
32+
}
33+
}
34+
}

src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
1414
{
1515
internal class Http3Connection : IRequestProcessor
1616
{
17-
public HttpConnectionContext Context { get; private set; }
17+
public Http3ConnectionContext Context { get; private set; }
1818

1919
public DynamicTable DynamicTable { get; set; }
2020

@@ -29,7 +29,7 @@ internal class Http3Connection : IRequestProcessor
2929
private MultiplexedConnectionContext _multiplexedContext;
3030
//private volatile bool _haveSentGoAway;
3131

32-
public Http3Connection(HttpConnectionContext context)
32+
public Http3Connection(Http3ConnectionContext context)
3333
{
3434
_multiplexedContext = context.MultiplexedConnectionContext;
3535
Context = context;
@@ -77,7 +77,7 @@ public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> appl
7777
{
7878
ConnectionId = streamContext.ConnectionId,
7979
ConnectionContext = streamContext,
80-
Protocols = Context.Protocols,
80+
Protocols = HttpProtocols.Http3,
8181
ServiceContext = Context.ServiceContext,
8282
ConnectionFeatures = streamContext.Features,
8383
MemoryPool = Context.MemoryPool,
@@ -145,7 +145,7 @@ private async ValueTask<Http3ControlStream> CreateNewUnidirectionalStreamAsync<T
145145
{
146146
ConnectionId = connectionContext.ConnectionId,
147147
ConnectionContext = connectionContext,
148-
Protocols = Context.Protocols,
148+
Protocols = HttpProtocols.Http3,
149149
ServiceContext = Context.ServiceContext,
150150
ConnectionFeatures = connectionContext.Features,
151151
MemoryPool = Context.MemoryPool,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Buffers;
5+
using System.IO.Pipelines;
6+
using System.Net;
7+
using Microsoft.AspNetCore.Connections;
8+
using Microsoft.AspNetCore.Http.Features;
9+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
10+
11+
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
12+
{
13+
internal class Http3ConnectionContext
14+
{
15+
public string ConnectionId { get; set; }
16+
public MultiplexedConnectionContext MultiplexedConnectionContext { get; set; }
17+
public ServiceContext ServiceContext { get; set; }
18+
public IFeatureCollection ConnectionFeatures { get; set; }
19+
public MemoryPool<byte> MemoryPool { get; set; }
20+
public IPEndPoint LocalEndPoint { get; set; }
21+
public IPEndPoint RemoteEndPoint { get; set; }
22+
public ITimeoutControl TimeoutControl { get; set; }
23+
}
24+
}
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Net;
7+
using System.Threading.Tasks;
8+
using Microsoft.AspNetCore.Connections;
9+
using Microsoft.AspNetCore.Connections.Features;
10+
using Microsoft.AspNetCore.Hosting.Server;
11+
using Microsoft.AspNetCore.Http.Features;
12+
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
13+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
14+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
15+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3;
16+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
17+
using Microsoft.Extensions.Logging;
18+
19+
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
20+
{
21+
internal class Http3ConnectionTemp : ITimeoutHandler
22+
{
23+
// Use C#7.3's ReadOnlySpan<byte> optimization for static data https://vcsjones.com/2019/02/01/csharp-readonly-span-bytes-static/
24+
private static ReadOnlySpan<byte> Http2Id => new[] { (byte)'h', (byte)'2' };
25+
26+
private readonly Http3ConnectionContext _context;
27+
private readonly ISystemClock _systemClock;
28+
private readonly TimeoutControl _timeoutControl;
29+
30+
private readonly object _protocolSelectionLock = new object();
31+
private ProtocolSelectionState _protocolSelectionState = ProtocolSelectionState.Initializing;
32+
private IRequestProcessor _requestProcessor;
33+
private Http1Connection _http1Connection;
34+
35+
public Http3ConnectionTemp(Http3ConnectionContext context)
36+
{
37+
_context = context;
38+
_systemClock = _context.ServiceContext.SystemClock;
39+
40+
_timeoutControl = new TimeoutControl(this);
41+
42+
// Tests override the timeout control sometimes
43+
_context.TimeoutControl ??= _timeoutControl;
44+
}
45+
46+
private IKestrelTrace Log => _context.ServiceContext.Log;
47+
48+
public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> httpApplication)
49+
{
50+
try
51+
{
52+
// Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs.
53+
_timeoutControl.Initialize(_systemClock.UtcNowTicks);
54+
55+
IRequestProcessor requestProcessor = null;
56+
57+
requestProcessor = new Http3Connection(_context);
58+
_protocolSelectionState = ProtocolSelectionState.Selected;
59+
60+
_requestProcessor = requestProcessor;
61+
62+
if (requestProcessor != null)
63+
{
64+
var connectionHeartbeatFeature = _context.ConnectionFeatures.Get<IConnectionHeartbeatFeature>();
65+
var connectionLifetimeNotificationFeature = _context.ConnectionFeatures.Get<IConnectionLifetimeNotificationFeature>();
66+
67+
// These features should never be null in Kestrel itself, if this middleware is ever refactored to run outside of kestrel,
68+
// we'll need to handle these missing.
69+
Debug.Assert(connectionHeartbeatFeature != null, nameof(IConnectionHeartbeatFeature) + " is missing!");
70+
Debug.Assert(connectionLifetimeNotificationFeature != null, nameof(IConnectionLifetimeNotificationFeature) + " is missing!");
71+
72+
// Register the various callbacks once we're going to start processing requests
73+
74+
// The heart beat for various timeouts
75+
connectionHeartbeatFeature?.OnHeartbeat(state => ((Http3ConnectionTemp)state).Tick(), this);
76+
77+
// Register for graceful shutdown of the server
78+
using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((Http3ConnectionTemp)state).StopProcessingNextRequest(), this);
79+
80+
// Register for connection close
81+
using var closedRegistration = _context.MultiplexedConnectionContext.ConnectionClosed.Register(state => ((Http3ConnectionTemp)state).OnConnectionClosed(), this);
82+
83+
await requestProcessor.ProcessRequestsAsync(httpApplication);
84+
}
85+
}
86+
catch (Exception ex)
87+
{
88+
Log.LogCritical(0, ex, $"Unexpected exception in {nameof(HttpConnection)}.{nameof(ProcessRequestsAsync)}.");
89+
}
90+
finally
91+
{
92+
if (_http1Connection?.IsUpgraded == true)
93+
{
94+
_context.ServiceContext.ConnectionManager.UpgradedConnectionCount.ReleaseOne();
95+
}
96+
}
97+
}
98+
99+
// For testing only
100+
internal void Initialize(IRequestProcessor requestProcessor)
101+
{
102+
_requestProcessor = requestProcessor;
103+
_http1Connection = requestProcessor as Http1Connection;
104+
_protocolSelectionState = ProtocolSelectionState.Selected;
105+
}
106+
107+
private void StopProcessingNextRequest()
108+
{
109+
ProtocolSelectionState previousState;
110+
lock (_protocolSelectionLock)
111+
{
112+
previousState = _protocolSelectionState;
113+
Debug.Assert(previousState != ProtocolSelectionState.Initializing, "The state should never be initializing");
114+
115+
switch (_protocolSelectionState)
116+
{
117+
case ProtocolSelectionState.Selected:
118+
case ProtocolSelectionState.Aborted:
119+
break;
120+
}
121+
}
122+
123+
switch (previousState)
124+
{
125+
case ProtocolSelectionState.Selected:
126+
_requestProcessor.StopProcessingNextRequest();
127+
break;
128+
case ProtocolSelectionState.Aborted:
129+
break;
130+
}
131+
}
132+
133+
private void OnConnectionClosed()
134+
{
135+
ProtocolSelectionState previousState;
136+
lock (_protocolSelectionLock)
137+
{
138+
previousState = _protocolSelectionState;
139+
Debug.Assert(previousState != ProtocolSelectionState.Initializing, "The state should never be initializing");
140+
141+
switch (_protocolSelectionState)
142+
{
143+
case ProtocolSelectionState.Selected:
144+
case ProtocolSelectionState.Aborted:
145+
break;
146+
}
147+
}
148+
149+
switch (previousState)
150+
{
151+
case ProtocolSelectionState.Selected:
152+
_requestProcessor.OnInputOrOutputCompleted();
153+
break;
154+
case ProtocolSelectionState.Aborted:
155+
break;
156+
}
157+
}
158+
159+
private void Abort(ConnectionAbortedException ex)
160+
{
161+
ProtocolSelectionState previousState;
162+
163+
lock (_protocolSelectionLock)
164+
{
165+
previousState = _protocolSelectionState;
166+
Debug.Assert(previousState != ProtocolSelectionState.Initializing, "The state should never be initializing");
167+
168+
_protocolSelectionState = ProtocolSelectionState.Aborted;
169+
}
170+
171+
switch (previousState)
172+
{
173+
case ProtocolSelectionState.Selected:
174+
_requestProcessor.Abort(ex);
175+
break;
176+
case ProtocolSelectionState.Aborted:
177+
break;
178+
}
179+
}
180+
181+
182+
private void Tick()
183+
{
184+
if (_protocolSelectionState == ProtocolSelectionState.Aborted)
185+
{
186+
// It's safe to check for timeouts on a dead connection,
187+
// but try not to in order to avoid extraneous logs.
188+
return;
189+
}
190+
191+
// It's safe to use UtcNowUnsynchronized since Tick is called by the Heartbeat.
192+
var now = _systemClock.UtcNowUnsynchronized;
193+
_timeoutControl.Tick(now);
194+
_requestProcessor.Tick(now);
195+
}
196+
197+
public void OnTimeout(TimeoutReason reason)
198+
{
199+
// In the cases that don't log directly here, we expect the setter of the timeout to also be the input
200+
// reader, so when the read is canceled or aborted, the reader should write the appropriate log.
201+
switch (reason)
202+
{
203+
case TimeoutReason.KeepAlive:
204+
_requestProcessor.StopProcessingNextRequest();
205+
break;
206+
case TimeoutReason.RequestHeaders:
207+
_requestProcessor.HandleRequestHeadersTimeout();
208+
break;
209+
case TimeoutReason.ReadDataRate:
210+
_requestProcessor.HandleReadDataRateTimeout();
211+
break;
212+
case TimeoutReason.WriteDataRate:
213+
Log.ResponseMinimumDataRateNotSatisfied(_context.ConnectionId, _http1Connection?.TraceIdentifier);
214+
Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied));
215+
break;
216+
case TimeoutReason.RequestBodyDrain:
217+
case TimeoutReason.TimeoutFeature:
218+
Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedOutByServer));
219+
break;
220+
default:
221+
Debug.Assert(false, "Invalid TimeoutReason");
222+
break;
223+
}
224+
}
225+
226+
private enum ProtocolSelectionState
227+
{
228+
Initializing,
229+
Selected,
230+
Aborted
231+
}
232+
}
233+
}

src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,6 @@ public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> http
6868
requestProcessor = new Http2Connection(_context);
6969
_protocolSelectionState = ProtocolSelectionState.Selected;
7070
break;
71-
case HttpProtocols.Http3:
72-
requestProcessor = new Http3Connection(_context);
73-
_protocolSelectionState = ProtocolSelectionState.Selected;
74-
break;
7571
case HttpProtocols.None:
7672
// An error was already logged in SelectProtocol(), but we should close the connection.
7773
break;
@@ -204,11 +200,6 @@ private void Abort(ConnectionAbortedException ex)
204200

205201
private HttpProtocols SelectProtocol()
206202
{
207-
if (_context.Protocols == HttpProtocols.Http3)
208-
{
209-
return HttpProtocols.Http3;
210-
}
211-
212203
var hasTls = _context.ConnectionFeatures.Get<ITlsConnectionFeature>() != null;
213204
var applicationProtocol = _context.ConnectionFeatures.Get<ITlsApplicationProtocolFeature>()?.ApplicationProtocol
214205
?? new ReadOnlyMemory<byte>();

src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,11 @@
1010

1111
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
1212
{
13-
// TODO consider duplicating this and HttpConnection for Http3.
1413
internal class HttpConnectionContext
1514
{
1615
public string ConnectionId { get; set; }
1716
public HttpProtocols Protocols { get; set; }
1817
public ConnectionContext ConnectionContext { get; set; }
19-
public MultiplexedConnectionContext MultiplexedConnectionContext { get; set; }
2018
public ServiceContext ServiceContext { get; set; }
2119
public IFeatureCollection ConnectionFeatures { get; set; }
2220
public MemoryPool<byte> MemoryPool { get; set; }

0 commit comments

Comments
 (0)