ASP.NET Core Framework deep learning (three) Server Objects

8, Server fifth objects

  Server duties in the pipeline is very clear, when we start WebHost host of applications, and services that it is started automatically. After starting the server will bind to the port specified request listening , if there is a request arrived, the server creates a response to the request on behalf of the HttpContext object context , and the context of the calling built of all registered middleware as input RequestDelegate objects .

 

  For simplicity, we use the following abbreviations are IServer this interface represents the server. The only way to StartAsync we start by defining IServer server interface, as a handler parameter is the co-built by all registered middleware objects made RequestDelegate

public interface IServer
{ 
    Task StartAsync(RequestDelegate handler);
}

public class HttpListenerServer : IServer
{
    private readonly HttpListener _httpListener;// System.Net.HttpListener
    private readonly string[] _urls;
    public HttpListenerServer(params string[] urls)
    {
        _httpListener = new HttpListener();
        _urls = urls.Any() ? urls : new string[] { "http://localhost:5000/" };
    }

    public async Task StartAsync(RequestDelegate handler)
    {
        Array.ForEach(_urls, url => _httpListener.Prefixes.Add(url));
        _httpListener.Start();
        Console.WriteLine("Server started and is listening on: {0}", string.Join(';', _urls));
        while (true)
        {
            var listenerContext = await _httpListener.GetContextAsync();
            var feature = new HttpListenerFeature(listenerContext);
            var features = new FeatureCollection()
                .Set<IHttpRequestFeature>(feature)
                .Set<IHttpResponseFeature>(feature);
            var httpContext = new HttpContext(features);
            await handler(httpContext);
            listenerContext.Response.Close();
        }
    }
}

 

9, and adaptation between HttpContext Server

  HttpContext object oriented encapsulation of the application layer is a request and response, but originally requested from the server , in response to operation for any HttpContext also necessary to act on the current server really works. Now the question is, all of the applications that use ASP.NET Core HttpContext are the same type, but you can register different types of servers, we need to address adaptation issues between the two.

 

  Computer field have a very classic: "Any problems can be solved by adding a layer of abstraction of the way, if not solve, then add a layer." HttpContext same adaptation problems between different types of server types can also be solved by adding a layer of abstraction, we define objects in the layer called the Feature . As shown above, we can define a set of Feature interface to provide information to the HttpContext context, the most important of which is to provide and request completion response of IResponseFeature IRequestFeature interfaces. Less specific server only needs to implement these interfaces Feature on it .

 

  We went from the code level to look at the implementation. The following code fragment, we define a IFeatureCollection Feature interface represents a collection of storage objects . As can be seen from the definition This is a Type and Object Dictionary as Key and Value, and Key Feature representative of the type used in registration , and on behalf of Feature Value natural object itself , then saying that we provide final Feature object is to correspond Feature type (usually interface type) to register. In order to facilitate the programming, we define two extension method Set <T> and Get <T> to set and get the Feature object.

public interface IFeatureCollection : IDictionary<Type, object> { }
public class FeatureCollection : Dictionary<Type, object>, IFeatureCollection { }   
public static partial class Extensions
{
    public static T Get<T>(this IFeatureCollection features) => features.TryGetValue(typeof(T), out var value) ? (T)value : default(T);
    public static IFeatureCollection Set<T>(this IFeatureCollection features, T feature)
    { 
        features[typeof(T)] = feature;
        return features;
    }
}

Is used below and provide the request and response IHttpRequestFeature IHttpResponseFeature interface definition, it can be seen that they have exactly the same and with HttpRequest HttpResponse member definitions .

public interface IHttpRequestFeature
{
    Uri                     Url { get; }
    NameValueCollection     Headers { get; }
    Stream                  Body { get; }
}    
public interface IHttpResponseFeature
{
    int                       StatusCode { get; set; }
    NameValueCollection     Headers { get; }
    Stream                  Body { get; }
}

  Next we look at the specific realization of HttpContext. ASP.NET Core Mini HttpContext and contains only attributes Request and Response two members, respectively, corresponding to the type and the HttpResponse HttpRequest, particularly to achieve this is shown in two types as follows. We can see HttpRequest and HttpResponse are constructed from a IFeatureCollection objects by their attributes corresponding members have respectively IHttpRequestFeature Feature and IHttpResponseFeature objects in this set is provided by containing.

