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对象即可。

ASP.NET Core框架深度学习(三) Server对象的更多相关文章

  1. ASP.NET Core框架深度学习(四)宿主对象

    11.WebHost  第六个对象 到目前为止我们已经知道了由一个服务器和多个中间件构成的管道是如何完整针对请求的监听.接收.处理和最终响应的,接下来来讨论这样的管道是如何被构建出来的.管道是在作为应 ...

  2. ASP.NET Core框架深度学习(二) 管道对象

    4.HttpContext 第一个对象 我们的ASP.NET Core Mini由7个核心对象构建而成.第一个就是大家非常熟悉的HttpContext对象,它可以说是ASP.NET Core应用开发中 ...

  3. ASP.NET Core框架深度学习(一) Hello World

    对于学习Core的框架,对我帮助最大的一篇文章是Artech的<200行代码,7个对象——让你了解ASP.NET Core框架的本质>,最近我又重新阅读了一遍该文.本系列文章就是结合我的阅 ...

  4. ASP.NET Core 框架本质学习

    本文作为学习过程中的一个记录. 学习文章地址: https://www.cnblogs.com/artech/p/inside-asp-net-core-framework.html 一. ASP.N ...

  5. 一个Mini的ASP.NET Core框架的实现

    一.ASP.NET Core Mini 在2019年1月的微软技术(苏州)俱乐部成立大会上,蒋金楠老师(大内老A)分享了一个名为“ASP.NET Core框架揭秘”的课程,他用不到200行的代码实现了 ...

  6. 200行代码,7个对象——让你了解ASP.NET Core框架的本质

    2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘>的分享.在此次分享中,我按照ASP.NET Core自身的运行原理和设计 ...

  7. 了解ASP.NET Core框架的本质

    了解ASP.NET Core框架的本质 ASP.NET Core自身的运行原理和设计思想创建了一个 “迷你版” 的ASP.NET Core框架,并且利用这个 “极简” 的模拟框架阐述了ASP.NET ...

  8. 200行代码,7个对象——让你了解ASP.NET Core框架的本质

    原文:200行代码,7个对象--让你了解ASP.NET Core框架的本质 2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘&g ...

  9. ASP.NET Core框架的本质

    源文章地址:http://www.cnblogs.com/artech/p/inside-asp-net-core-framework.html 1.从Hello World谈起 当我们最开始学习一门 ...

随机推荐

  1. 使用Castle DynamicProxy (AOP)

    在本文中,我将引导您了解.NET环境中的面向方面编程(AOP)概念,以及如何使用Castle DynamicProxy创建和附加方面.在我们开始之前,让我快速介绍AOP和  IoC.如果您已经熟悉这些 ...

  2. java 单链表 练习

    练习一下java单链表的简单习题 package com.test1; import java.util.Stack; public class SingleListDemo { /** * 返回单链 ...

  3. Python 你见过三行代码的爬虫吗

    ------------恢复内容开始------------ 每次讲爬虫的时候都会从“发送请求” 开始讲,讲到解析页面的时候可能大部分读者都会卡住,因为这部分确实需要一点XPATH或者CSS选择器的前 ...

  4. Selenium模块的安装

    Selenium模块 1.安装selenium python2:pip install selenium python3:pip install selenium 2.设置浏览器驱动 解压后必须与浏览 ...

  5. PHP intdiv 数学函数

    定义和用法 intdiv - 对除法结果取整 版本支持 PHP4 PHP5 PHP7 不支持 不支持 支持 语法 intdiv ( int $dividend , int $divisor ) int ...

  6. Java日期处理组件joda-time

    版权声明:本文为xing_star原创文章,转载请注明出处! 本文同步自http://javaexception.com/archives/175 Java日期处理组件joda-time 平常在开发过 ...

  7. 一文解读HTTP (转)

    先扒一扒HTTP协议背景? HTTP(HyperText Transfer Protocol) 即超文本传输协议,现在基本上所有web项目都遵从HTTP协议(协议就是一种人为的规范). 目前绝大部分使 ...

  8. Callable实现JAVA多线程

    最近项目用到一个功能需要实现多线程分发任务且需要任务的返回值,之前一直都是实现Runnable接口,但里面的run方法是返回void的.后来在网上查了下JAVA1.5开始就有了Callable. 下面 ...

  9. fastadmin中js是如何调用的

    想要了解fastadmin中的js是怎么调用的,就应该先了解RequireJs. RequireJs是模块化工具,每一个我们自己的js文件或者库都可以看成是一个模块,按需引入.写法如下: <sc ...

  10. WebAPI的使用

    后台API的代码: using System; using System.Collections.Generic; using System.Linq; using System.Web; using ...