开场

  Web服务器是啥玩意? 是那个托管了我的网站的机器么? No,虽然那个也是服务器,但是我们今天要说的Web服务器主要是指像IIS这样一类的,用于处理request并返回response的工具,没错我们可以说它是一个工具,不就是一个应用程序吗?谁不会写应用程序呀,等着,三分钟就搞一个出来。

Web Server的介绍

  我们先来看一下web server主要干什么?

  这图很熟悉么?我是直接从小坦克的那篇http协议里面拿过来的,但是要注意的是,图中的Web Server是指的那台机器。我们网站的文件可能放在它上面的某一个磁盘目录下,但是接收request并且最后返回给我们的response的却不是机器本身,它就是我们今天的开场web server。一般我们ASP.NET网站开发时所指的web server就是IIS了,但是还有一些开源的像Apache,Lighttpd, Nginx等在php和java领域以及开源社区都有很大的名声,并且Apache才是被使用最多的web server(大概占60%左右的市场)。

  虽然说web server的主要工作是处理request返回response,但是一些主流的web server还包括了很多其它的扩展模块

  • 应用程序生命周期管理
  • 认证
  • 授权
  • 缓存
  • 安全
  • 队列处理
  • 压缩
  • 线程管理
  • ......

  当然,上面这些功能呢,我们一个也不会实现,:(  我们今天只实现对一个静态站点的访问,其实我的静态站点里面也就一个页面。但是这只是一个思路,给大家留下足够的想象空间,更重要的是好戏还在后头!

类库介绍

  • HttpListener: http协议监听器。
  • HttpListenerContext:包含resquest 和 response信息的一个上下文对象。
  • HttpListenerRequest:包含请求信息,头,体等。
  • HttpListenerResponse:包含响应信息,头,体等。

  我们今天就主要借助以上4个类来帮助实现我们的web server,这4个类都是包含在System.Net命名空间下,并且是在2.0的时候就已经存在了,所以并不是什么新鲜事了。我们创建了一个控制台应用程序,然后在不到3分钟的时间内写了以下代码。

public static HttpListener listener = new HttpListener();
// 暂时把程序启动目标设置为我们网站的根目录
public static string startUpPath = System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
static void Main(string[] args)
{
listener.Start(); // 使用本机IP地址监听
listener.Prefixes.Add("http://192.168.1.100/");
Thread t = new Thread(new ThreadStart(clientListener));
t.Start();
Console.Write("Web server started...");
while (true)
{
string s = Console.ReadLine();
Console.Write("Web server ended...");
}
} public static void clientListener()
{
while (true)
{
try
{
HttpListenerContext request = listener.GetContext();
// 从线程池从开一个新的线程去处理请求
ThreadPool.QueueUserWorkItem(processRequest, request);
}
catch (Exception e) { Console.WriteLine(e.Message); }
}
}

  //处理请求的代码

public static void processRequest(object listenerContext)
{
try
{
var context = (HttpListenerContext)listenerContext;
string filename = context.Request.RawUrl.Remove(0, 1);
string path = Path.Combine(startUpPath, filename);
byte[] msg;
if (!File.Exists(path))
{
Console.WriteLine("文件未找到,找错了!");
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
msg = File.ReadAllBytes(startUpPath + "\\webroot\\error.html");
}
else
{
context.Response.StatusCode = (int)HttpStatusCode.OK;
msg = File.ReadAllBytes(path);
}
context.Response.ContentLength64 = msg.Length;
using (Stream s = context.Response.OutputStream)
{
// 直接将文件写入response
s.Write(msg, 0, msg.Length);
}
}
catch
{
}
}

  接下来,我放了一个简单的index.html和一个images文件夹在我们应用程序的bin目录下,然后按F5启动这个控制台应用程序,最后输入我们的http://192.168.1.100/index.html,你们将会看到:

  怎么样?有图有真相,我们这个小小的web server已经可以处理一个静态的站点了,包括css文件js文件都没有问题。当然对于HttpListener的用法,如果大家感兴趣可以继续研究,我们这里就点到为止。因为如果你觉得写一个小小的web server是本文的重点,那么我只能说,少年,你实在是太年轻了!

  好的,让我们重新开始吧!

本文概述

  为什么会有一个关于自定义web server的例子摆在本文概述的前面呢?本文又到底是要阐述一个什么样的话题呢?让我们把时钟拔到2周以前,也就是我的上一篇博客,通过介绍ASP.NET Identity的登录原理引入了微软开源家族中的又一个亮点产品OWin(Open web interface for .net),关于什么是OWin,我们在上一篇博客中已经有了比较具体的介绍,我就不打算重复了。简而言之,它是一个有着潜力可以让ASP.NET MVC脱离 IIS(我想通过这里,你或许可以猜到我们为什么会有前面的那个demo),或者说可以让我们用全新的方式开发基于.NET的WEB应用程序的。

  问题一:ASP.NET开发的网站能Host在除了IIS以外的其它server上么?
  问题二:基于.NET的来开发web应用程序的方式除和ASP.NET Web Form和ASP.NET MVC以外,还有其它方式么?

IIS到底哪里错了?

  由于篇幅的原因,今天我们先来回答第一个问题。到目前为止,ASP.NET开发的网站是不能托管在除了IIS以外的Web服务器之上的,至少很难,为什么呢?我们要从ASP.NET的管道模型开始说起, 上周你们不是推荐了那篇ASP.NET是如何在IIS工作的 么?我借鉴一下里面的那张.NET运行时的序列图:    

  

  但是今天我们不是讲IIS是如何工作的,我们把上面用到的对象列出来看一下:

  • ISAPIRuntime: System.Web.Hosting.ISAPIRuntime, System.Web
  • HttpWorkerRequest: System.Web.HttpWorkerRequest, System.Web
  • HttpRuntime: System.Web.Runtime, System.Web
  • HttpApplicationFactory: System.Web.HttpApplicationFactory, System.Web
  • HttpApplication: System.Web.HttpApplication, System.Web
  • HttpContext: System.Web.HttpContext, System.Web
  • HttpRequest: System.Web.HttpRequest, System.Web
  • HttpReponse:  System.Web.HttpResponse, System.Web

System.Web是属于.NET Framewok的一部份

  大家可以发现,这些类全部是被放到了System.Web这个dll中的,包括其中没有列出来的Session,IHttpModule和IHttpHandle同样也是。那么这个dll有什么问题么?这个dll本身没有问题,问题在于它是.NET Framework的一部份,回顾一下.NET Framework多久更新一次?2-3年? 当然.NET Framework 2-3年更新一次并没有什么错,因为毕竟它是非常底层的东西,必须保证它的稳定性的健壮性。但是Web这个词汇本身就是一个更新换代非常快的东西,万一它有个什么bug,我们也得等个2-3年,这就直接导致了如果想要对这些相关的功能做一些改进或者优化,等它出来也得等2到3年(一个程序员的青春有几个3年啊!)

  不过ASP.NET Team吸取了教训,现在的Web API就已经完全摆脱了对System.Web的依懒,所以Web API是用Nuget来发布版本的,.NET Framework 10年多的时间才到4.5,而Web API不到两年的时间已经接近了12个release 现在是 2.1 。 这也使得Web API能够更好的拥抱变化,更快的响应开发者以及开源社区的需求,当然Web API本身也是开源的。

  为什么ASP.NET MVC没有放到.NET Framework中,也是这个原因。

  还有一个问题是,所有的这些东西全部放在System.Web中,随着时间的推移,这个dll就会越来越大,越来越复杂。

HttpModule是基于IIS管道的

  在上一篇文章中,我们讲到为什么要解耦服务器与应用程序时,我们也提到了IIS的处理模型,从上到下,IIS给我们暴露了这样的一些事件,而我们开发自定义的HttpModule就是绑定这些事件来做一些处理。设想一下,如果我要在Authorization之后实现多个HttpModule,并且要按照指定顺序来执行怎么办?

  

  我们并不能改变以上管道中每一个结点中的执行顺序,而我们自定义的HttpModule是按照我们在web.config中定义的顺序被添加的。这里的局限性是,这条管道就是这么多个执行过程,我们只能够在其中的某一个结点之前,或之后来做一些事件。又或者我想关掉其中的某些步骤(比如说我不要Authentication),怎么办?

ASP.NET 多数Modules默认全部开启

  我们可以用VS2013新建一个空白的MVC站点,记住是完全空白的,然后我们可以看一下有哪些HttpModule是在工作的。我们只需要建一个HomeController加一个Index的Action就可以了。

public class HomeController : Controller
{
public void Index()
{
HttpApplication httpApps = ControllerContext.HttpContext.ApplicationInstance;
// 获取所有 http module
HttpModuleCollection httpModules = httpApps.Modules; Response.Write(string.Format("一共有{0}个 HttpModule</br>", httpModules.Count.ToString()));
foreach (string activeModule in httpModules.AllKeys)
{
Response.Write(activeModule + "</br>");
}
}
}

  大家可以看到,OutputCache,Session,WindowsAuthentication,FormsAuthentiation, RoleManager, Profile等等,这些你在项目中真的有用到么?如果没有,你有关闭他们么?

  如果不使用它们,这些Module是需要手动在config文件里面移除的。但是大多数情况下,程序员们并不会想到去移除他们,这其实是一个性能上的损失。

  

  当然我们并不能因为这一些问题就否认IIS,就算是ASP.NET在当初设计的时候也是被认为它就是要被托管在IIS上的。但是它又不具有很好扩展性,同时ASP.NET也是时候要考虑开放了,特别是在Node.js以及一些开源前端MVVM框架的影响下,Web后端开发有逐渐要被取代的趋势,所以OWin来了,它为了解决这些问题而来,一切都还是我们所熟悉的,但是却给了我们更灵活的开发方式。

随心所欲-建立你自己的管道

  我们上篇有说到OWin只是一套定义,它本身不具备任何代码。它主要定义了服务器在处理resquest所需要的一些信息(大多都是http协议里面要求的),和一个应用程序代理。  

  IDictionary<string,object>叫做环境变量,这个将要贯穿我们整个处理管道的集合里面存储了我们所需要的所有信息。而后面的Task,代表着管道的下一个结点,我们可以调用Invoke方法处理流程交给下一个结点。

  就是这么简单,在这套定义的帮助下,我们完全摆了上面提到了System.Web中的所有类,HttpApplication, HttpContext, HttpRequest, HttpResponse全部都不需要了。什么HttpModule, HttpHandler 这些玩意就让他们成为历史吧!

OWin环境变量都包含哪些?

  首先,环境变量是可以在生一个处理结点的时候随意添加的。其次OWin有定义一些必须的环境变量,因为没有这些是不能构成一个完整的Request的。

  为了让大家更好的理解我们上面所讲的自定义管道的概念,我们来做一个小小的demo。注意我们下面用的的所有类库是来自微软的另外一个开源项目Katana,我们说Owin只是一套定义,而Katana,则是微软对于Owin的一套实现。大家不要觉得Katana陌生,现在你用VS2013新建一个MVC5的项目都会自动引用相关的dll(Owin.dll, Microsoft.Owin.dll) ,也会自动添加Startup的配置类。 关于Katana的源码,大家可以到CodePlex上去下载。下面是对Katana项目结构的一个简单介绍:

  好了,知道了Katana的存在,我们就可以来看我们的Demo了,我们打算这样干:

  1. 建立一个空的MVC站点
  2. 从Nuget中添加Microsoft.Owin.Host.SystemWeb
  3. 添加Startup配置类

Microsoft.Owin.Host.SystemWeb

  这个dll可以让OWin接管IIS的请求,虽然同样是托管在IIS,但是所有的请求都会被OWin来处理。在OWin的4层结构中(Applicaton->Middleware->Server->Host),Microsoft.Owin.Host.SystemWeb属于Server层,还有一个同样也在Server层的是Microsoft.Owin.Host.HttpListener,这个可以实现利用控制台程序现实自托管,就可以完全摆脱IIS了。

Startup配置类

  要使用Owin的应用程序都要有一个叫Startup的类,在这个类里面有一个Configuration的方法,这两个名字是默认约定,必须用同样的名字才会被Owin找到。你也可以用Attribute和在web.config文件中配置的方式来定义这个类,详情见Startup。我们在Configuration方法里面,就可以定义我们自己的管道了。我们可以通过Use来添加自己的管道的处理步骤,并且可以自己设置处理顺序。

public void Configuration(IAppBuilder app)
{
app.Use(async (context, next) =>
{
await context.Response.WriteAsync(" Authentication... ");
await next();
}); app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Authorization... ");
await next();
}); app.Use(async (context, next) =>
{
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Hello World!");
});
}

  我们需要在web.config中加入一个配置,让OWin处理所有的请求:

  <appSettings>
