8、Server   第五个对象

  服务器在管道中的职责非常明确,当我们启动应用宿主的WebHost的时候,服务它被自动启动。启动后的服务器会绑定到指定的端口进行请求监听,一旦有请求抵达,服务器会根据该请求创建出代表上下文的HttpContext对象并将该上下文作为输入调用由所有注册中间件构建而成的RequestDelegate对象

 ASP.NET Core框架深度学习(三) Server对象

  简单起见,我们使用如下这个简写的IServer接口来表示服务器。我们通过定义在IServer接口的唯一方法StartAsync启动服务器,作为参数的handler正是由所有注册中间件共同构建而成的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、HttpContext和Server之间的适配

  面向应用层的HttpContext对象是对请求和响应的封装,但是请求最初来源于服务器,针对HttpContext的任何响应操作也必需作用于当前的服务器才能真正起作用。现在问题来了,所有的ASP.NET Core应用使用的都是同一个HttpContext类型,但是却可以注册不同类型的服务器,我们必需解决两者之间的适配问题。

 ASP.NET Core框架深度学习(三) Server对象

  计算机领域有一句非常经典的话:“任何问题都可以通过添加一个抽象层的方式来解决,如果解决不了,那就再加一层”。同一个HttpContext类型与不同服务器类型之间的适配问题也可可以通过添加一个抽象层来解决,我们定义在该层的对象称为Feature。如上图所示,我们可以定义一系列的Feature接口来为HttpContext提供上下文信息,其中最重要的就是提供请求的IRequestFeature和完成响应的IResponseFeature接口。那么具体的服务器只需要实现这些Feature接口就可以了

 ASP.NET Core框架深度学习(三) Server对象

  我们接着从代码层面来看看具体的实现。如下面的代码片段所示,我们定义了一个IFeatureCollection接口来表示存放Feature对象的集合。从定义可以看出这是一个以Type和Object作为Key和Value的字典,Key代表注册Feature所采用的类型而Value自然就代表Feature对象本身,话句话说我们提供的Feature对象最终是以对应Feature类型(一般为接口类型)进行注册的。为了编程上便利,我们定义了两个扩展方法Set<T>和Get<T>来设置和获取Feature对象。

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;
    }
}

如下所示的用来提供请求和响应IHttpRequestFeature和IHttpResponseFeature接口的定义,可以看出它们具有与HttpRequest和HttpResponse完全一致的成员定义

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; }
}

  接下来我们来看看HttpContext的具体实现。ASP.NET Core Mini的HttpContext只包含Request和Response两个属性成员,对应的类型分别为HttpRequest和HttpResponse,如下所示的就是这两个类型的具体实现。我们可以看出HttpRequest和HttpResponse都是通过一个IFeatureCollection对象构建而成的,它们对应的属性成员均有分别由包含在这个Feature集合中的IHttpRequestFeature和IHttpResponseFeature对象来提供的。

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的实现就更加简单了。如下面的代码片段所示,我们在创建一个HttpContext对象是同样会提供一个IFeatureCollection对象,我们利用该对象创建对应的HttpRequest和HttpResponse对象,并作为对应的属性值。

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

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

IfeatureCollection在源码里面也有定义   Microsoft.AspNetCore.Http.Features  AspNetCore-2.2.4lib的http命名空间

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

相关文章:

  • 2021-07-06
  • 2021-11-17
  • 2021-08-25
  • 2021-11-11
  • 2019-08-12
猜你喜欢
  • 2022-01-06
  • 2021-10-01
  • 2021-07-02
  • 2022-01-15
  • 2021-08-28
  • 2021-05-06
  • 2021-08-22
相关资源
相似解决方案