Invert the dependency between connection adapters and Frame (#1822)

* Invert the dependency between connection adapters and Frame
- Removed PrepareRequest from IAdaptedConnection and instead added
a feature collection to the ConnectionAdapterContext. This allows features to be set
once by the adapter instead of per request. It's the Frame's job to copy features
from the connection level feature collection into the per request feature collection.
- Set the scheme to "https" based on the presence of ITlsConnectionFeature.
- Always set ITlsConnection feature if the HttpsAdaptedConnection doesn't throw during
the handshake
This commit is contained in:
David Fowler 2017-05-10 15:29:43 -07:00 committed by GitHub
parent 90ee2252a0
commit b9518e3684
9 changed files with 37 additions and 55 deletions

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{
@ -9,11 +10,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
// we want to add more connection metadata later.
public class ConnectionAdapterContext
{
internal ConnectionAdapterContext(Stream connectionStream)
internal ConnectionAdapterContext(IFeatureCollection features, Stream connectionStream)
{
Features = features;
ConnectionStream = connectionStream;
}
public IFeatureCollection Features { get; }
public Stream ConnectionStream { get; }
}
}

View File

@ -3,14 +3,11 @@
using System;
using System.IO;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{
public interface IAdaptedConnection : IDisposable
{
Stream ConnectionStream { get; }
void PrepareRequest(IFeatureCollection requestFeatures);
}
}

View File

@ -40,10 +40,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
public Stream ConnectionStream { get; }
public void PrepareRequest(IFeatureCollection requestFeatures)
{
}
public void Dispose()
{
}

View File

@ -7,6 +7,7 @@ using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@ -20,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
private readonly FrameConnectionContext _context;
private readonly Frame _frame;
private readonly List<IConnectionAdapter> _connectionAdapters;
private List<IAdaptedConnection> _adaptedConnections;
private readonly TaskCompletionSource<object> _socketClosedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
private long _lastTimestamp;
@ -33,7 +34,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
_context = context;
_frame = context.Frame;
_connectionAdapters = context.ConnectionAdapters;
}
public string ConnectionId => _context.ConnectionId;
@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
var input = _context.Input.Reader;
var output = _context.Output;
if (_connectionAdapters.Count > 0)
if (_context.ConnectionAdapters.Count > 0)
{
adaptedPipeline = new AdaptedPipeline(input,
output,
@ -159,17 +159,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
private async Task<Stream> ApplyConnectionAdaptersAsync()
{
var features = new FeatureCollection();
var connectionAdapters = _context.ConnectionAdapters;
var stream = new RawStream(_context.Input.Reader, _context.Output.Writer);
var adapterContext = new ConnectionAdapterContext(stream);
var adaptedConnections = new IAdaptedConnection[_connectionAdapters.Count];
var adapterContext = new ConnectionAdapterContext(features, stream);
_adaptedConnections = new List<IAdaptedConnection>(connectionAdapters.Count);
try
{
for (var i = 0; i < _connectionAdapters.Count; i++)
for (var i = 0; i < connectionAdapters.Count; i++)
{
var adaptedConnection = await _connectionAdapters[i].OnConnectionAsync(adapterContext);
adaptedConnections[i] = adaptedConnection;
adapterContext = new ConnectionAdapterContext(adaptedConnection.ConnectionStream);
var adaptedConnection = await connectionAdapters[i].OnConnectionAsync(adapterContext);
_adaptedConnections.Add(adaptedConnection);
adapterContext = new ConnectionAdapterContext(features, adaptedConnection.ConnectionStream);
}
}
catch (Exception ex)
@ -180,7 +182,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
}
finally
{
_frame.AdaptedConnections = adaptedConnections;
_frame.ConnectionFeatures = features;
}
return adapterContext.ConnectionStream;
@ -188,10 +190,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
private void DisposeAdaptedConnections()
{
var adaptedConnections = _frame.AdaptedConnections;
var adaptedConnections = _adaptedConnections;
if (adaptedConnections != null)
{
for (int i = adaptedConnections.Length - 1; i >= 0; i--)
for (int i = adaptedConnections.Count - 1; i >= 0; i--)
{
adaptedConnections[i].Dispose();
}

View File

@ -12,7 +12,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
@ -21,6 +20,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Microsoft.AspNetCore.Http.Features;
// ReSharper disable AccessToModifiedClosure
@ -103,7 +103,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public IPipeReader Input { get; set; }
public OutputProducer Output { get; set; }
public IAdaptedConnection[] AdaptedConnections { get; set; }
public IFeatureCollection ConnectionFeatures { get; set; }
public ITimeoutControl TimeoutControl { get; set; }
protected IKestrelTrace Log => ServiceContext.Log;
@ -349,18 +349,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
RequestHeaders = FrameRequestHeaders;
ResponseHeaders = FrameResponseHeaders;
if (AdaptedConnections != null)
if (ConnectionFeatures != null)
{
try
foreach (var feature in ConnectionFeatures)
{
foreach (var adaptedConnection in AdaptedConnections)
// Set the scheme to https if there's an ITlsConnectionFeature
if (feature.Key == typeof(ITlsConnectionFeature))
{
adaptedConnection.PrepareRequest(this);
Scheme = "https";
}
}
catch (Exception ex)
{
Log.LogError(0, ex, $"Uncaught exception from the {nameof(IAdaptedConnection.PrepareRequest)} method of an {nameof(IAdaptedConnection)}.");
FastFeatureSet(feature.Key, feature.Value);
}
}

View File

@ -109,6 +109,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
return _closedAdaptedConnection;
}
// Always set the feature even though the cert might be null
context.Features.Set<ITlsConnectionFeature>(new TlsConnectionFeature
{
ClientCertificate = (X509Certificate2)sslStream.RemoteCertificate
});
return new HttpsAdaptedConnection(sslStream);
}
@ -123,17 +129,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
public Stream ConnectionStream => _sslStream;
public void PrepareRequest(IFeatureCollection requestFeatures)
{
var clientCertificate = (X509Certificate2)_sslStream.RemoteCertificate;
if (clientCertificate != null)
{
requestFeatures.Set<ITlsConnectionFeature>(new TlsConnectionFeature { ClientCertificate = clientCertificate });
}
requestFeatures.Get<IHttpRequestFeature>().Scheme = "https";
}
public void Dispose()
{
_sslStream.Dispose();
@ -144,10 +139,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
{
public Stream ConnectionStream { get; } = new ClosedStream();
public void PrepareRequest(IFeatureCollection requestFeatures)
{
}
public void Dispose()
{
}

View File

@ -7,7 +7,6 @@ using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Testing;
@ -218,10 +217,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
public Stream ConnectionStream { get; }
public void PrepareRequest(IFeatureCollection requestFeatures)
{
}
public void Dispose()
{
}

View File

@ -94,7 +94,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var server = new TestServer(context =>
{
Assert.Equal(context.Features.Get<ITlsConnectionFeature>(), null);
var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
Assert.NotNull(tlsFeature);
Assert.Null(tlsFeature.ClientCertificate);
return context.Response.WriteAsync("hello world");
},
serviceContext, listenOptions))

View File

@ -27,10 +27,6 @@ namespace Microsoft.AspNetCore.Testing
public Stream ConnectionStream { get; }
public void PrepareRequest(IFeatureCollection requestFeatures)
{
}
public void Dispose()
{
}