一、IFeatureCollection
表示Http特性的集合,该集合存放一些http相关特性,如IHttpRequestFeature、IHttpResponseFeature
当构建HttpContext、HttpRequest、HttpResponse对象时,会从这些特性里面解析构建对应的类
public interface IFeatureCollection : IEnumerableobject>> { /// /// Indicates if the collection can be modified. /// bool IsReadOnly { get; } /// /// Incremented for each modification and can be used to verify cached results. /// int Revision { get; } /// /// Gets or sets a given feature. Setting a null value removes the feature. /// /// /// The requested feature, or null if it is not present. object this[Type key] { get; set; } /// /// Retrieves the requested feature from the collection. /// /// The feature key. /// The requested feature, or null if it is not present. TFeature Get (); /// /// Sets the given feature in the collection. /// /// The feature key. /// The feature value. void Set (TFeature instance); }
public class FeatureCollection : IFeatureCollection { private static KeyComparer FeatureKeyComparer = new KeyComparer(); private readonly IFeatureCollection _defaults; private IDictionaryobject> _features; private volatile int _containerRevision; public FeatureCollection() { } public FeatureCollection(IFeatureCollection defaults) { _defaults = defaults; } public virtual int Revision { get { return _containerRevision + (_defaults?.Revision ?? 0); } } public bool IsReadOnly { get { return false; } } public object this[Type key] { get { if (key == null) { throw new ArgumentNullException(nameof(key)); } object result; return _features != null && _features.TryGetValue(key, out result) ? result : _defaults?[key]; } set { if (key == null) { throw new ArgumentNullException(nameof(key)); } if (value == null) { if (_features != null && _features.Remove(key)) { _containerRevision++; } return; } if (_features == null) { _features = new Dictionary object>(); } _features[key] = value; _containerRevision++; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public IEnumerator object>> GetEnumerator() { if (_features != null) { foreach (var pair in _features) { yield return pair; } } if (_defaults != null) { // Don't return features masked by the wrapper. foreach (var pair in _features == null ? _defaults : _defaults.Except(_features, FeatureKeyComparer)) { yield return pair; } } } public TFeature Get () { return (TFeature)this[typeof(TFeature)]; } public void Set (TFeature instance) { this[typeof(TFeature)] = instance; } private class KeyComparer : IEqualityComparer object>> { public bool Equals(KeyValuePair object> x, KeyValuePair object> y) { return x.Key.Equals(y.Key); } public int GetHashCode(KeyValuePair object> obj) { return obj.Key.GetHashCode(); } } }
HttpRequest
////// Represents the incoming side of an individual HTTP request. /// public abstract class HttpRequest { /// /// Gets the for this request. /// public abstract HttpContext HttpContext { get; } /// /// Gets or sets the HTTP method. /// /// The HTTP method. public abstract string Method { get; set; } /// /// Gets or sets the HTTP request scheme. /// /// The HTTP request scheme. public abstract string Scheme { get; set; } /// /// Returns true if the RequestScheme is https. /// /// true if this request is using https; otherwise, false. public abstract bool IsHttps { get; set; } /// /// Gets or sets the Host header. May include the port. /// /// The Host header. public abstract HostString Host { get; set; } /// /// Gets or sets the RequestPathBase. /// /// The RequestPathBase. public abstract PathString PathBase { get; set; } /// /// Gets or sets the request path from RequestPath. /// /// The request path from RequestPath. public abstract PathString Path { get; set; } /// /// Gets or sets the raw query string used to create the query collection in Request.Query. /// /// The raw query string. public abstract QueryString QueryString { get; set; } /// /// Gets the query value collection parsed from Request.QueryString. /// /// The query value collection parsed from Request.QueryString. public abstract IQueryCollection Query { get; set; } /// /// Gets or sets the request protocol (e.g. HTTP/1.1). /// /// The request protocol. public abstract string Protocol { get; set; } /// /// Gets the request headers. /// /// The request headers. public abstract IHeaderDictionary Headers { get; } /// /// Gets the collection of Cookies for this request. /// /// The collection of Cookies for this request. public abstract IRequestCookieCollection Cookies { get; set; } /// /// Gets or sets the Content-Length header. /// /// The value of the Content-Length header, if any. public abstract long? ContentLength { get; set; } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. public abstract string ContentType { get; set; } /// /// Gets or sets the request body . /// /// The request body . public abstract Stream Body { get; set; } /// /// Gets the request body . /// /// The request body . public virtual PipeReader BodyReader { get => throw new NotImplementedException(); } /// /// Checks the Content-Type header for form types. /// /// true if the Content-Type header represents a form content type; otherwise, false. public abstract bool HasFormContentType { get; } /// /// Gets or sets the request body as a form. /// public abstract IFormCollection Form { get; set; } /// /// Reads the request body if it is a form. /// /// public abstract Task ReadFormAsync(CancellationToken cancellationToken = new CancellationToken()); /// /// Gets the collection of route values for this request. /// /// The collection of route values for this request. public virtual RouteValueDictionary RouteValues { get; set; } }
HttpResponse
////// Represents the outgoing side of an individual HTTP request. /// public abstract class HttpResponse { private static readonly Func<object, Task> _callbackDelegate = callback => ((Func )callback)(); private static readonly Func<object, Task> _disposeDelegate = disposable => { ((IDisposable)disposable).Dispose(); return Task.CompletedTask; }; private static readonly Func<object, Task> _disposeAsyncDelegate = disposable => ((IAsyncDisposable)disposable).DisposeAsync().AsTask(); /// /// Gets the for this response. /// public abstract HttpContext HttpContext { get; } /// /// Gets or sets the HTTP response code. /// public abstract int StatusCode { get; set; } /// /// Gets the response headers. /// public abstract IHeaderDictionary Headers { get; } /// /// Gets or sets the response body . /// public abstract Stream Body { get; set; } /// /// Gets the response body /// /// The response body . public virtual PipeWriter BodyWriter { get => throw new NotImplementedException(); } /// /// Gets or sets the value for the Content-Length response header. /// public abstract long? ContentLength { get; set; } /// /// Gets or sets the value for the Content-Type response header. /// public abstract string ContentType { get; set; } /// /// Gets an object that can be used to manage cookies for this response. /// public abstract IResponseCookies Cookies { get; } /// /// Gets a value indicating whether response headers have been sent to the client. /// public abstract bool HasStarted { get; } /// /// Adds a delegate to be invoked just before response headers will be sent to the client. /// /// The delegate to execute. /// A state object to capture and pass back to the delegate. public abstract void OnStarting(Func<object, Task> callback, object state); /// /// Adds a delegate to be invoked just before response headers will be sent to the client. /// /// The delegate to execute. public virtual void OnStarting(Func callback) => OnStarting(_callbackDelegate, callback); /// /// Adds a delegate to be invoked after the response has finished being sent to the client. /// /// The delegate to invoke. /// A state object to capture and pass back to the delegate. public abstract void OnCompleted(Func<object, Task> callback, object state); /// /// Registers an object for disposal by the host once the request has finished processing. /// /// The object to be disposed. public virtual void RegisterForDispose(IDisposable disposable) => OnCompleted(_disposeDelegate, disposable); /// /// Registers an object for asynchronous disposal by the host once the request has finished processing. /// /// The object to be disposed asynchronously. public virtual void RegisterForDisposeAsync(IAsyncDisposable disposable) => OnCompleted(_disposeAsyncDelegate, disposable); /// /// Adds a delegate to be invoked after the response has finished being sent to the client. /// /// The delegate to invoke. public virtual void OnCompleted(Func callback) => OnCompleted(_callbackDelegate, callback); /// /// Returns a temporary redirect response (HTTP 302) to the client. /// /// The URL to redirect the client to. This must be properly encoded for use in http headers /// where only ASCII characters are allowed. public virtual void Redirect(string location) => Redirect(location, permanent: false); /// /// Returns a redirect response (HTTP 301 or HTTP 302) to the client. /// /// The URL to redirect the client to. This must be properly encoded for use in http headers /// where only ASCII characters are allowed. /// True if the redirect is permanent (301), otherwise false (302). public abstract void Redirect(string location, bool permanent); /// /// Starts the response by calling OnStarting() and making headers unmodifiable. /// /// public virtual Task StartAsync(CancellationToken cancellationToken = default) { throw new NotImplementedException(); } /// /// Flush any remaining response headers, data, or trailers. /// This may throw if the response is in an invalid state such as a Content-Length mismatch. /// /// public virtual Task CompleteAsync() { throw new NotImplementedException(); } }
HttpContext
////// Encapsulates all HTTP-specific information about an individual HTTP request. /// public abstract class HttpContext { /// /// Gets the collection of HTTP features provided by the server and middleware available on this request. /// public abstract IFeatureCollection Features { get; } /// /// Gets the object for this request. /// public abstract HttpRequest Request { get; } /// /// Gets the object for this request. /// public abstract HttpResponse Response { get; } /// /// Gets information about the underlying connection for this request. /// public abstract ConnectionInfo Connection { get; } /// /// Gets an object that manages the establishment of WebSocket connections for this request. /// public abstract WebSocketManager WebSockets { get; } /// /// Gets or sets the user for this request. /// public abstract ClaimsPrincipal User { get; set; } /// /// Gets or sets a key/value collection that can be used to share data within the scope of this request. /// public abstract IDictionary<object, object> Items { get; set; } /// /// Gets or sets the that provides access to the request's service container. /// public abstract IServiceProvider RequestServices { get; set; } /// /// Notifies when the connection underlying this request is aborted and thus request operations should be /// cancelled. /// public abstract CancellationToken RequestAborted { get; set; } /// /// Gets or sets a unique identifier to represent this request in trace logs. /// public abstract string TraceIdentifier { get; set; } /// /// Gets or sets the object used to manage user session data for this request. /// public abstract ISession Session { get; set; } /// /// Aborts the connection underlying this request. /// public abstract void Abort(); }
DefaultHttpContext
public sealed class DefaultHttpContext : HttpContext { // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624 private readonly static Func_newItemsFeature = f => new ItemsFeature(); private readonly static Func _newServiceProvidersFeature = context => new RequestServicesFeature(context, context.ServiceScopeFactory); private readonly static Func _newHttpAuthenticationFeature = f => new HttpAuthenticationFeature(); private readonly static Func _newHttpRequestLifetimeFeature = f => new HttpRequestLifetimeFeature(); private readonly static Func _newSessionFeature = f => new DefaultSessionFeature(); private readonly static Func _nullSessionFeature = f => null; private readonly static Func _newHttpRequestIdentifierFeature = f => new HttpRequestIdentifierFeature(); private FeatureReferences _features; private readonly DefaultHttpRequest _request; private readonly DefaultHttpResponse _response; private DefaultConnectionInfo _connection; private DefaultWebSocketManager _websockets; public DefaultHttpContext() : this(new FeatureCollection()) { Features.Set (new HttpRequestFeature()); Features.Set (new HttpResponseFeature()); Features.Set (new StreamResponseBodyFeature(Stream.Null)); } public DefaultHttpContext(IFeatureCollection features) { _features.Initalize(features); _request = new DefaultHttpRequest(this); _response = new DefaultHttpResponse(this); } public void Initialize(IFeatureCollection features) { var revision = features.Revision; _features.Initalize(features, revision); _request.Initialize(revision); _response.Initialize(revision); _connection?.Initialize(features, revision); _websockets?.Initialize(features, revision); } public void Uninitialize() { _features = default; _request.Uninitialize(); _response.Uninitialize(); _connection?.Uninitialize(); _websockets?.Uninitialize(); } public FormOptions FormOptions { get; set; } public IServiceScopeFactory ServiceScopeFactory { get; set; } private IItemsFeature ItemsFeature => _features.Fetch(ref _features.Cache.Items, _newItemsFeature); private IServiceProvidersFeature ServiceProvidersFeature => _features.Fetch(ref _features.Cache.ServiceProviders, this, _newServiceProvidersFeature); private IHttpAuthenticationFeature HttpAuthenticationFeature => _features.Fetch(ref _features.Cache.Authentication, _newHttpAuthenticationFeature); private IHttpRequestLifetimeFeature LifetimeFeature => _features.Fetch(ref _features.Cache.Lifetime, _newHttpRequestLifetimeFeature); private ISessionFeature SessionFeature => _features.Fetch(ref _features.Cache.Session, _newSessionFeature); private ISessionFeature SessionFeatureOrNull => _features.Fetch(ref _features.Cache.Session, _nullSessionFeature); private IHttpRequestIdentifierFeature RequestIdentifierFeature => _features.Fetch(ref _features.Cache.RequestIdentifier, _newHttpRequestIdentifierFeature); public override IFeatureCollection Features => _features.Collection ?? ContextDisposed(); public override HttpRequest Request => _request; public override HttpResponse Response => _response; public override ConnectionInfo Connection => _connection ?? (_connection = new DefaultConnectionInfo(_features.Collection)); public override WebSocketManager WebSockets => _websockets ?? (_websockets = new DefaultWebSocketManager(_features.Collection)); public override ClaimsPrincipal User { get { var user = HttpAuthenticationFeature.User; if (user == null) { user = new ClaimsPrincipal(new ClaimsIdentity()); HttpAuthenticationFeature.User = user; } return user; } set { HttpAuthenticationFeature.User = value; } } public override IDictionary<object, object> Items { get { return ItemsFeature.Items; } set { ItemsFeature.Items = value; } } public override IServiceProvider RequestServices { get { return ServiceProvidersFeature.RequestServices; } set { ServiceProvidersFeature.RequestServices = value; } } public override CancellationToken RequestAborted { get { return LifetimeFeature.RequestAborted; } set { LifetimeFeature.RequestAborted = value; } } public override string TraceIdentifier { get { return RequestIdentifierFeature.TraceIdentifier; } set { RequestIdentifierFeature.TraceIdentifier = value; } } public override ISession Session { get { var feature = SessionFeatureOrNull; if (feature == null) { throw new InvalidOperationException("Session has not been configured for this application " + "or request."); } return feature.Session; } set { SessionFeature.Session = value; } } // This property exists because of backwards compatibility. // We send an anonymous object with an HttpContext property // via DiagnosticListener in various events throughout the pipeline. Instead // we just send the HttpContext to avoid extra allocations [EditorBrowsable(EditorBrowsableState.Never)] public HttpContext HttpContext => this; public override void Abort() { LifetimeFeature.Abort(); } private static IFeatureCollection ContextDisposed() { ThrowContextDisposed(); return null; } private static void ThrowContextDisposed() { throw new ObjectDisposedException(nameof(HttpContext), $"Request has finished and {nameof(HttpContext)} disposed."); } struct FeatureInterfaces { public IItemsFeature Items; public IServiceProvidersFeature ServiceProviders; public IHttpAuthenticationFeature Authentication; public IHttpRequestLifetimeFeature Lifetime; public ISessionFeature Session; public IHttpRequestIdentifierFeature RequestIdentifier; } }
DefaultHttpRequest
internal sealed class DefaultHttpRequest : HttpRequest { private const string Http = "http"; private const string Https = "https"; // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624 private readonly static Func_nullRequestFeature = f => null; private readonly static Func _newQueryFeature = f => new QueryFeature(f); private readonly static Func _newFormFeature = r => new FormFeature(r, r._context.FormOptions ?? FormOptions.Default); private readonly static Func _newRequestCookiesFeature = f => new RequestCookiesFeature(f); private readonly static Func _newRouteValuesFeature = f => new RouteValuesFeature(); private readonly static Func _newRequestBodyPipeFeature = context => new RequestBodyPipeFeature(context); private readonly DefaultHttpContext _context; private FeatureReferences _features; public DefaultHttpRequest(DefaultHttpContext context) { _context = context; _features.Initalize(context.Features); } public void Initialize() { _features.Initalize(_context.Features); } public void Initialize(int revision) { _features.Initalize(_context.Features, revision); } public void Uninitialize() { _features = default; } public override HttpContext HttpContext => _context; private IHttpRequestFeature HttpRequestFeature => _features.Fetch(ref _features.Cache.Request, _nullRequestFeature); private IQueryFeature QueryFeature => _features.Fetch(ref _features.Cache.Query, _newQueryFeature); private IFormFeature FormFeature => _features.Fetch(ref _features.Cache.Form, this, _newFormFeature); private IRequestCookiesFeature RequestCookiesFeature => _features.Fetch(ref _features.Cache.Cookies, _newRequestCookiesFeature); private IRouteValuesFeature RouteValuesFeature => _features.Fetch(ref _features.Cache.RouteValues, _newRouteValuesFeature); private IRequestBodyPipeFeature RequestBodyPipeFeature => _features.Fetch(ref _features.Cache.BodyPipe, this.HttpContext, _newRequestBodyPipeFeature); public override PathString PathBase { get { return new PathString(HttpRequestFeature.PathBase); } set { HttpRequestFeature.PathBase = value.Value; } } public override PathString Path { get { return new PathString(HttpRequestFeature.Path); } set { HttpRequestFeature.Path = value.Value; } } public override QueryString QueryString { get { return new QueryString(HttpRequestFeature.QueryString); } set { HttpRequestFeature.QueryString = value.Value; } } public override long? ContentLength { get { return Headers.ContentLength; } set { Headers.ContentLength = value; } } public override Stream Body { get { return HttpRequestFeature.Body; } set { HttpRequestFeature.Body = value; } } public override string Method { get { return HttpRequestFeature.Method; } set { HttpRequestFeature.Method = value; } } public override string Scheme { get { return HttpRequestFeature.Scheme; } set { HttpRequestFeature.Scheme = value; } } public override bool IsHttps { get { return string.Equals(Https, Scheme, StringComparison.OrdinalIgnoreCase); } set { Scheme = value ? Https : Http; } } public override HostString Host { get { return HostString.FromUriComponent(Headers[HeaderNames.Host]); } set { Headers[HeaderNames.Host] = value.ToUriComponent(); } } public override IQueryCollection Query { get { return QueryFeature.Query; } set { QueryFeature.Query = value; } } public override string Protocol { get { return HttpRequestFeature.Protocol; } set { HttpRequestFeature.Protocol = value; } } public override IHeaderDictionary Headers { get { return HttpRequestFeature.Headers; } } public override IRequestCookieCollection Cookies { get { return RequestCookiesFeature.Cookies; } set { RequestCookiesFeature.Cookies = value; } } public override string ContentType { get { return Headers[HeaderNames.ContentType]; } set { Headers[HeaderNames.ContentType] = value; } } public override bool HasFormContentType { get { return FormFeature.HasFormContentType; } } public override IFormCollection Form { get { return FormFeature.ReadForm(); } set { FormFeature.Form = value; } } public override Task ReadFormAsync(CancellationToken cancellationToken) { return FormFeature.ReadFormAsync(cancellationToken); } public override RouteValueDictionary RouteValues { get { return RouteValuesFeature.RouteValues; } set { RouteValuesFeature.RouteValues = value; } } public override PipeReader BodyReader { get { return RequestBodyPipeFeature.Reader; } } struct FeatureInterfaces { public IHttpRequestFeature Request; public IQueryFeature Query; public IFormFeature Form; public IRequestCookiesFeature Cookies; public IRouteValuesFeature RouteValues; public IRequestBodyPipeFeature BodyPipe; } }
DefaultHttpResponse
internal sealed class DefaultHttpResponse : HttpResponse { // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624 private readonly static Func_nullResponseFeature = f => null; private readonly static Func _nullResponseBodyFeature = f => null; private readonly static Func _newResponseCookiesFeature = f => new ResponseCookiesFeature(f); private readonly DefaultHttpContext _context; private FeatureReferences _features; public DefaultHttpResponse(DefaultHttpContext context) { _context = context; _features.Initalize(context.Features); } public void Initialize() { _features.Initalize(_context.Features); } public void Initialize(int revision) { _features.Initalize(_context.Features, revision); } public void Uninitialize() { _features = default; } private IHttpResponseFeature HttpResponseFeature => _features.Fetch(ref _features.Cache.Response, _nullResponseFeature); private IHttpResponseBodyFeature HttpResponseBodyFeature => _features.Fetch(ref _features.Cache.ResponseBody, _nullResponseBodyFeature); private IResponseCookiesFeature ResponseCookiesFeature => _features.Fetch(ref _features.Cache.Cookies, _newResponseCookiesFeature); public override HttpContext HttpContext { get { return _context; } } public override int StatusCode { get { return HttpResponseFeature.StatusCode; } set { HttpResponseFeature.StatusCode = value; } } public override IHeaderDictionary Headers { get { return HttpResponseFeature.Headers; } } public override Stream Body { get { return HttpResponseBodyFeature.Stream; } set { var otherFeature = _features.Collection.Get (); if (otherFeature is StreamResponseBodyFeature streamFeature && streamFeature.PriorFeature != null && object.ReferenceEquals(value, streamFeature.PriorFeature.Stream)) { // They're reverting the stream back to the prior one. Revert the whole feature. _features.Collection.Set(streamFeature.PriorFeature); // CompleteAsync is registered with HttpResponse.OnCompleted and there's no way to unregister it. // Prevent it from running by marking as disposed. streamFeature.Dispose(); return; } var feature = new StreamResponseBodyFeature(value, otherFeature); OnCompleted(feature.CompleteAsync); _features.Collection.Set (feature); } } public override long? ContentLength { get { return Headers.ContentLength; } set { Headers.ContentLength = value; } } public override string ContentType { get { return Headers[HeaderNames.ContentType]; } set { if (string.IsNullOrEmpty(value)) { HttpResponseFeature.Headers.Remove(HeaderNames.ContentType); } else { HttpResponseFeature.Headers[HeaderNames.ContentType] = value; } } } public override IResponseCookies Cookies { get { return ResponseCookiesFeature.Cookies; } } public override bool HasStarted { get { return HttpResponseFeature.HasStarted; } } public override PipeWriter BodyWriter { get { return HttpResponseBodyFeature.Writer; } } public override void OnStarting(Func<object, Task> callback, object state) { if (callback == null) { throw new ArgumentNullException(nameof(callback)); } HttpResponseFeature.OnStarting(callback, state); } public override void OnCompleted(Func<object, Task> callback, object state) { if (callback == null) { throw new ArgumentNullException(nameof(callback)); } HttpResponseFeature.OnCompleted(callback, state); } public override void Redirect(string location, bool permanent) { if (permanent) { HttpResponseFeature.StatusCode = 301; } else { HttpResponseFeature.StatusCode = 302; } Headers[HeaderNames.Location] = location; } public override Task StartAsync(CancellationToken cancellationToken = default) { if (HasStarted) { return Task.CompletedTask; } return HttpResponseBodyFeature.StartAsync(cancellationToken); } public override Task CompleteAsync() => HttpResponseBodyFeature.CompleteAsync(); struct FeatureInterfaces { public IHttpResponseFeature Response; public IHttpResponseBodyFeature ResponseBody; public IResponseCookiesFeature Cookies; } }