Re-layer the feature interfaces.

This commit is contained in:
Chris Ross 2014-02-20 14:51:31 -08:00
parent ab7e4cb3c8
commit c6c5dd6fbf
6 changed files with 393 additions and 251 deletions

View File

@ -518,7 +518,8 @@ namespace Microsoft.AspNet.Server.WebListener
{
// TODO: Make disconnect registration lazy
RegisterForDisconnectNotification(requestContext);
await _appFunc(requestContext.Features).SupressContext();
FeatureContext featureContext = new FeatureContext(requestContext);
await _appFunc(featureContext.Features).SupressContext();
await requestContext.ProcessResponseAsync().SupressContext();
}
catch (Exception ex)

View File

@ -0,0 +1,338 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
#if NET45
using System.Security.Cryptography.X509Certificates;
#endif
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.FeatureModel;
using Microsoft.AspNet.HttpFeature;
namespace Microsoft.AspNet.Server.WebListener
{
internal class FeatureContext : IHttpRequestInformation, IHttpConnection, IHttpResponseInformation, IHttpSendFile
#if NET45
, IHttpTransportLayerSecurity
#endif
{
private RequestContext _requestContext;
private FeatureCollection _features;
private Stream _requestBody;
private IDictionary<string, string[]> _requestHeaders;
private string _scheme;
private string _httpMethod;
private string _httpProtocolVersion;
private string _query;
private string _pathBase;
private string _path;
#if NET45
private IPAddress _remoteIpAddress;
private IPAddress _localIpAddress;
#endif
private int? _remotePort;
private int? _localPort;
private bool? _isLocal;
#if NET45
private X509Certificate _clientCert;
#endif
private Stream _responseStream;
private IDictionary<string, string[]> _responseHeaders;
internal FeatureContext(RequestContext requestContext)
{
_requestContext = requestContext;
_features = new FeatureCollection();
PopulateFeatures();
}
internal IFeatureCollection Features
{
get { return _features; }
}
private Request Request
{
get { return _requestContext.Request; }
}
private Response Response
{
get { return _requestContext.Response; }
}
private void PopulateFeatures()
{
_features.Add(typeof(IHttpRequestInformation), this);
_features.Add(typeof(IHttpConnection), this);
if (Request.IsSecureConnection)
{
#if NET45
// TODO: Should this feature be conditional? Should we add this for HTTP requests?
_features.Add(typeof(IHttpTransportLayerSecurity), this);
#endif
}
_features.Add(typeof(IHttpResponseInformation), this);
_features.Add(typeof(IHttpSendFile), this);
// TODO:
// _environment.CallCancelled = _cts.Token;
// _environment.User = _request.User;
// Opaque/WebSockets
// Channel binding
/*
// Server
_environment.Listener = _server;
_environment.ConnectionId = _request.ConnectionId;
*/
}
#region IHttpRequestInformation
Stream IHttpRequestInformation.Body
{
get
{
if (_requestBody == null)
{
_requestBody = Request.Body;
}
return _requestBody;
}
set { _requestBody = value; }
}
IDictionary<string, string[]> IHttpRequestInformation.Headers
{
get
{
if (_requestHeaders == null)
{
_requestHeaders = Request.Headers;
}
return _requestHeaders;
}
set { _requestHeaders = value; }
}
string IHttpRequestInformation.Method
{
get
{
if (_httpMethod == null)
{
_httpMethod = Request.Method;
}
return _httpMethod;
}
set { _httpMethod = value; }
}
string IHttpRequestInformation.Path
{
get
{
if (_path == null)
{
_path = Request.Path;
}
return _path;
}
set { _path = value; }
}
string IHttpRequestInformation.PathBase
{
get
{
if (_pathBase == null)
{
_pathBase = Request.PathBase;
}
return _pathBase;
}
set { _pathBase = value; }
}
string IHttpRequestInformation.Protocol
{
get
{
if (_httpProtocolVersion == null)
{
_httpProtocolVersion = Request.Protocol;
}
return _httpProtocolVersion;
}
set { _httpProtocolVersion = value; }
}
string IHttpRequestInformation.QueryString
{
get
{
if (_query == null)
{
_query = Request.QueryString;
}
return _query;
}
set { _query = value; }
}
string IHttpRequestInformation.Scheme
{
get
{
if (_scheme == null)
{
_scheme = Request.Scheme;
}
return _scheme;
}
set { _scheme = value; }
}
#endregion
#region IHttpConnection
bool IHttpConnection.IsLocal
{
get
{
if (_isLocal == null)
{
_isLocal = Request.IsLocal;
}
return _isLocal.Value;
}
set { _isLocal = value; }
}
#if NET45
IPAddress IHttpConnection.LocalIpAddress
{
get
{
if (_localIpAddress == null)
{
_localIpAddress = Request.LocalIpAddress;
}
return _localIpAddress;
}
set { _localIpAddress = value; }
}
IPAddress IHttpConnection.RemoteIpAddress
{
get
{
if (_remoteIpAddress == null)
{
_remoteIpAddress = Request.RemoteIpAddress;
}
return _remoteIpAddress;
}
set { _remoteIpAddress = value; }
}
#endif
int IHttpConnection.LocalPort
{
get
{
if (_localPort == null)
{
_localPort = Request.LocalPort;
}
return _localPort.Value;
}
set { _localPort = value; }
}
int IHttpConnection.RemotePort
{
get
{
if (_remotePort == null)
{
_remotePort = Request.RemotePort;
}
return _remotePort.Value;
}
set { _remotePort = value; }
}
#endregion
#region IHttpTransportLayerSecurity
#if NET45
X509Certificate IHttpTransportLayerSecurity.ClientCertificate
{
get
{
if (_clientCert == null)
{
_clientCert = Request.GetClientCertificateAsync().Result; // TODO: Sync;
}
return _clientCert;
}
set { _clientCert = value; }
}
async Task IHttpTransportLayerSecurity.LoadAsync()
{
if (_clientCert == null)
{
_clientCert = await Request.GetClientCertificateAsync();
}
}
#endif
#endregion
#region IHttpResponseInformation
Stream IHttpResponseInformation.Body
{
get
{
if (_responseStream == null)
{
_responseStream = Response.Body;
}
return _responseStream;
}
set { _responseStream = value; }
}
IDictionary<string, string[]> IHttpResponseInformation.Headers
{
get
{
if (_responseHeaders == null)
{
_responseHeaders = Response.Headers;
}
return _responseHeaders;
}
set { _responseHeaders = value; }
}
void IHttpResponseInformation.OnSendingHeaders(Action<object> callback, object state)
{
Response.OnSendingHeaders(callback, state);
}
string IHttpResponseInformation.ReasonPhrase
{
get { return Response.ReasonPhrase; }
set { Response.ReasonPhrase = value; }
}
int IHttpResponseInformation.StatusCode
{
get { return Response.StatusCode; }
set { Response.StatusCode = value; }
}
#endregion
Task IHttpSendFile.SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
{
return Response.SendFileAsync(path, offset, length, cancellation);
}
}
}