<add key="owin:HandleAllRequests" value="true"/>
</appSettings>

  这样的话,不管我们输入什么URL,都会返回同样的结果,因为不管哪个URL,对应的都是我们上面所写的代码。

用Microsoft.Owin.Host.HttpListener实现自寄宿

  上面的网站我们依旧是托管在IIS中的,但是我们今天的主题是摆脱IIS,所以接下来我们就来利用Owin的自托管功能。

  1. 新建一个控制台程序
  2. 拷贝我们上面建立的Startup类
  3. 用Nuget安装 Microsoft.Owin.Hosting 和 Microsoft.Owin.HttpListener

  我们需要在Main方法中加入下面的一段代码去启动我们的网站。

class Program
{
static void Main(string[] args)
{
using (WebApp.Start<Startup>(
new StartOptions(url: "http://localhost:7000")))
{
Console.ReadLine();
}
Console.ReadLine();
}
}

  按F5启动我们的控制台程序之后,我们就可以通过浏览器访问我们的7000端口了。

  当然,结果和我们Host在IIS上是一样的。

一切都在IDictionary<string,object>集合中

  当我们用控制台程序自寄宿的时候,没有IIS,没有System.Web,那么我们的Request信息和Response信息从何而来呢?

  首先,我们可以看到其实这里的Context,Reuqest, Response都已经不是原来的了,Katana自己有一些对应的类来封装了这些信息。但是就算是没有这些类,我们也可以很方便的拿到Request和Reponse,因为他们全部都在我们所讲的环境变量中。

  我们的Request Header, Url, Method等都被放到了这个环境变量的集合中,包括Response Header, Response Body, Response Status等同样也是。而这个环境变量会从一开始,一直到最后结束,在整个管道的每一步中我们都能够访问得到,并且可以添加和修改。就是这样最后得到一个Http Response返回给客户端的。