public class HttpRequest
{
    private readonly IHttpRequestFeature _feature;    
      
    public  Uri Url => _feature.Url;
    public  NameValueCollection Headers => _feature.Headers;
    public  Stream Body => _feature.Body;

    public HttpRequest(IFeatureCollection features) => _feature = features.Get<IHttpRequestFeature>();
}
public class HttpResponse
{
    private readonly IHttpResponseFeature _feature;

    public  NameValueCollection Headers => _feature.Headers;
    public  Stream Body => _feature.Body;
    public int StatusCode { get => _feature.StatusCode; set => _feature.StatusCode = value; }

    public HttpResponse(IFeatureCollection features) => _feature = features.Get<IHttpResponseFeature>();

}

  HttpContext implementation more simple. The following code fragment, we create a HttpContext object is to provide a IFeatureCollection same object, we use the object and create the corresponding HttpRequest HttpResponse object, and as the corresponding property value.

public class HttpContext
{           
    public  HttpRequest Request { get; }
    public  HttpResponse Response { get; }

    public HttpContext(IFeatureCollection features)
    {
        Request = new HttpRequest(features);
        Response = new HttpResponse(features);
    }
}

IfeatureCollection defined in the source and there was Microsoft.AspNetCore.Http.Features AspNetCore-2.2.4lib the http namespace

//
// 摘要:
//     Represents a collection of HTTP features.
[DefaultMember("Item")]
public interface IFeatureCollection : IEnumerable<KeyValuePair<Type, object>>, IEnumerable
{
    //
    // 摘要:
    //     Gets or sets a given feature. Setting a null value removes the feature.
    //
    // 参数:
    //   key:
    //
    // 返回结果:
    //     The requested feature, or null if it is not present.
    object this[Type key] { get; set; }

    //
    // 摘要:
    //     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; }

    //
    // 摘要:
    //     Retrieves the requested feature from the collection.
    // 类型参数:
    //   TFeature:
    //     The feature key.
    // 返回结果:
    //     The requested feature, or null if it is not present.
    TFeature Get<TFeature>();


    // 摘要:
    //     Sets the given feature in the collection.
    // 参数:
    //   instance:
    //     The feature value.
    // 类型参数:
    //   TFeature:
    //     The feature key.
    void Set<TFeature>(TFeature instance);
}

namespace Microsoft.AspNetCore.Http.Features
{
    /// <summary>
    /// Contains the details of a given request. These properties should all be mutable.
    /// None of these properties should ever be set to null.
    /// </summary>
    public interface IHttpRequestFeature
    {
        /// <summary>
        /// The HTTP-version as defined in RFC 7230. E.g. "HTTP/1.1"
        /// </summary>
        string Protocol { get; set; }

        /// <summary>
        /// The request uri scheme. E.g. "http" or "https". Note this value is not included
        /// in the original request, it is inferred by checking if the transport used a TLS
        /// connection or not.
        /// </summary>
        string Scheme { get; set; }

        /// <summary>
        /// The request method as defined in RFC 7230. E.g. "GET", "HEAD", "POST", etc..
        /// </summary>
        string Method { get; set; }

        /// <summary>
        /// The first portion of the request path associated with application root. The value
        /// is un-escaped. The value may be string.Empty.
        /// </summary>
        string PathBase { get; set; }

        /// <summary>
        /// The portion of the request path that identifies the requested resource. The value
        /// is un-escaped. The value may be string.Empty if <see cref="PathBase"/> contains the
        /// full path.
        /// </summary>
        string Path { get; set; }

        /// <summary>
        /// The query portion of the request-target as defined in RFC 7230. The value
        /// may be string.Empty. If not empty then the leading '?' will be included. The value
        /// is in its original form, without un-escaping.
        /// </summary>
        string QueryString { get; set; }

        /// <summary>
        /// The request target as it was sent in the HTTP request. This property contains the
        /// raw path and full query, as well as other request targets such as * for OPTIONS
        /// requests (https://tools.ietf.org/html/rfc7230#section-5.3).
        /// </summary>
        /// <remarks>
        /// This property is not used internally for routing or authorization decisions. It has not
        /// been UrlDecoded and care should be taken in its use.
        /// </remarks>
        string RawTarget { get; set; }

