我们在《服务器在管道中的“龙头”地位》中对ASP.NET Core默认提供的具有跨平台能力的KestrelServer进行了介绍,为了让读者朋友们对管道中的服务器具有更加深刻的认识,接下来我们采用实例演示的形式创建一个自定义的服务器。这个自定义的服务器直接利用HttpListener来完成针对请求的监听、接收和响应,我们将其命名为HttpListenerServer。在正式介绍HttpListenerServer的设计和实现之前,我们先来显示一下如何将它应用到 一个具体的Web应用中。我们依然采用最简单的Hello World应用来演示针对HttpListenerServer的应用,所以我们在Startup类的Configure方法中编写如下的程序直接响应一个“Hello World”字符串。[本文已经同步到《ASP.NET Core框架揭秘》之中]

   1: public class Startup

   2: {

   3:     public void Configure(IApplicationBuilder app)

   4:     {

   5:         app.Run(async context => await context.Response.WriteAsync("Hello World!"));

   6:     }

   7: }

在作为程序入口的Main方法中,我们直接创建一个WebHostBuilder对象并调用扩展方法UseHttpListener完成针对自定义HttpListenerServer的注册。我们接下来调用UseStartup方法注册上面定义的这个启动类型,然后调用Build方法创建一个WebHost对象,最后调用Run方法运行这个作为宿主的WebHost。

   1: public class Program

   2: {

   3:     public static void Main()

   4:     {

   5:         new WebHostBuilder()

   6:             .UseHttpListener()

   7:             .UseStartup<Startup>()

   8:             .Build()

   9:             .Run();

  10:     }

  11: }

  12:  

  13: public static class WebHostBuilderExtensions

  14: {

  15:     public static IWebHostBuilder UseHttpListener(this IWebHostBuilder builder)

  16:     {

  17:         builder.ConfigureServices(services => services.AddSingleton<IServer, HttpListenerServer>());

  18:         return builder;

  19:     }

  20: }