用Middleware来串成一个完整的管道

  其实我们上面的3个Use方法已经构成了一个完整的管道,但是不具有通用性,而且因为我们的Demo十分的简单,代码量少才允许我们那样写。但是在真正的开发过程中,我们要将Use中的代码转换成Middleware,打包成dll供其它项目使用。

  IAppBuilder 提供了一个Use的重载可以把一个Middleware作为泛型参数传进去来实现将这个Middleware注册进Owin的管道。下面模拟一下AuthenticationMiddleware和AuthorizationMiddleware的实现,我们可以直接从OwinMiddleware继承。

class AuthenticationMiddleware : OwinMiddleware
{
public AuthenticationMiddleware(OwinMiddleware next) : base(next) { } // 主要逻辑入口
public override async Task Invoke(IOwinContext context)
{
await context.Response.WriteAsync("Authentication....");
     // 如果你想在这里中断整个管道,下面这句话不调就可以了。
await Next.Invoke(context);
}
} class AuthorizationMiddleware : OwinMiddleware
{
public AuthorizationMiddleware(OwinMiddleware next) : base(next) { } // 主要逻辑入口
public override async Task Invoke(IOwinContext context)
{
await context.Response.WriteAsync("Authorization....");
await Next.Invoke(context);
}
}

 这里要注意的是,所有的Middleware构造函数都接收一个OwinMiddleware作为参数传给基类,基类会把它作为下一下Middleware,和我们上面用到的Next一样都是为了确定管道继续进行下去。那我们就可以用下面这种办法来注册我们的Middleware了。