        /// <summary>
        /// Headers included in the request, aggregated by header name. The values are not split
        /// or merged across header lines. E.g. The following headers:
        /// HeaderA: value1, value2
        /// HeaderA: value3
        /// Result in Headers["HeaderA"] = { "value1, value2", "value3" }
        /// </summary>
        IHeaderDictionary Headers { get; set; }

        /// <summary>
        /// A <see cref="Stream"/> representing the request body, if any. Stream.Null may be used
        /// to represent an empty request body.
        /// </summary>
        Stream Body { get; set; }
    }
}
View Code

10、HttpListenerServer

  在对服务器和它与HttpContext的适配原理具有清晰的认识之后,我们来尝试着自己定义一个服务器。在前面的Hello World实例中,我们利用WebHostBuilder的扩展方法UseHttpListener注册了一个HttpListenerServer,我们现在就来看看这个采用HttpListener作为监听器的服务器类型是如何实现的。

  由于所有的服务器都需要自己的Feature实现来为HttpContext提供对应的上下文信息,所以我们得先来为HttpListenerServer定义相应的接口。对HttpListener稍微了解的朋友应该知道它在接收到请求之后同行会创建一个自己的上下文对象,对应的类型为HttpListenerContext。如果采用HttpListenerServer作为应用的服务器,意味着HttpContext承载的上下文信息最初来源于这个HttpListenerContext所以Feature的目的旨在解决这两个上下文之间的适配问题。

 

  如下所示的HttpListenerFeature就是我们为HttpListenerServer定义的Feature。HttpListenerFeature同时实现了IHttpRequestFeature和IHttpResponseFeature,实现的6个属性成员最初都来源于创建该Feature对象提供的HttpListenerContext对象。

public class HttpListenerFeature : IHttpRequestFeature, IHttpResponseFeature
{
    private readonly HttpListenerContext _context;
    public HttpListenerFeature(HttpListenerContext context) => _context = context;

Uri IHttpRequestFeature.Url => _context.Request.Url;

    NameValueCollection IHttpRequestFeature.Headers => _context.Request.Headers;
NameValueCollection IHttpResponseFeature.Headers => _context.Response.Headers;

    Stream IHttpRequestFeature.Body => _context.Request.InputStream;
Stream IHttpResponseFeature.Body => _context.Response.OutputStream;

    int IHttpResponseFeature.StatusCode { get => _context.Response.StatusCode; set => _context.Response.StatusCode = value; }
}

  如下所示的是HttpListenerServer的最终定义。我们在构造一个HttpListenerServer对象的时候可以提供一组监听地址,如果没有提供,会采用“localhost:5000”作为默认的监听地址。在实现的StartAsync方法中,我们启动了在构造函数中创建的HttpListenerServer对象,并在一个循环中通过调用其GetContextAsync方法实现了针对请求的监听和接收。

public class HttpListenerServer : IServer
{
    private readonly HttpListener     _httpListener;
    private readonly string[]             _urls;

    public HttpListenerServer(params string[] urls)
    {
        _httpListener = new HttpListener();
        _urls = urls.Any()?urls: new string[] { "http://localhost:5000/"};
    }

    public async Task StartAsync(RequestDelegate handler)
    {
        Array.ForEach(_urls, url => _httpListener.Prefixes.Add(url));    
        _httpListener.Start();
        while (true)
        {
            var listenerContext = await _httpListener.GetContextAsync(); 
            var feature = new HttpListenerFeature(listenerContext);
            var features = new FeatureCollection()
                .Set<IHttpRequestFeature>(feature)
                .Set<IHttpResponseFeature>(feature);
            var httpContext = new HttpContext(features);
            await handler(httpContext);
            listenerContext.Response.Close();
        }
    }
}

  当HttpListener监听到抵达的请求后,我们会得到一个HttpListenerContext对象,此时我们只需要据此创建一个HttpListenerFeature对象并它分别以IHttpRequestFeature和IHttpResponseFeature接口类型注册到创建FeatureCollection集合上。我们最终利用这个FeatureCollection对象创建出代表上下文的HttpContext,然后将它作为参数调用由所有中间件共同构建的RequestDelegate对象即可。

 

Guess you like

Origin www.cnblogs.com/qixinbo/p/12046632.html