8、Server 第五个对象
服务器在管道中的职责非常明确,当我们启动应用宿主的WebHost的时候,服务它被自动启动。启动后的服务器会绑定到指定的端口进行请求监听,一旦有请求抵达,服务器会根据该请求创建出代表上下文的HttpContext对象,并将该上下文作为输入调用由所有注册中间件构建而成的RequestDelegate对象。
简单起见,我们使用如下这个简写的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类型,但是却可以注册不同类型的服务器,我们必需解决两者之间的适配问题。
计算机领域有一句非常经典的话:“任何问题都可以通过添加一个抽象层的方式来解决,如果解决不了,那就再加一层”。同一个HttpContext类型与不同服务器类型之间的适配问题也可可以通过添加一个抽象层来解决,我们定义在该层的对象称为Feature。如上图所示,我们可以定义一系列的Feature接口来为HttpContext提供上下文信息,其中最重要的就是提供请求的IRequestFeature和完成响应的IResponseFeature接口。那么具体的服务器只需要实现这些Feature接口就可以了。
我们接着从代码层面来看看具体的实现。如下面的代码片段所示,我们定义了一个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; } } }
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对象即可。