View File

@ -11,17 +11,15 @@ using System.IO;
using System.Net;
using System.Runtime.InteropServices;
#if NET45
using System.Security.Authentication.ExtendedProtection;
using System.Security.Cryptography.X509Certificates;
#endif
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.HttpFeature;
namespace Microsoft.AspNet.Server.WebListener
{
internal sealed class Request : IHttpRequestInformation, IHttpConnection, IHttpTransportLayerSecurity, IDisposable
internal sealed class Request : IDisposable
{
private RequestContext _requestContext;
private NativeRequestContext _nativeRequestContext;
@ -31,11 +29,9 @@ namespace Microsoft.AspNet.Server.WebListener
private ulong _contextId;
private SslStatus _sslStatus;
private string _scheme;
private string _httpMethod;
private Version _httpVersion;
private string _httpProtocolVersion;
// private Uri _requestUri;
private string _rawUrl;
@ -53,17 +49,9 @@ namespace Microsoft.AspNet.Server.WebListener
private BoundaryType _contentBoundaryType;
private long _contentLength;
private Stream _nativeStream;
private Stream _requestStream;
private SocketAddress _localEndPoint;
private SocketAddress _remoteEndPoint;
#if NET45
private IPAddress _remoteIpAddress;
private IPAddress _localIpAddress;
#endif
private int? _remotePort;
private int? _localPort;
private bool? _isLocal;
private IPrincipal _user;
@ -215,25 +203,6 @@ namespace Microsoft.AspNet.Server.WebListener
}
}
#if NET45
X509Certificate IHttpTransportLayerSecurity.ClientCertificate
{
get
{
if (_clientCert == null)
{
// TODO: Sync
((IHttpTransportLayerSecurity)this).LoadAsync().Wait();
}
return _clientCert;
}
set
{
_clientCert = value;
}
}
#endif
// TODO: Move this to the constructor, that's where it will be called.
internal long ContentLength64
{
@ -270,33 +239,15 @@ namespace Microsoft.AspNet.Server.WebListener
public IDictionary<string, string[]> Headers
{
get
{
return _headers;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
_headers = value;
}
get { return _headers; }
}
public string Method
{
get
{
return _httpMethod;
}
set
{
_httpMethod = value;
}
get { return _httpMethod; }
}
internal Stream NativeStream
public Stream Body
{
get
{
@ -309,60 +260,25 @@ namespace Microsoft.AspNet.Server.WebListener
}
}
public Stream Body
{
get
{
if (_requestStream == null)
{
// TODO: Move this to the constructor (or a lazy Env dictionary)
_requestStream = NativeStream;
}
return _requestStream;
}
set
{
_requestStream = value;
}
}
public string PathBase
{
get
{
return _pathBase;
}
set
{
_pathBase = value;
}
get { return _pathBase; }
}
public string Path
{
get
{
return _path;
}
set
{
_path = value;
}
get { return _path; }
}
public bool IsLocal
{
get
{
if (!_isLocal.HasValue)
{
_isLocal = LocalEndPoint.GetIPAddress().Equals(RemoteEndPoint.GetIPAddress());
}
return _isLocal.Value;
}
set
{
_isLocal = value;
#if NET45
return LocalEndPoint.GetIPAddress().Equals(RemoteEndPoint.GetIPAddress());
#else
throw new NotImplementedException();
#endif
}
}
@ -394,30 +310,19 @@ namespace Microsoft.AspNet.Server.WebListener
{
get
{
if (_httpProtocolVersion == null)
if (_httpVersion.Major == 1)
{
if (_httpVersion.Major == 1)
if (_httpVersion.Minor == 1)
{
if (_httpVersion.Minor == 1)
{
_httpProtocolVersion = "HTTP/1.1";
}
else if (_httpVersion.Minor == 0)
{
_httpProtocolVersion = "HTTP/1.0";
}
return "HTTP/1.1";
}
else
else if (_httpVersion.Minor == 0)
{
_httpProtocolVersion = "HTTP/" + _httpVersion.ToString(2);
return "HTTP/1.0";
}
}
return _httpProtocolVersion;
}
set
{
// TODO: Set _httpVersion?
_httpProtocolVersion = value;
return "HTTP/" + _httpVersion.ToString(2);
}
}
@ -432,7 +337,7 @@ namespace Microsoft.AspNet.Server.WebListener
}
}
internal SocketAddress RemoteEndPoint
private SocketAddress RemoteEndPoint
{
get
{
@ -445,7 +350,7 @@ namespace Microsoft.AspNet.Server.WebListener
}
}
internal SocketAddress LocalEndPoint
private SocketAddress LocalEndPoint
{
get
{
@ -457,85 +362,30 @@ namespace Microsoft.AspNet.Server.WebListener
return _localEndPoint;
}
}
#if NET45
public IPAddress RemoteIpAddress
{
get
{
if (_remoteIpAddress == null)
{
_remoteIpAddress = RemoteEndPoint.GetIPAddress();
}
return _remoteIpAddress;
}
set
{
_remoteIpAddress = value;
}
get { return RemoteEndPoint.GetIPAddress(); }
}
public IPAddress LocalIpAddress
{
get
{
if (_localIpAddress == null)
{
_localIpAddress = LocalEndPoint.GetIPAddress();
}
return _localIpAddress;
}
set
{
_localIpAddress = value;
}
get { return LocalEndPoint.GetIPAddress(); }
}
#endif
public int RemotePort
{
get
{
if (!_remotePort.HasValue)
{
_remotePort = RemoteEndPoint.GetPort();
}
return _remotePort.Value;
}
set
{
_remotePort = value;
}
get { return RemoteEndPoint.GetPort(); }
}
public int LocalPort
{
get
{
if (!_localPort.HasValue)
{
_localPort = LocalEndPoint.GetPort();
}
return _localPort.Value;
}
set
{
_localPort = value;
}
get { return LocalEndPoint.GetPort(); }
}
public string Scheme
{
get
{
if (_scheme == null)
{
_scheme = IsSecureConnection ? Constants.HttpsScheme : Constants.HttpScheme;
}
return _scheme;
}
set
{
_scheme = value;
}
get { return IsSecureConnection ? Constants.HttpsScheme : Constants.HttpScheme; }
}
/*
internal Uri RequestUri
@ -594,21 +444,21 @@ namespace Microsoft.AspNet.Server.WebListener
#endif
}
#if NET45
// Populates the client certificate. The result may be null if there is no client cert.
// TODO: Does it make sense for this to be invoked multiple times (e.g. renegotiate)? Client and server code appear to
// enable this, but it's unclear what Http.Sys would do.
async Task IHttpTransportLayerSecurity.LoadAsync()
public async Task<X509Certificate> GetClientCertificateAsync()
{
if (SslStatus == SslStatus.Insecure)
{
// Non-SSL
return;
return null;
}
// TODO: Verbose log
#if NET45
if (_clientCert != null)
{
return;
return _clientCert;
}
ClientCertLoader certLoader = new ClientCertLoader(RequestContext);
@ -630,10 +480,9 @@ namespace Microsoft.AspNet.Server.WebListener
}
throw;
}
#else
throw new NotImplementedException();
#endif
return _clientCert;
}
#endif
// Use this to save the blob from dispose if this object was never used (never given to a user) and is about to be
// disposed.

View File

@ -8,15 +8,9 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Authentication.ExtendedProtection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.FeatureModel;
using Microsoft.AspNet.HttpFeature;
namespace Microsoft.AspNet.Server.WebListener
{
@ -27,7 +21,6 @@ namespace Microsoft.AspNet.Server.WebListener
{
private static readonly string[] ZeroContentLength = new[] { "0" };
private FeatureCollection _features;
private OwinWebListener _server;
private Request _request;
private Response _response;
@ -44,18 +37,9 @@ namespace Microsoft.AspNet.Server.WebListener
_request = new Request(this, _memoryBlob);
_response = new Response(this);
_cts = new CancellationTokenSource();
_features = new FeatureCollection();
PopulateFeatures();
_request.ReleasePins();
}
internal IFeatureCollection Features
{
get { return _features; }
}
internal Request Request
{
get
@ -100,31 +84,6 @@ namespace Microsoft.AspNet.Server.WebListener
return Request.RequestId;
}
}
private void PopulateFeatures()
{
_features.Add(typeof(IHttpRequestInformation), Request);
_features.Add(typeof(IHttpConnection), Request);
if (Request.IsSecureConnection)
{
// TODO: Should this feature be conditional? Should we add this for HTTP requests?
_features.Add(typeof(IHttpTransportLayerSecurity), Request);
}
_features.Add(typeof(IHttpResponseInformation), Response);
_features.Add(typeof(IHttpSendFile), Response);
// TODO:
// _environment.CallCancelled = _cts.Token;
// _environment.User = _request.User;
// Opaque/WebSockets
// Channel binding
/*
// Server
_environment.Listener = _server;
_environment.ConnectionId = _request.ConnectionId;
*/
}
/*
public bool TryGetOpaqueUpgrade(ref Action<IDictionary<string, object>, OpaqueFunc> value)
{
@ -265,7 +224,7 @@ namespace Microsoft.AspNet.Server.WebListener
Request.SwitchToOpaqueMode();
Response.SwitchToOpaqueMode();
opaqueEnv[Constants.OpaqueStreamKey] = new OpaqueStream(Request.NativeStream, Response.NativeStream);
opaqueEnv[Constants.OpaqueStreamKey] = new OpaqueStream(Request.Body, Response.Body);
return opaqueEnv;
}

View File

@ -8,23 +8,19 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.HttpFeature;
namespace Microsoft.AspNet.Server.WebListener
{
internal sealed unsafe class Response : IHttpResponseInformation, IHttpSendFile, IDisposable
internal sealed unsafe class Response : IDisposable
{
private ResponseState _responseState;
private IDictionary<string, string[]> _headers;
private string _reasonPhrase;
private ResponseStream _nativeStream;
private Stream _responseStream;
private long _contentLength;
private BoundaryType _boundaryType;
private UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE _nativeResponse;
@ -79,17 +75,31 @@ namespace Microsoft.AspNet.Server.WebListener
{
throw new ArgumentOutOfRangeException("value", value, string.Format(Resources.Exception_InvalidStatusCode, value));
}
CheckResponseStarted();
_nativeResponse.StatusCode = (ushort)value;
}
}
private void CheckResponseStarted()
{
if (_responseState >= ResponseState.SentHeaders)
{
throw new InvalidOperationException("Headers already sent.");
}
}
public string ReasonPhrase
{
get { return _reasonPhrase; }
set { _reasonPhrase = value; }
set
{
// TODO: Validate user input for illegal chars, length limit, etc.?
CheckResponseStarted();
_reasonPhrase = value;
}
}
internal ResponseStream NativeStream
internal ResponseStream Body
{
get
{
@ -99,25 +109,8 @@ namespace Microsoft.AspNet.Server.WebListener
}
}
public Stream Body
{
get
{
if (_responseStream == null)
{
_responseStream = NativeStream;
}
return _responseStream;
}
set
{
_responseStream = value;
}
}
internal string GetReasonPhrase(int statusCode)
{
// TODO: Validate user input for illegal chars, length limit, etc.?
string reasonPhrase = ReasonPhrase;
if (string.IsNullOrWhiteSpace(reasonPhrase))
{

View File

@ -89,6 +89,7 @@ namespace Microsoft.AspNet.Server.WebListener
uint statusCode;
unsafe
{
// TODO: Don't add MoreData flag if content-length == 0?
flags |= UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
statusCode = _requestContext.Response.SendHeaders(null, null, flags, false);
}
@ -120,6 +121,7 @@ namespace Microsoft.AspNet.Server.WebListener
// TODO: Real cancellation
cancellationToken.ThrowIfCancellationRequested();
// TODO: Don't add MoreData flag if content-length == 0?
flags |= UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
ResponseStreamAsyncResult asyncResult = new ResponseStreamAsyncResult(this, null, null, null, 0, 0, _requestContext.Response.BoundaryType == BoundaryType.Chunked, false);