我们自定义的扩展方法UseHttpListener的逻辑很简单,它只是调用WebHostBuilder的ConfigureServices方法将我们自定义的HttpListenerServer类型以单例模式注册到指定的ServiceCollection上而已。我们直接运行这个程序并利用浏览器访问默认的监听地址(http://localhost:5000),服务端响应的“Hello World”字符串会按照如下图所示的形式显示在浏览器上。

接下来我们来介绍一下HttpListenerServer的大体涉及。除了HttpListenerServer这个实现了IServer的自定义Server类型之外,我们只定义了一个名为HttpListenerServerFeature的特性类型,图7所示的UML基本上体现了HttpListenerServer的总体设计。

如果我们利用HttpListener来监听请求,它会为接收到的每次请求创建一个属于自己的上下文,具体来说这是一个类型为HttpListenerContext对象。我们可以利用这个HttpListenerContext对象获取所有与请求相关的信息,针对请求的任何响应也都是利用它完成的。上面这个HttpListenerServerFeature实际上就是对这个作为原始上下文的HttpListenerContext对象的封装,或者说它是管道使用的DefaultHttpContext与这个原始上下文之间沟通的中介。

如下所示的代码片段展示了HttpListenerServerFeature类型的完整定义。简单起见,我们并没有实现上面提到过的所有特性接口,而只是选择性地实现了IHttpRequestFeature和IHttpResponseFeature这两个最为核心的特性接口。它的构造函数除了具有一个类型为HttpListenerContext的参数之外,还具有一个字符串的参数pathBase用来指定请求URL的基地址(对应IHttpRequestFeature的PathBase属性),我们利用它来计算请求URL的相对地址(对应IHttpRequestFeature的Path属性)。IHttpRequestFeature和IHttpResponseFeature中定义的属性都可以直接利用HttpListenerContext对应的成员来实现,这方面并没有什么特别之处。

   1: public class HttpListenerServerFeature : IHttpRequestFeature, IHttpResponseFeature

   2: {

   3:     private readonly HttpListenerContext     httpListenerContext;

   4:     private string                           queryString;

   5:     private IHeaderDictionary                requestHeaders;

   6:     private IHeaderDictionary                responseHeaders;

   7:     private string                           protocol;

   8:     private readonly string                  pathBase;

   9:  

  10:     public HttpListenerServerFeature(HttpListenerContext httpListenerContext, string pathBase)

  11:     {

  12:         this.httpListenerContext     = httpListenerContext;

  13:         this.pathBase                 = pathBase;

  14:     }

  15:  

  16:     #region IHttpRequestFeature

  17:  

  18:     Stream IHttpRequestFeature.Body

  19:     {

  20:         get { return httpListenerContext.Request.InputStream; }

  21:         set { throw new NotImplementedException(); }

  22:     }

  23:  

  24:     IHeaderDictionary IHttpRequestFeature.Headers

  25:     {

  26:         get { return requestHeaders ?? (requestHeaders = GetHttpHeaders(httpListenerContext.Request.Headers)); }

  27:         set { throw new NotImplementedException(); }

  28:     }

  29:  

  30:     string IHttpRequestFeature.Method

  31:     {

  32:         get { return httpListenerContext.Request.HttpMethod; }

  33:         set { throw new NotImplementedException(); }

  34:     }

  35:  

  36:     string IHttpRequestFeature.Path

  37:     {

  38:         get { return httpListenerContext.Request.RawUrl.Substring(pathBase.Length);}

  39:         set { throw new NotImplementedException(); }

  40:     }

  41:  

  42:     string IHttpRequestFeature.PathBase

  43:     {

  44:         get { return pathBase; }

  45:         set { throw new NotImplementedException(); }

  46:     }

  47:  

  48:     string IHttpRequestFeature.Protocol

  49:     {

  50:         get{ return protocol ?? (protocol = this.GetProtocol());}

  51:         set { throw new NotImplementedException(); }

  52:     }

  53:  

  54:     string IHttpRequestFeature.QueryString

  55:     {

  56:         Get { return queryString ?? (queryString = this.ResolveQueryString());}

  57:         set { throw new NotImplementedException(); }

  58:     }

  59:  

  60:     string IHttpRequestFeature.Scheme

  61:     {

  62:         get { return httpListenerContext.Request.IsWebSocketRequest ? "https" : "http"; }

  63:         set { throw new NotImplementedException(); }

  64:     }

  65:     #endregion

  66:  

  67:     #region IHttpResponseFeature

  68:     Stream IHttpResponseFeature.Body

  69:     {

  70:         get { return httpListenerContext.Response.OutputStream; }

  71:         set { throw new NotImplementedException(); }

  72:     }

  73:  

  74:     string IHttpResponseFeature.ReasonPhrase

  75:     {

  76:         get { return httpListenerContext.Response.StatusDescription; }

  77:         set { httpListenerContext.Response.StatusDescription = value; }

  78:     }

  79:  

  80:     bool IHttpResponseFeature.HasStarted

  81:     {

  82:         get { return httpListenerContext.Response.SendChunked; }

  83:     }

  84:  

  85:     IHeaderDictionary IHttpResponseFeature.Headers

  86:     {

  87:         get { return responseHeaders ?? (responseHeaders = GetHttpHeaders(httpListenerContext.Response.Headers)); }

  88:         set { throw new NotImplementedException(); }

  89:     }

  90:     int IHttpResponseFeature.StatusCode

  91:     {

  92:         get { return httpListenerContext.Response.StatusCode; }

  93:         set { httpListenerContext.Response.StatusCode = value; }

  94:     }

  95:  

  96:     void IHttpResponseFeature.OnCompleted(Func<object, Task> callback, object state)

  97:     {

  98:         throw new NotImplementedException();

  99:     }

 100:  

 101:     void IHttpResponseFeature.OnStarting(Func<object, Task> callback, object state)

 102:     {

 103:         throw new NotImplementedException();

 104:     }

 105:     #endregion

 106:  

 107:     private string ResolveQueryString()

 108:     {

 109:         string queryString = "";

 110:         var collection = httpListenerContext.Request.QueryString;

 111:         for (int i = 0; i < collection.Count; i++)

 112:         {

 113:             queryString += $"{collection.GetKey(i)}={collection.Get(i)}&";

 114:         }

 115:         return queryString.TrimEnd('&');

 116:     }

 117:  

 118:     private IHeaderDictionary GetHttpHeaders(NameValueCollection headers)

 119:     {

 120:         HeaderDictionary dictionary = new HeaderDictionary();

 121:         foreach (string name in headers.Keys)

 122:         {

 123:             dictionary[name] = new StringValues(headers.GetValues(name));

 124:         }

 125:         return dictionary;

 126:     }

 127:  

 128:     private string GetProtocol()

 129:     {

 130:         HttpListenerRequest request = httpListenerContext.Request;

 131:         Version version = request.ProtocolVersion;

 132:         return string.Format("{0}/{1}.{2}", request.IsWebSocketRequest ? "HTTPS" : "HTTP", version.Major, version.Minor);

 133:     }

 134: }

接下来我们来看看HttpListenerServer的定义。如下面的代码片段所示,用来监听请求的HttpListener在构造函数中被创建,与此同时,我们会创建一个用于获取监听地址的ServerAddressesFeature对象并将其添加到属于自己的特性列表中。当HttpListenerServer随着Start方法的调用而被启动后,它将这个ServerAddressesFeature对象提取出来,然后利用它得到所有的地址并添加到HttpListener的Prefixes属性表示的监听地址列表中。接下来,HttpListener的Start方法被调用,并在一个无限循环中开启请求的监听与接收。

   1: public class HttpListenerServer : IServer

   2: {

   3:     private readonly HttpListener listener;

   4:  

   5:     public IFeatureCollection Features { get; } = new FeatureCollection();

   6:     

   7:     public HttpListenerServer()

   8:     {

   9:         listener = new HttpListener();

  10:         this.Features.Set<IServerAddressesFeature>(new ServerAddressesFeature());

  11:     }

  12:  

  13:     public void Dispose()

  14:     {

  15:         listener.Stop();

  16:      }

  17:  

  18:     public void Start<TContext>(IHttpApplication<TContext> application)

  19:     {

  20:         foreach (string address in this.Features.Get<IServerAddressesFeature>().Addresses)

  21:         {

  22:             listener.Prefixes.Add(address.TrimEnd('/') + "/");

  23:         }

  24:  

  25:         listener.Start();

  26:         while (true)

  27:         {

  28:             HttpListenerContext httpListenerContext = listener.GetContext();

  29:  

  30:             string listenUrl = this.Features.Get<IServerAddressesFeature>().Addresses

  31:              .First(address => httpListenerContext.Request.Url.IsBaseOf(new Uri(address)));

  32:             string pathBase = new Uri(listenUrl).LocalPath.TrimEnd('/') ;

  33:             HttpListenerServerFeature feature = new HttpListenerServerFeature(httpListenerContext, pathBase);

  34:  

  35:             FeatureCollection features = new FeatureCollection();

  36:             features.Set<IHttpRequestFeature>(feature);

  37:             features.Set<IHttpResponseFeature>(feature);

  38:             TContext context = application.CreateContext(features);

  39:  

  40:             application.ProcessRequestAsync(context).ContinueWith(task =>

  41:             {

  42:                 httpListenerContext.Response.Close();

  43:                 application.DisposeContext(context, task.Exception);

  44:             });

  45:         }

  46:     }

  47: }

HttpListener的GetContext方法以同步的方式监听请求,并利用接收到的请求创建返回的HttpListenerContext对象。我们利用它解析出当前请求的基地址,并进一步创建出描述当前原始上下文的HttpListenerServerFeature。接下来我们将这个对象分别采用特性接口IHttpRequestFeature和IHttpResponseFeature添加到创建的FeatureCollection对象中。然后我们将这个FeatureCollection作为参数调用HttpApplication的CreateContext创建出上下文对象,并将其作为参数调用HttpApplication的ProcessContext方法让注册的中间件来逐个地对请求进行处理。

学习ASP.NET Core, 怎能不了解请求处理管道[3]: 自定义一个服务器感受一下管道是如何监听、接收和响应请求的的更多相关文章

  1. ASP.NET Core 6框架揭秘实例演示[28]:自定义一个服务器

    作为ASP.NET Core请求处理管道的"龙头"的服务器负责监听和接收请求并最终完成对请求的响应.它将原始的请求上下文描述为相应的特性(Feature),并以此将HttpCont ...

  2. 学习ASP.NET Core, 怎能不了解请求处理管道[6]: 管道是如何随着WebHost的开启被构建出来的?

    注册的服务器和中间件共同构成了ASP.NET Core用于处理请求的管道, 这样一个管道是在我们启动作为应用宿主的WebHost时构建出来的.要深刻了解这个管道是如何被构建出来的,我们就必须对WebH ...

  3. 学习ASP.NET Core,怎能不了解请求处理管道[1]: 中间件究竟是个什么东西?

    ASP.NET Core管道虽然在结构组成上显得非常简单,但是在具体实现上却涉及到太多的对象,所以我们在 "通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流 ...

  4. 学习ASP.NET Core, 怎能不了解请求处理管道[5]: 中间件注册可以除了可以使用Startup之外,还可以选择StartupFilter

    中间件的注册除了可以借助Startup对象(DelegateStartup或者ConventionBasedStartup)来完成之外,也可以利用另一个叫做StartupFilter的对象来实现.所谓 ...

  5. 学习ASP.NET Core, 怎能不了解请求处理管道[4]: 应用的入口——Startup

    一个ASP.NET Core应用被启动之后就具有了针对请求的处理能力,而这个能力是由管道赋予的,所以应用的启动同时意味着管道的成功构建.由于管道是由注册的服务器和若干中间件构成的,所以应用启动过程中一 ...

  6. 学习ASP.NET Core,怎能不了解请求处理管道[2]: 服务器在管道中的“龙头”地位

    ASP.NET Core管道由注册的服务器和一系列中间件构成.我们在上一篇中深入剖析了中间件,现在我们来了解一下服务器.服务器是ASP .NET Core管道的第一个节点,它负责完整请求的监听和接收, ...

  7. 学习ASP.NET Core,你必须了解无处不在的“依赖注入”

    ASP.NET Core的核心是通过一个Server和若干注册的Middleware构成的管道,不论是管道自身的构建,还是Server和Middleware自身的实现,以及构建在这个管道的应用,都需要 ...

  8. 学习ASP.NET Core Razor 编程系列二——添加一个实体

    在Razor页面应用程序中添加一个实体 在本篇文章中,学习添加用于管理数据库中的书籍的实体类.通过实体框架(EF Core)使用这些类来处理数据库.EF Core是一个对象关系映射(ORM)框架,它简 ...

  9. 学习ASP.NET Core Razor 编程系列四——Asp.Net Core Razor列表模板页面

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

随机推荐

  1. Fis3前端工程化之项目实战

    Fis3项目 项目目录结构: E:. │ .gitignore │ fis-conf.js │ index.html │ package.json │ README.md │ ├─material │ ...

  2. AutoMapper:Unmapped members were found. Review the types and members below. Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type

    异常处理汇总-后端系列 http://www.cnblogs.com/dunitian/p/4523006.html 应用场景:ViewModel==>Mode映射的时候出错 AutoMappe ...

  3. 前端学HTTP之报文首部

    前面的话 首部和方法配合工作,共同决定了客户端和服务器能做什么事情.在请求和响应报文中都可以用首部来提供信息,有些首部是某种报文专用的,有些首部则更通用一些.本文将详细介绍HTTP报文中的首部 结构 ...

  4. html5 与视频

    1.视频支持格式. 有3种视频格式被浏览器广泛支持:.ogg,.mp4,.webm. Theora+Vorbis=.ogg  (Theora:视频编码器,Vorbis:音频编码器) H.264+$$$ ...

  5. 要想提高PHP的编程效率,你必须知道的要点

    1.当操作字符串并需要检验其长度是否满足某种要求时,你想当然地会使用strlen()函数.此函数执行起来相当快,因为它不做任何计算,只返回在zval 结构(C的内置数据结构,用于存储PHP变量)中存储 ...

  6. 微信公众号开发(一)--验证服务器地址的Java实现

    现在主流上都用php写微信公众号后台,其实作为后端语言之一的java也可以实现. 这篇文章将对验证服务器地址这一步做出实现. 参考资料:1.慕课网-<初识java微信公众号开发>,2.微信 ...

  7. Flexible 弹性盒子模型之CSS flex-basis 属性

    实例 设置第二个弹性盒元素的初始长度为 80 像素: div:nth-of-type(2){flex-basis:80px;}   效果预览 浏览器支持 表格中的数字表示支持该属性的第一个浏览器的版本 ...

  8. iOS之计算上次日期距离现在多久, 如 xx 小时前、xx 分钟前等

    /**  *  计算上次日期距离现在多久  *  *  @param lastTime    上次日期(需要和格式对应)  *  @param format1     上次日期格式  *  @para ...

  9. Mysql - 增删改

    因为项目原因, mysql用了两年了, 但是一直都未曾去总结过. 最近也是领导让总结项目, 才想起把mysql的使用小结一下. 一. Create 1. 单条插入, sql格式: insert int ...

  10. zookeeper集群的搭建以及hadoop ha的相关配置

    1.环境 centos7 hadoop2.6.5 zookeeper3.4.9 jdk1.8 master作为active主机,data1作为standby备用机,三台机器均作为数据节点,yarn资源 ...