public void Configuration(IAppBuilder app)
{
app.Use<AuthenticationMiddleware>(); app.Use<AuthorizationMiddleware>(); app.Use(async (context, next) =>
{
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Hello World!");
});
}

  是不是比之前一堆的Use方法要简洁了很多?如果这还不够的话,我们还可以学习ASP.NET Identity Middleware以及WEB Api Owin Middleware的作法,为IAppBuilder添加扩展方法,这样调用都甚至都不需要知道我们Middleware的类名,只需要调用扩展方法就可以了,比如说Web Api的app.UseWebAPI()。

用Microsoft.Owin.StaticFiles来实现静态站点的托管

  我们可以接着上面的控制台程序继续添加代码,用Nuget下载Microsoft.Owin.StaticFiles,然后在Startup里面添加下面的代码。

同样,我们还是用控制台托管的方式:

就是这么几行代码,我们就用Owin实现了一个静态网站的的Web服务器了,因为我把站点的根目录指向了我们文章一开始那个站点的根目录,所以结果当然是一样的,但是请注意,我是换了端口的!

大功告成,但是为什么要前最前面那个Demo,因为Owin的Host就是用同样的方法实现的,只不过进行了一些封装而已,有兴趣的朋友也可以自己开载Katana的源码进行阅读,我后面也会继续写关于Owin的博客。

