来源: [转载]ASP.NET MVC随想录——漫谈OWIN – 木宛城主 – 博客园
什么是OWIN
OWIN是Open Web Server Interface for .NET的首字母缩写,他的定义如下:
OWIN在.NET Web Servers与Web Application之间定义了一套标准接口,OWIN的目标是用于解耦Web Server和Web Application。基于此标准,鼓励开发者开发简单、灵活的模块,从而推进.NET Web Development开源生态系统的发展。
正如你看到的这样,OWIN是接口、契约,而非具体的代码实现,仅仅是规范(specifications),所以要实现自定义基于OWIN的Web Server必须要实现此规范。
历时两年(2010-2012),OWIN的规范终于完成并且当前版本是1.0,在OWIN的官网上可以看到更具体的信息。
为什么我们需要OWIN
过去,IIS作为.NET 开发者来说是最常用的Web Server(没有之一),源于微软产品的紧耦合关系,我们不得不将Website、Web Application、Web API等部署在IIS上,事实上在2010年前并没有什么不妥,但随着近些年来Web的发展,特别是移动互联网飞速发展,IIS作为Web Server已经暴露出他的不足了。主要体现在两个方面,ASP.NET (System.Web)紧耦合IIS,IIS紧耦合OS,这就意味着,我们的Web Framework必须部署在微软的操作系统上,难以跨平台。
ASP.NET 和 IIS
我们知道,不管是ASP.NET MVC 还是ASP.NET WEB API 等都是基于ASP.NET Framework的,这种关系从前缀就可以窥倪出来。而ASP.NET的核心正是System.Web这个程序集,而且System.Web紧耦合IIS,他存在于.NET Framework中。所以,这导致了Web Framework严重的局限性:
- ASP.NET 的核心System.Web,而System.Web紧耦合IIS
- System.Web 是.NET Framework重要组成,已有15年以上历史,沉重、冗余,性能差,难于测试,约2.5M
- System.Web要更新和发布新功能必须等待.NET Framework发布
- .但NET Framework是Windows的基础,往往不会随意更新。
所以要想获取最新的Web Framework是非常麻烦的,幸运的事,微软已经意识到了问题的严重性,最新的Web Framework都是通过Nuget来获取。
当然这是一部分原因,还有一层原因是ASP.NET & IIS实在太过于笨重,如何讲呢?
复杂的生命周期已成为累赘?简单来说,当请求到达服务器时,Windows内核组件HTTP.SYS组件捕获请求,他会分析请求并决定是否交给IIS来处理,当请求到达IIS之后,IIS会根据处理程序映射来匹配请求并交给对应的程序集(实现了ISAPI接口,比如我们熟知的aspnet_isapi.dll是专门用来处理ASP.NET Application)处理,最后加载了CLR运行环境,将请求交给aspnet_wp.exe去处理,这时复杂的ASP.NET生命周期往往令人头大,但事实上有很多时候我们并不需要他。
如下图所示ASP.NET Architecture:
打开IIS,你会发现他提供了非常丰富的功能:缓存、身份验证、压缩、加密等。但随着移动互联网蓬勃的发展,特别是HTML 5越来越成熟的今天,我们看到越来越多的操作发生在客户端,而不是沉重的从服务器产生HTML返回,更多的是通过异步AJAX返回原生的数据。同理,对于 APP来说我们只需要Mobile Service返回数据。显然IIS显得笨重了点,而且IIS作为微软产品系的一环,耦合程度太高。所以我们迫切需要轻量、快速、可扩展的宿主来承载Web Application和Web Service。
IIS 和 OS
IIS必须是安装并运行在Windows操作系统中,这是微软产品的一贯风格,环环相套,但不得不考虑他们的限制和局限性:
- IIS往往和操作系统(Windows Server)绑定在一起,这意味着对于一些新功能如WebSocket Protocol ,我们不得不等待操作系统Windows Sever 2012、Windows 8的发布(IIS 8.0)。
- 为了使用WebSocket这类新特性,他仅被IIS 8.0支持,如下所示:
这时你不得不去升级IIS,但升级操作系统可能会引发旧系统的不稳定性,所以要想平稳的升级IIS并不是简单的。
- IIS作为经典的Web Server必须安装在Windows系统中,Windows Server需要授权使用。
正是由于微软产品系紧耦合的关系,才造成跨平台上的不足,这也是被饱受诟病。所以我们需要OWIN来解耦,在面向对象的世界里,接口往往是解耦的关键,如下图所示:
使用OWIN,Web Framework不再依赖IIS和OS,这意味着你能使用任何你想的来替换IIS(比如:Katana或者Nowin),并且在必要时随时升级,而不是更新操作系统。当然,如果你需要的话,你可以构建自定义的宿主和Pipeline去处理Http 请求。
这一切的改变都是由于OWIN的出现,他提供了明晰的规范以便我们快速灵活的去扩展Pipeline来处理Http请求,甚至可以不写任何一句代码来切换不同的Web Server,前提是这些Web Server 遵循OWIN规范。
OWIN的规范
现在我们已经了解了什么是OWIN已经为什么需要OWIN,现在是时候来分析一下OWIN的规范了。
OWIN Layers
实际上,OWIN的规范非常简单,他定义了一系列的层(Layer),并且他们的顺序是以堆(Stack)的形式定义,如下所示。OWIN中的接口被称之为应用程序委托或者AppFunc,用来在这些层之间通信。
OWIN定义了4层:
Host:主要负责应用程序的配置和启动进程,包括初始化OWIN Pipeline、运行Server。
Server:这是实际的Http Server,绑定套接字并监听的HTTP请求然后将Request和Response的Body、Header封装成符合OWIN规范的字典并发送到OWIN Middleware Pipeline中,最后Application为Response Data填充合适的字段输出。
Middleware:称之为中间件、组件,位于Server与Application之间,用来处理发送到Pipeline中的请求,这类组件可以是简单的Logger或者是复杂的Web Framework比如Web API、SignalR,只要Sever连接成功,Middleware中间件可以是任何实现应用程序委托的组件。
Application:这是具体的应用程序代码,可能在Web Framework之上。对于Web API、SignalR这类Web Framework中间件而言,我们仅仅是改变了他们的托管方式,而不是取代ASP.NET WEB API、SignalR原先的应用程序开发。所以该怎么开发就怎么开发,只不过我们将他们注册到OWIN Pipeline中去处理HTTP 请求,成为OWIN管道的一部分,所以此处的Application即正在意义上的处理程序代码。
Application Delegate
OWIN规范另一个重要的组成部分是接口的定义,用于Server和Middleware的交互。他并不是严格意义上的接口,而是一个委托并且每个OWIN中间件组件必须提供。
从字面上理解,每个OWIN中间件在必须有一个方法接受类型了IDictionary<string,object>的变量(俗称环境字典),然后必须返回Task来异步执行。
Environment Dictionary
环境字典包含了Request、Response所有信息以及Server State。通过Pipeline,每个中间件组件和层都可以添加额外的信息,但环境字典定义了一系列强制必须存在的Key,如下所示:
Request Data:
Required |
Key Name |
Value Description |
Yes |
"owin.RequestBody" |
A Stream with the request body, if any. Stream.Null MAY be used as a placeholder if there is no request body. See Request Body. |
Yes |
"owin.RequestHeaders" |
An IDictionary<string, string[]> of request headers. See Headers. |
Yes |
"owin.RequestMethod" |
A string containing the HTTP request method of the request (e.g., "GET" , "POST" ). |
Yes |
"owin.RequestPath" |
A string containing the request path. The path MUST be relative to the “root” of the application delegate; see Paths. |
Yes |
"owin.RequestPathBase" |
A string containing the portion of the request path corresponding to the “root” of the application delegate; see Paths. |
Yes |
"owin.RequestProtocol" |
A string containing the protocol name and version (e.g. " HTTP/1.0" or " HTTP/1.1" ). |
Yes |
"owin.RequestQueryString" |
A string containing the query string component of the HTTP request URI, without the leading “?” (e.g., "foo=bar&baz=quux" ). The value may be an empty string. |
Yes |
"owin.RequestScheme" |
A string containing the URI scheme used for the request (e.g., "http" , "https" ); see URI Scheme. |
Response Data:
Required |
Key Name |
Value Description |
Yes |
"owin.ResponseBody" |
A Stream used to write out the response body, if any. See Response Body. |
Yes |
"owin.ResponseHeaders" |
An IDictionary<string, string[]> of response headers. See Headers. |
No |
"owin.ResponseStatusCode" |
An optional int containing the HTTP response status code as defined in RFC 2616 section 6.1.1. The default is 200. |
No |
"owin.ResponseReasonPhrase" |
An optional string containing the reason phrase associated the given status code. If none is provided then the server SHOULD provide a default as described in RFC 2616 section 6.1.1 |
No |
"owin.ResponseProtocol" |
An optional string containing the protocol name and version (e.g. " HTTP/1.0" or " HTTP/1.1" ). If none is provided then the“owin.RequestProtocol” key’s value is the default. |
Other Data:
Required |
Key Name |
Value Description |
Yes |
"owin.CallCancelled" |
A CancellationToken indicating if the request has been cancelled/aborted. See Request Lifetime. |
Yes |
"owin.Version" |
The string "1.0" indicating OWIN version. See Versioning. |
小结
这些规范看起来可能简单到微不足道,但OWIN的思想就是简单、灵活——通过要求OWIN中间件只依赖AppFun类型,为开发基于OWIN的中间件提供了的最低门槛。同时,通过使用环境字典在各个中间件之间进行信息的传递,而非传统ASP.NET(System.Web)中使用HttpContext贯穿ASP.NET整个生命周期来传递。
既然OWIN是规范,而非真正实现,所以是无法使用在项目中的,若要使用OWIN,必须要实现他,所以这也是接下来我想聊的,OWIN的实现:Katana 。