https://msdn.microsoft.com/zh-cn/magazine/dn451439.aspx (Katana 项目入门)
一不小心写了个WEB服务器
快刀斩乱麻之 Katana
OWIN知识
OWIN的全称是Open Web Interface For .Net。
OWIN提供的只是一种规范,而没有具体实现。其目的是在web服务器和应用程序组件之间隔离出一个抽象层,使它们之间解耦。
应用程序委托和环境字典
OWIN将服务器与应用程序之间的交互减少到一小部分类型和单个函数签名,这个函数签名被称为应用程序委托(即 AppFunc):
using AppFunc = Func, Task>;
基于 OWIN 的应用程序中的每个组件都向服务器提供应用程序委托。 然后,这些组件链接成一个管道,基于 OWIN 的服务器将会向该管道推送请求。
一个符合OWIN的web服务器,需要将请求信息(应用程序状态、请求状态和服务器状态等所有相关信息)包装到一个字典里(应用程序委托上指定的 IDictionary
虽然任何键/值数据都可以插入到环境字典中,但 OWIN 规范为某些 HTTP 核心元素定义了键:
key | value |
"owin.RequestBody" | 一个带有请求正文(如果有)的流。如果没有请求正文,Stream.Null 可以用作占位符。 |
"owin.RequestHeaders" | 请求标头的 IDictionary |
"owin.RequestMethod" | 一个包含请求的 HTTP 请求方法的字符串(例如 GET 和 POST)。 |
"owin.RequestPath" | 一个包含请求路径的字符串。 此路径必须是应用程序委托的“根”的相对路径。 |
"owin.RequestPathBase" | 一个字符串,包含对应于应用程序委托的“根”的请求路径部分。 |
"owin.RequestProtocol" | 一个包含协议名称和版本的字符串(例如 HTTP/1.0 或 HTTP/1.1)。 |
"owin.RequestQueryString" | 一个字符串,包含 HTTP 请求 URI 的查询字符串组成部分,不带前导“?”(例如 foo=bar&baz=quux)。 该值可以是空字符串。 |
"owin.RequestScheme" | 一个字符串,包含用于请求的 URI 方案(例如 HTTP 或 HTTPS)。 |
随着请求在OWIN管道中流动,每个中间件(Middleware,集成到管道中的组件或应用程序)所要做的就是读取、修改这个字典的数据。最后,Web服务器得到这个层层处理过的字典,然后输出网页到客户端。
与ASP.NET管道相比,OWIN规范非常简洁,且并没有引用.Net Framework中的System.Web.dll。
1. 新的组件能够非常简单的开发和应用
2. 程序能够简便地在host和OS上迁移
Katana
Katana 项目是 Microsoft 创建和推出的基于 OWIN 的组件和框架集合。
https://katanaproject.codeplex.com
但上面这个项目最后一次提交是15年1月
目前它的托管代码已经被拆分:
New 'Katana' Source:
- https://github.com/aspnet/HttpAbstractions
- https://github.com/aspnet/Hosting
- https://github.com/aspnet/Security
- https://github.com/aspnet/StaticFiles
- ...
Katana 体系结构
- 主机:运行应用程序的进程,可以是从 IIS 或独立可执行文件到您自己的自定义程序的任何内容。 主机负责启动、加载其他 OWIN 组件和正常关闭。
- 服务器:负责绑定到 TCP 端口,构造环境字典和通过 OWIN 管道处理请求。
- 中间件:这是为处理 OWIN 管道中的请求的所有组件指定的名称。 它可以是从简单压缩组件到 ASP.NET Web API 这样的完整框架,不过从服务器的角度而言,它只是一个公开应用程序委托的组件。
- 应用程序:这是您的代码。 由于 Katana 并不取代 ASP.NET,而是一种编写和托管组件的新方式,因此现有的 ASP.NET Web API 和 SignalR 应用程序将保持不变,因为这些框架可以参与 OWIN 管道。 事实上,对于这些类型的应用程序,Katana 组件只需使用一个小的配置类即可见。
在体系结构方面,Katana 可以按其中每个层都能够轻松替代的方式分解,通常不需要重新生成代码。 在处理 HTTP 请求时,各个层一起工作,方式类似于下图中所示的数据流。
使用IIS做Host和Server
首先建立一个空的web应用程序。因为默认的 Katana 主机会在此 /bin 文件夹中查找程序集。而Web应用程序默认会将编译的程序集直接放在 /bin 文件夹而不是 /bin/debug 文件夹中。
删除无关文件,引入OWIN的支持包
添加Startup启动类
Startup类的作用是用来初始化OWIN管道,这里,我们添加和初始化OWIN管道中的Middleware.
在Startup.Configuration方法中,添加如下代码:
public class Startup { public void Configuration(IAppBuilder app) { // New code: app.Run(context => { context.Response.ContentType = "text/plain"; return context.Response.WriteAsync("Hello, world."+ context.Request.Uri); }); } }
上面的代码做的事情,就是把一个简单的Middleware注册到OWIN管道中。
其中context的类型是IOwinContext:
public interface IOwinContext { // Gets the Authentication middleware functionality available on the current request. IAuthenticationManager Authentication { get; } // Gets the OWIN environment. IDictionaryEnvironment { get; } // Gets a wrapper exposing request specific properties. IOwinRequest Request { get; } // Gets a wrapper exposing response specific properties. IOwinResponse Response { get; } // Gets or sets the host.TraceOutput environment value. TextWriter TraceOutput { get; set; } // Gets a value from the OWIN environment, or returns default(T) if not present. T Get (string key); // Sets the given key and value in the OWIN environment. IOwinContext Set (string key, T value); }
运行结果:
如图 可以顺利解析到不同的访问Url,自然也就可以在后续的处理中做出不同的处理,直接分支处理或读取静态文件或者实现MVC架构等等……。
改用其他形式的Host和Server
上例中IIS同时充当了Host和Server的角色,
首先创建一个简单的Console应用程序,用Nuget添加Microsoft.Owin.SelfHost
以同样的方式添加Startup启动类
将控制台程序改造为Host
class Program { static void Main(string[] args) { using (Microsoft.Owin.Hosting.WebApp.Start("http://localhost:9000")) { Console.WriteLine("Press [enter] to quit..."); Console.ReadLine(); } } }
运行结果:
Startup类
无论使用IIS, IIS Express还是OWIN Host, 微软在这些Host上实现的Service都会依照特定的规则来寻找到Startup类,执行Configuration方法,注册Middleware。
默认名称匹配
可以定义Startup.cs类,只要这个类的namespace和Assembly的名称相同。那么,这个Startup.cs中的Configuration方法,就会在OWIN管道初始化的时候执行。
使用OwinStartup Attribute
直接指定哪个具体类是Startup类。
在配置文件的appSetting 节点设置
路由配置
protected void Application_Start(object sender, EventArgs e) { // Registers a route for the default OWIN application. RouteTable.Routes.MapOwinPath("/owin"); // Invokes the System.Action startup delegate to build the OWIN application and // then registers a route for it on the given path. RouteTable.Routes.MapOwinPath("/special", app => { app.Run(OwinApp2.Invoke); }); }
public class OwinApp2 { // Invoked once per request. public static Task Invoke(IOwinContext context) { context.Response.ContentType = "text/plain"; return context.Response.WriteAsync("Hello World 2"); } }
自定义Middleware
通过继承OwinMiddleware基类可以便捷地新建中间件:
public class HelloWorldMiddleware : OwinMiddleware { public HelloWorldMiddleware(OwinMiddleware next) : base(next) { } public override Task Invoke(IOwinContext context) { var response = "Hello World! It is " + DateTime.Now; context.Response.Write(response); return Next.Invoke(context); } }
注册:
public class Startup { public void Configuration(IAppBuilder app) { app.Use(); } }
应用
ASP.NET Web Form,ASP.NET MVC5项目结合OWIN
由于ASP.NET Web Form和ASP.NET MVC5依赖于System.Web.dll中的很多类型,而在OWIN管道中,是无法提供这些依赖的。所以ASP.NET Web Form和ASP.NET MVC5不能作为一个中间件直接集成到OWIN管道中。
但在这些项目中,也可以添加Startup.cs, 指定成为OWIN的初始化类型,那么请求会先经过OWIN管道处理,最后转向ASP.NET Web Form或者ASP.NET MVC程序。这种方式,常常用来配置log, authentication, cache等等这些Middleware。
引入OWIN后的管道执行顺序
Web API作为Middleware注册到OWIN管道中
Web API由于无任何依赖于System.web.dll, 所以可以作为Middleware注册到OWIN管道中。
public class Startup { // Invoked once at startup to configure your application. public void Configuration(IAppBuilder builder) { HttpConfiguration config = new HttpConfiguration(); config.Routes.MapHttpRoute("Default", "api/{controller}/{customerID}", new { controller = "Customer", customerID = RouteParameter.Optional });//定义web api route //xml格式输出结果 config.Formatters.XmlFormatter.UseXmlSerializer = true; config.Formatters.Remove(config.Formatters.JsonFormatter); // config.Formatters.JsonFormatter.UseDataContractJsonSerializer = true; //将web api以Middleware注册到OWIN管道中 builder.UseWebApi(config); } }
ASP.NET 5
ASP.NET 5中终于去System.web.dll化,MVC,Web API都统一在了OWIN管道中。
ASP.NET 5 的 project.json 配置文件(Microsoft.Owin改做Microsoft.AspNet 了):
ASP.NET 5 中Startup.cs 的两个重要方法:
// This method gets called by the runtime. public void ConfigureServices(IServiceCollection services) { // Add EF services to the services container. services.AddEntityFramework(Configuration) .AddSqlServer() .AddDbContext(); // Add Identity services to the services container. services.AddDefaultIdentity (Configuration); // Add MVC services to the services container. services.AddMvc(); // Uncomment the following line to add Web API servcies which makes it easier to port Web API 2 controllers. // You need to add Microsoft.AspNet.Mvc.WebApiCompatShim package to project.json // services.AddWebApiConventions(); } // Configure is called after ConfigureServices is called. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory) { // Configure the HTTP request pipeline. // Add the console logger. loggerfactory.AddConsole(); // Add the following to the request pipeline only in development environment. if (string.Equals(env.EnvironmentName, "Development", StringComparison.OrdinalIgnoreCase)) { app.UseBrowserLink(); app.UseErrorPage(ErrorPageOptions.ShowAll); app.UseDatabaseErrorPage(DatabaseErrorPageOptions.ShowAll); } else { // Add Error handling middleware which catches all application specific errors and // send the request to the following path or controller action. app.UseErrorHandler("/Home/Error"); } // Add static files to the request pipeline. app.UseStaticFiles(); // Add MVC to the request pipeline. app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller}/{action}/{id?}", defaults: new { controller = "Home", action = "Index" }); // Uncomment the following line to add a route for porting Web API 2 controllers. // routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}"); }); }
ConfigureServices 在运行时的时候被运行,Configure 运行在 ConfigureServices 之后,查看 ConfigureServices 中的 Add 方法注释,你会发现最后一个单词总是 container(容器),这是怎么回事呢,其实就是往Ioc容器中注入类型依赖的对象,这些类型对象的管理都是在 Owin 管道中的,你只需要在 ConfigureServices 中使用 Add 方法注册相应模块就可以了,其他的东西 ASP.NET 5 会帮你完成,而 Configure 是什么作用呢?我自己觉得它是配置模块的一个“配置”,用户你使用中间件或者应用程序的一个配置,比如,你使用 app.UseCookieAuthentication 进行配置用户验证的一些操作,你查看 UseCookieAuthentication 的定义,会发现其命名空间为 Microsoft.AspNet.Builder.CookieAuthenticationExtensions,所在程序集为 CookieAuthenticationExtensions(Owin 中间件),查看 Configure 中其他 Use 使用,你同样会发现命名空间都是 Microsoft.AspNet.Builder 开头,之前说 Owin 是一种协定,Extensions 就是一种中间件和应用程序的扩展,但都必须符合此协定,这样才会有无限可能。