YY一下Owin的未来

   Owin(Open web interface for .NET)为了解放.NET而来,摆脱了.NET Framework的束缚,摆脱了IIS的束缚,ASP.NET才可以跑得越来越快。.NET的世界会越来越精彩,我们已经看到Web API可以用Owin来托管,SignalR也可以用Owin来托管,静态文件同样用Owin来托管,再加上Owin这种开放式的,可插拔式的设计,最后还是开源的,我相信会有越来越多的Framework加入到Owin中来。我们文中看到Owin已经是可以实现动态生成Reponse,那我们可以大胆猜测一下,ASP.NET MVC会不会加入到Owin中来,那么这样的话ASP.NET MVC也可以托管在Owin上了,同时ASP.NET Team也表示,Owin很快就会支持MONO !那ASP.NET 是不是可以跨平台了(当然现在也可以),但是有了Owin这样一个框架在这里面以后,一切都会变得更容易一些!所以小伙伴们要Hold住了,小纳不是说了么,对开发者好,为什么不去做呢? 那就做吧!

一不小心写了个WEB服务器的更多相关文章

  1. 写一个简易web服务器、ASP.NET核心知识(4)

    前言 昨天尝试了,基于对http协议的探究,我们用控制台写了一个简单的浏览器.尽管浏览器很low,但是对于http协议有个更好的理解. 说了上面这一段,诸位猜到我要干嘛了吗?(其实不用猜哈,标题里都有 ...

  2. 写一个简易web服务器、ASP.NET核心知识(4)--转载

    第一次尝试(V1.0) 1.理论支持 这里主要要说的关于Socket方面的.主要是一个例子,关于Socket如何建立服务端程序的简单的代码. static void Main(string[] arg ...

  3. atitit.跨架构 bs cs解决方案. 自定义web服务器的实现方案 java .net jetty  HttpListener

    atitit.跨架构 bs cs解决方案. 自定义web服务器的实现方案 java .net jetty  HttpListener 1. 自定义web服务器的实现方案,基于原始socket vs   ...

  4. 读《图解HTTP》有感-(与HTTP协作的WEB服务器)

    写在前面 Web服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,可以向浏览器等Web客户端提供文档: 一台web服务器可以搭建多个独立域名的web网站,也可以作为通信路径(路由)上的中 ...

  5. Tornado WEB服务器框架 Epoll

    引言: 回想Django的部署方式 以Django为代表的python web应用部署时采用wsgi协议与服务器对接(被服务器托管),而这类服务器通常都是基于多线程的,也就是说每一个网络请求服务器都会 ...

  6. java写的web服务器

    经常用Tomcat,不知道的以为Tomcat很牛,其实Tomcat就是用java写的,Tomcat对jsp的支持做的很好,那么今天我们用java来写一个web服务器 //首先得到一个server, S ...

  7. 转:C#写的WEB服务器

    转:http://www.cnblogs.com/x369/articles/79245.html 这只是一个简单的用C#写的WEB服务器,只实现了get方式的对html文件的请求,有兴趣的朋友可以在 ...

  8. 徒手用Java来写个Web服务器和框架吧<第三章:Service的实现和注册>

    徒手用Java来写个Web服务器和框架吧<第一章:NIO篇> 徒手用Java来写个Web服务器和框架吧<第二章:Request和Response> 这一章先把Web框架的功能说 ...

  9. 徒手用Java来写个Web服务器和框架吧<第二章:Request和Response>

    徒手用Java来写个Web服务器和框架吧<第一章:NIO篇> 接上一篇,说到接受了请求,接下来就是解析请求构建Request对象,以及创建Response对象返回. 多有纰漏还请指出.省略 ...

随机推荐

  1. 戏说HTML5

    如果有非技术人员问你,HTML5是什么,你会怎么回答? 新的HTML规范... 给浏览器提供了牛逼能力,干以前不能干的事...(确切地说应该是给浏览器规定了许多新的接口标准,要求浏览器实现牛逼的功能. ...

  2. Asp.net Boilerplate源码中NotNullAttribute的用处

    看Asp.net Boilerplate 1.1.3.0源码时发现有一个NotNullAttribute的定义和27处的引用,就是不知道它的作用,当然顾名思义是可以的,就是不知道它是怎么判断的,在哪里 ...

  3. 逆天Kali带你游遍大江南北~安全之前人铺路!

    0.Linux基础学习(基本指令) http://www.cnblogs.com/dunitian/p/4822807.html 1.Kali安装到移动硬盘或者U盘中~Linux系列通用方法(包括An ...

  4. 网站定位之---根据IP获得区域

    记得以前做一个培训机构网站时候需要定位,那时候用的搜狐的api,不是很精准. demo:https://github.com/dunitian/LoTCodeBase/tree/master/NetC ...

  5. WPF 微信 MVVM

    公司的同事离职了,接下来的日子可能会忙碌,能完善DEMO的时间也会少了,因此,把做的简易DEMO整体先记录一下,等后续不断的完善. 参考两位大神的日志:WEB版微信协议部分功能分析.[完全开源]微信客 ...

  6. 预览github里面的网页或dome

    1.问题所在: 之前把项目提交到github都可以在路径前面加上http://htmlpreview.github.io/?来预览demo,最近发现这种方式预览的时候加载不出来css,js(原因不详) ...

  7. iOS逆向工程之Hopper+LLDB调试第三方App

    LLDB是Low Level Debugger的简称,在iOS开发的调试中LLDB是经常使用的,LLDB是Xcode内置的动态调试工具.使用LLDB可以动态的调试你的应用程序,如果你不做其他的额外处理 ...

  8. VICA 架构设计(1)

    本文记录最近完成的一个通用实时通信客户端的架构.   背景 我们公司是做税务相关的软件,有针对大客户 MIS 系统,也有针对中小客户的 SaaS 平台.这些系统虽然都是 B/S 的,但是也需要使用 A ...

  9. java中Action层、Service层和Dao层的功能区分

    Action/Service/DAO简介: Action是管理业务(Service)调度和管理跳转的. Service是管理具体的功能的. Action只负责管理,而Service负责实施. DAO只 ...

  10. from表单提交数据之后,后台对象接受不到值

    如果SSH框架下,前段页面通过from表单提交数据之后,在后台对象显示空值,也就是接收不到值得情况下.首先保证前段输入框有值,这个可以在提交的时候用jQuery的id或者name选择器alert弹出测 ...