asp.net core 3.1 源码学习 http相关
一、IFeatureCollection
表示Http特性的集合,该集合存放一些http相关特性,如IHttpRequestFeature、IHttpResponseFeature
当构建HttpContext、HttpRequest、HttpResponse对象时,会从这些特性里面解析构建对应的类
public interface IFeatureCollection : IEnumerable<KeyValuePair<Type, object>> { /// <summary> /// Indicates if the collection can be modified. /// </summary> bool IsReadOnly { get; } /// <summary> /// Incremented for each modification and can be used to verify cached results. /// </summary> int Revision { get; } /// <summary> /// Gets or sets a given feature. Setting a null value removes the feature. /// </summary> /// <param name="key"></param> /// <returns>The requested feature, or null if it is not present.</returns> object this[Type key] { get; set; } /// <summary> /// Retrieves the requested feature from the collection. /// </summary> /// <typeparam name="TFeature">The feature key.</typeparam> /// <returns>The requested feature, or null if it is not present.</returns> TFeature Get<TFeature>(); /// <summary> /// Sets the given feature in the collection. /// </summary> /// <typeparam name="TFeature">The feature key.</typeparam> /// <param name="instance">The feature value.</param> void Set<TFeature>(TFeature instance); }
public class FeatureCollection : IFeatureCollection { private static KeyComparer FeatureKeyComparer = new KeyComparer(); private readonly IFeatureCollection _defaults; private IDictionary<Type, object> _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<Type, object>(); } _features[key] = value; _containerRevision++; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public IEnumerator<KeyValuePair<Type, 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<TFeature>() { return (TFeature)this[typeof(TFeature)]; } public void Set<TFeature>(TFeature instance) { this[typeof(TFeature)] = instance; } private class KeyComparer : IEqualityComparer<KeyValuePair<Type, object>> { public bool Equals(KeyValuePair<Type, object> x, KeyValuePair<Type, object> y) { return x.Key.Equals(y.Key); } public int GetHashCode(KeyValuePair<Type, object> obj) { return obj.Key.GetHashCode(); } } }
HttpRequest
/// <summary> /// Represents the incoming side of an individual HTTP request. /// </summary> public abstract class HttpRequest { /// <summary> /// Gets the <see cref="HttpContext"/> for this request. /// </summary> public abstract HttpContext HttpContext { get; } /// <summary> /// Gets or sets the HTTP method. /// </summary> /// <returns>The HTTP method.</returns> public abstract string Method { get; set; } /// <summary> /// Gets or sets the HTTP request scheme. /// </summary> /// <returns>The HTTP request scheme.</returns> public abstract string Scheme { get; set; } /// <summary> /// Returns true if the RequestScheme is https. /// </summary> /// <returns>true if this request is using https; otherwise, false.</returns> public abstract bool IsHttps { get; set; } /// <summary> /// Gets or sets the Host header. May include the port. /// </summary> /// <return>The Host header.</return> public abstract HostString Host { get; set; } /// <summary> /// Gets or sets the RequestPathBase. /// </summary> /// <returns>The RequestPathBase.</returns> public abstract PathString PathBase { get; set; } /// <summary> /// Gets or sets the request path from RequestPath. /// </summary> /// <returns>The request path from RequestPath.</returns> public abstract PathString Path { get; set; } /// <summary> /// Gets or sets the raw query string used to create the query collection in Request.Query. /// </summary> /// <returns>The raw query string.</returns> public abstract QueryString QueryString { get; set; } /// <summary> /// Gets the query value collection parsed from Request.QueryString. /// </summary> /// <returns>The query value collection parsed from Request.QueryString.</returns> public abstract IQueryCollection Query { get; set; } /// <summary> /// Gets or sets the request protocol (e.g. HTTP/1.1). /// </summary> /// <returns>The request protocol.</returns> public abstract string Protocol { get; set; } /// <summary> /// Gets the request headers. /// </summary> /// <returns>The request headers.</returns> public abstract IHeaderDictionary Headers { get; } /// <summary> /// Gets the collection of Cookies for this request. /// </summary> /// <returns>The collection of Cookies for this request.</returns> public abstract IRequestCookieCollection Cookies { get; set; } /// <summary> /// Gets or sets the Content-Length header. /// </summary> /// <returns>The value of the Content-Length header, if any.</returns> public abstract long? ContentLength { get; set; } /// <summary> /// Gets or sets the Content-Type header. /// </summary> /// <returns>The Content-Type header.</returns> public abstract string ContentType { get; set; } /// <summary> /// Gets or sets the request body <see cref="Stream"/>. /// </summary> /// <value>The request body <see cref="Stream"/>.</value> public abstract Stream Body { get; set; } /// <summary> /// Gets the request body <see cref="PipeReader"/>. /// </summary> /// <value>The request body <see cref="PipeReader"/>.</value> public virtual PipeReader BodyReader { get => throw new NotImplementedException(); } /// <summary> /// Checks the Content-Type header for form types. /// </summary> /// <returns>true if the Content-Type header represents a form content type; otherwise, false.</returns> public abstract bool HasFormContentType { get; } /// <summary> /// Gets or sets the request body as a form. /// </summary> public abstract IFormCollection Form { get; set; } /// <summary> /// Reads the request body if it is a form. /// </summary> /// <returns></returns> public abstract Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken = new CancellationToken()); /// <summary> /// Gets the collection of route values for this request. /// </summary> /// <returns>The collection of route values for this request.</returns> public virtual RouteValueDictionary RouteValues { get; set; } }
HttpResponse
/// <summary> /// Represents the outgoing side of an individual HTTP request. /// </summary> public abstract class HttpResponse { private static readonly Func<object, Task> _callbackDelegate = callback => ((Func<Task>)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(); /// <summary> /// Gets the <see cref="HttpContext"/> for this response. /// </summary> public abstract HttpContext HttpContext { get; } /// <summary> /// Gets or sets the HTTP response code. /// </summary> public abstract int StatusCode { get; set; } /// <summary> /// Gets the response headers. /// </summary> public abstract IHeaderDictionary Headers { get; } /// <summary> /// Gets or sets the response body <see cref="Stream"/>. /// </summary> public abstract Stream Body { get; set; } /// <summary> /// Gets the response body <see cref="PipeWriter"/> /// </summary> /// <value>The response body <see cref="PipeWriter"/>.</value> public virtual PipeWriter BodyWriter { get => throw new NotImplementedException(); } /// <summary> /// Gets or sets the value for the <c>Content-Length</c> response header. /// </summary> public abstract long? ContentLength { get; set; } /// <summary> /// Gets or sets the value for the <c>Content-Type</c> response header. /// </summary> public abstract string ContentType { get; set; } /// <summary> /// Gets an object that can be used to manage cookies for this response. /// </summary> public abstract IResponseCookies Cookies { get; } /// <summary> /// Gets a value indicating whether response headers have been sent to the client. /// </summary> public abstract bool HasStarted { get; } /// <summary> /// Adds a delegate to be invoked just before response headers will be sent to the client. /// </summary> /// <param name="callback">The delegate to execute.</param> /// <param name="state">A state object to capture and pass back to the delegate.</param> public abstract void OnStarting(Func<object, Task> callback, object state); /// <summary> /// Adds a delegate to be invoked just before response headers will be sent to the client. /// </summary> /// <param name="callback">The delegate to execute.</param> public virtual void OnStarting(Func<Task> callback) => OnStarting(_callbackDelegate, callback); /// <summary> /// Adds a delegate to be invoked after the response has finished being sent to the client. /// </summary> /// <param name="callback">The delegate to invoke.</param> /// <param name="state">A state object to capture and pass back to the delegate.</param> public abstract void OnCompleted(Func<object, Task> callback, object state); /// <summary> /// Registers an object for disposal by the host once the request has finished processing. /// </summary> /// <param name="disposable">The object to be disposed.</param> public virtual void RegisterForDispose(IDisposable disposable) => OnCompleted(_disposeDelegate, disposable); /// <summary> /// Registers an object for asynchronous disposal by the host once the request has finished processing. /// </summary> /// <param name="disposable">The object to be disposed asynchronously.</param> public virtual void RegisterForDisposeAsync(IAsyncDisposable disposable) => OnCompleted(_disposeAsyncDelegate, disposable); /// <summary> /// Adds a delegate to be invoked after the response has finished being sent to the client. /// </summary> /// <param name="callback">The delegate to invoke.</param> public virtual void OnCompleted(Func<Task> callback) => OnCompleted(_callbackDelegate, callback); /// <summary> /// Returns a temporary redirect response (HTTP 302) to the client. /// </summary> /// <param name="location">The URL to redirect the client to. This must be properly encoded for use in http headers /// where only ASCII characters are allowed.</param> public virtual void Redirect(string location) => Redirect(location, permanent: false); /// <summary> /// Returns a redirect response (HTTP 301 or HTTP 302) to the client. /// </summary> /// <param name="location">The URL to redirect the client to. This must be properly encoded for use in http headers /// where only ASCII characters are allowed.</param> /// <param name="permanent"><c>True</c> if the redirect is permanent (301), otherwise <c>false</c> (302).</param> public abstract void Redirect(string location, bool permanent); /// <summary> /// Starts the response by calling OnStarting() and making headers unmodifiable. /// </summary> /// <param name="cancellationToken"></param> public virtual Task StartAsync(CancellationToken cancellationToken = default) { throw new NotImplementedException(); } /// <summary> /// 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. /// </summary> /// <returns></returns> public virtual Task CompleteAsync() { throw new NotImplementedException(); } }
HttpContext
/// <summary> /// Encapsulates all HTTP-specific information about an individual HTTP request. /// </summary> public abstract class HttpContext { /// <summary> /// Gets the collection of HTTP features provided by the server and middleware available on this request. /// </summary> public abstract IFeatureCollection Features { get; } /// <summary> /// Gets the <see cref="HttpRequest"/> object for this request. /// </summary> public abstract HttpRequest Request { get; } /// <summary> /// Gets the <see cref="HttpResponse"/> object for this request. /// </summary> public abstract HttpResponse Response { get; } /// <summary> /// Gets information about the underlying connection for this request. /// </summary> public abstract ConnectionInfo Connection { get; } /// <summary> /// Gets an object that manages the establishment of WebSocket connections for this request. /// </summary> public abstract WebSocketManager WebSockets { get; } /// <summary> /// Gets or sets the user for this request. /// </summary> public abstract ClaimsPrincipal User { get; set; } /// <summary> /// Gets or sets a key/value collection that can be used to share data within the scope of this request. /// </summary> public abstract IDictionary<object, object> Items { get; set; } /// <summary> /// Gets or sets the <see cref="IServiceProvider"/> that provides access to the request's service container. /// </summary> public abstract IServiceProvider RequestServices { get; set; } /// <summary> /// Notifies when the connection underlying this request is aborted and thus request operations should be /// cancelled. /// </summary> public abstract CancellationToken RequestAborted { get; set; } /// <summary> /// Gets or sets a unique identifier to represent this request in trace logs. /// </summary> public abstract string TraceIdentifier { get; set; } /// <summary> /// Gets or sets the object used to manage user session data for this request. /// </summary> public abstract ISession Session { get; set; } /// <summary> /// Aborts the connection underlying this request. /// </summary> 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<IFeatureCollection, IItemsFeature> _newItemsFeature = f => new ItemsFeature(); private readonly static Func<DefaultHttpContext, IServiceProvidersFeature> _newServiceProvidersFeature = context => new RequestServicesFeature(context, context.ServiceScopeFactory); private readonly static Func<IFeatureCollection, IHttpAuthenticationFeature> _newHttpAuthenticationFeature = f => new HttpAuthenticationFeature(); private readonly static Func<IFeatureCollection, IHttpRequestLifetimeFeature> _newHttpRequestLifetimeFeature = f => new HttpRequestLifetimeFeature(); private readonly static Func<IFeatureCollection, ISessionFeature> _newSessionFeature = f => new DefaultSessionFeature(); private readonly static Func<IFeatureCollection, ISessionFeature> _nullSessionFeature = f => null; private readonly static Func<IFeatureCollection, IHttpRequestIdentifierFeature> _newHttpRequestIdentifierFeature = f => new HttpRequestIdentifierFeature(); private FeatureReferences<FeatureInterfaces> _features; private readonly DefaultHttpRequest _request; private readonly DefaultHttpResponse _response; private DefaultConnectionInfo _connection; private DefaultWebSocketManager _websockets; public DefaultHttpContext() : this(new FeatureCollection()) { Features.Set<IHttpRequestFeature>(new HttpRequestFeature()); Features.Set<IHttpResponseFeature>(new HttpResponseFeature()); Features.Set<IHttpResponseBodyFeature>(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<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null; private readonly static Func<IFeatureCollection, IQueryFeature> _newQueryFeature = f => new QueryFeature(f); private readonly static Func<DefaultHttpRequest, IFormFeature> _newFormFeature = r => new FormFeature(r, r._context.FormOptions ?? FormOptions.Default); private readonly static Func<IFeatureCollection, IRequestCookiesFeature> _newRequestCookiesFeature = f => new RequestCookiesFeature(f); private readonly static Func<IFeatureCollection, IRouteValuesFeature> _newRouteValuesFeature = f => new RouteValuesFeature(); private readonly static Func<HttpContext, IRequestBodyPipeFeature> _newRequestBodyPipeFeature = context => new RequestBodyPipeFeature(context); private readonly DefaultHttpContext _context; private FeatureReferences<FeatureInterfaces> _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<IFormCollection> 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<IFeatureCollection, IHttpResponseFeature> _nullResponseFeature = f => null; private readonly static Func<IFeatureCollection, IHttpResponseBodyFeature> _nullResponseBodyFeature = f => null; private readonly static Func<IFeatureCollection, IResponseCookiesFeature> _newResponseCookiesFeature = f => new ResponseCookiesFeature(f); private readonly DefaultHttpContext _context; private FeatureReferences<FeatureInterfaces> _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<IHttpResponseBodyFeature>(); 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<IHttpResponseBodyFeature>(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; } }