原文:Open Web Interface for .NET (OWIN)
作者:Steve Smith、 Rick Anderson
翻译:谢炀(kiler398)
校对:孟帅洋(书缘)
ASP.NET Core 支持 OWIN(即 Open Web Server Interface for .NET 的首字母缩写),OWIN的目标是用于解耦Web Server和Web Application。此外, OWIN为中间件定义了一个标准方法用来处理单个请求以及相关联的响应。ASP.NET Core 的程序和中间件可以和 OWIN-based 应用程序、服务器以及中间件相互交互。
章节:
- 在 ASP.NET 管道中运行 OWIN 中间件
- 在基于 OWIN 的服务器上宿主 ASP.NET
- 在 OWIN-based 服务器上运行 ASP.NET Core 并使用 WebSockets 支持
- OWIN 键值
- 附录资源
在 ASP.NET 管道中运行 OWIN 中间件
ASP.NET Core 对于 OWIN 的支持基于 Microsoft.AspNetCore.Owin
包。你可以在你的应用程序把这个包作为一个依赖导入到你的 project.json 文件里来实现对 OWIN 支持, 如下所示:
"dependencies": {
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
"Microsoft.AspNetCore.Owin": "1.0.0" //手动高亮
},
OWIN 中间件遵循 OWIN 标准, OWIN 标准定义了一系列 Func<IDictionary<string, object>, Task>
需要用到的属性接口, 并且规定了某些键值必须被设置 (例如 owin.ResponseBody
)。 下面的简单的中间件的例子来显示 "Hello World":
public Task OwinHello(IDictionary<string, object> environment)
{
string responseText = "Hello World via OWIN";
byte[] responseBytes = Encoding.UTF8.GetBytes(responseText);
// OWIN Environment Keys: http://owin.org/spec/spec/owin-1.0.0.html
var responseStream = (Stream)environment["owin.ResponseBody"];
var responseHeaders = (IDictionary<string, string[]>)environment["owin.ResponseHeaders"];
responseHeaders["Content-Length"] = new string[] { responseBytes.Length.ToString(CultureInfo.InvariantCulture) };
responseHeaders["Content-Type"] = new string[] { "text/plain" };
return responseStream.WriteAsync(responseBytes, 0, responseBytes.Length);
}
OWIN 最简单的方法签名是接收一个 IDictionary<string, object>
输入参数并且返回 Task
结果。
添加 OWIN 中间件到 ASP.NET 管道是最简单的办法是使用 UseOwin
扩展方法完成。参考上面所示的 OwinHello
方法,将它添加到管道是一个简单的事情:
public void Configure(IApplicationBuilder app)
{
app.UseOwin(pipeline =>
{
pipeline(next => OwinHello);
});
}
当然你也可以在 OWIN 管道中配置其他 actions 来替代。
注意
响应头信息只能在第一次写入响应流的时机之前修改。
注意
因为性能原因同时调用多个UseOwin
是不被鼓励的。 OWIN 组件如果能组合在一起将运作是最好的。
app.UseOwin(pipeline =>
{
pipeline(next =>
{
// do something before
return OwinHello;
// do something after
});
});
在基于 OWIN 的服务器上宿主 ASP.NET
基于 OWIN 的服务器可以宿主 ASP.NET 应用程序,Nowin
就是其中之一,一个.NET 的 OWIN Web 服务器。在本文的例子中,我已经包含一个非常简单的项目并引用 Nowin 并用它来创建一个能够自托管 ASP.NET 核心的一个简单的服务器。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Owin;
using Microsoft.Extensions.Options;
using Nowin;
namespace NowinSample
{
public class NowinServer : IServer //手动高亮
{
private INowinServer _nowinServer;
private ServerBuilder _builder;
public IFeatureCollection Features { get; } = new FeatureCollection();
public NowinServer(IOptions<ServerBuilder> options)
{
Features.Set<IServerAddressesFeature>(new ServerAddressesFeature());
_builder = options.Value;
}
public void Start<TContext>(IHttpApplication<TContext> application)
{
// Note that this example does not take into account of Nowin's "server.OnSendingHeaders" callback.
// Ideally we should ensure this method is fired before disposing the context.
Func<IDictionary<string, object>, Task> appFunc = async env =>
{
// The reason for 2 level of wrapping is because the OwinFeatureCollection isn't mutable
// so features can't be added
var features = new FeatureCollection(new OwinFeatureCollection(env));
var context = application.CreateContext(features);
try
{
await application.ProcessRequestAsync(context);
}
catch (Exception ex)
{
application.DisposeContext(context, ex);
throw;
}
application.DisposeContext(context, null);
};
// Add the web socket adapter so we can turn OWIN websockets into ASP.NET Core compatible web sockets.
// The calling pattern is a bit different
appFunc = OwinWebSocketAcceptAdapter.AdaptWebSockets(appFunc);
// Get the server addresses
var address = Features.Get<IServerAddressesFeature>().Addresses.First();
var uri = new Uri(address);
var port = uri.Port;
IPAddress ip;
if (!IPAddress.TryParse(uri.Host, out ip))
{
ip = IPAddress.Loopback;
}
_nowinServer = _builder.SetAddress(ip)
.SetPort(port)
.SetOwinApp(appFunc)
.Build();
_nowinServer.Start();
}
public void Dispose()
{
_nowinServer?.Dispose();
}
}
}
IServer
是一个需要 Features
属性和 Start
方法的接口。
Start
的职责是配置和启动服务器,在本示例中是通过一系列 fluent API 调用IServerAddressesFeature硬编码服务器地址来监听请求。注意 fluent 的 builder
变量指定了请求会被方法 appFunc
所处理。Func
方法在每一个请求被处理前调用。
我们同样会添加 IWebHostBuilder
扩展来使得 Nowin 服务器易于添加和配置。
using System;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.Extensions.DependencyInjection;
using Nowin;
using NowinSample;
namespace Microsoft.AspNetCore.Hosting
{
public static class NowinWebHostBuilderExtensions
{
public static IWebHostBuilder UseNowin(this IWebHostBuilder builder) //手动高亮
{
return builder.ConfigureServices(services =>
{
services.AddSingleton<IServer, NowinServer>();
});
}
public static IWebHostBuilder UseNowin(this IWebHostBuilder builder, Action<ServerBuilder> configure)
{
builder.ConfigureServices(services =>
{
services.Configure(configure);
});
return builder.UseNowin();
}
}
}
上述操作就绪以后,所有的需要使用自定义服务器运行 ASP.NET 应用程序的设置都在下面的 project.json 文件的命令中:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
namespace NowinSample
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseNowin() //手动高亮
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
了解更多关于 ASP.NET Servers
。
在 OWIN-based 服务器上运行 ASP.NET Core 并使用 WebSockets 支持
如何基于OWIN的服务器功能,可以通过ASP.NET核心加以利用另一个例子是获得像WebSockets的功能。在前面的例子中使用的.NET OWIN Web服务器具有内置的网络插座,可通过一个ASP.NET的核心应用加以利用的支持。下面的例子显示了支持网络套接字和简单的回显然后直接通过WebSockets发送到服务器的任何一个简单的Web应用程序。
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
if (context.WebSockets.IsWebSocketRequest) //手动高亮
{ //手动高亮
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(); //手动高亮
await EchoWebSocket(webSocket); //手动高亮
}
else
{
await next();
}
});
app.Run(context =>
{
return context.Response.WriteAsync("Hello World");
});
}
private async Task EchoWebSocket(WebSocket webSocket)
{
byte[] buffer = new byte[1024];
WebSocketReceiveResult received = await webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer), CancellationToken.None);
while (!webSocket.CloseStatus.HasValue)
{
// Echo anything we receive
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, received.Count),
received.MessageType, received.EndOfMessage, CancellationToken.None);
received = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer),
CancellationToken.None);
}
await webSocket.CloseAsync(webSocket.CloseStatus.Value,
webSocket.CloseStatusDescription, CancellationToken.None);
}
}
}
这个 例子 和前一个配置一样使用相同 NowinServer
唯一的区别是在该应用程序是如何在其 Configure
方法是如何配置的。用 一个简单的 websocket 客户端的演示实际效果:
OWIN 键值
OWIN 依赖一个 IDictionary<string,object>
对象用来在一个完整的 HTTP 请求/响应交互中通讯信息。ASP.NET Core 实现所有的 OWIN 规范中列出的要求的必需和可选的以及自身实现的键。在OWIN规范不要求任何键是可选的,并且可以仅在某些情况下可以使用。 在使用 OWIN 键的时候,参阅 OWIN Key Guidelines and Common Keys 是一个好习惯。
请求数据 (OWIN v1.0.0)
键 | 值 (类型) | 描述 |
---|---|---|
owin.RequestScheme | String |
|
owin.RequestMethod | String |
|
owin.RequestPathBase | String |
|
owin.RequestPath | String |
|
owin.RequestQueryString | String |
|
owin.RequestProtocol | String |
|
owin.RequestHeaders | IDictionary<string,string[]> |
|
owin.RequestBody | Stream |
请求数据 (OWIN v1.1.0)
键 | 值 (类型) | 描述 |
---|---|---|
owin.RequestId | String |
可选项 |
响应数据 (OWIN v1.0.0)
键 | 值 (类型) | 描述 |
---|---|---|
owin.ResponseStatusCode | int |
可选项 |
owin.ResponseReasonPhrase | String |
可选项 |
owin.ResponseHeaders | IDictionary<string,string[]> |
|
owin.ResponseBody | Stream |
其他数据 (OWIN v1.0.0)
键 | 值 (类型) | 描述 |
---|---|---|
owin.CallCancelled | CancellationToken |
|
owin.Version | String |
通用键值
键 | 值 (类型) | 描述 |
---|---|---|
ssl.ClientCertificate | X509Certificate |
|
ssl.LoadClientCertAsync | Func<Task> |
|
server.RemoteIpAddress | String |
|
server.RemotePort | String |
|
server.LocalIpAddress | String |
|
server.LocalPort | String |
|
server.IsLocal | bool |
|
server.OnSendingHeaders | Action<Action<object>,object> |
发送文件 v0.3.0
键 | 值 (类型) | 描述 |
---|---|---|
sendfile.SendAsync | 参考 delegate signature | 每请求 |
Opaque v0.3.0
键 | 值 (类型) | 描述 |
---|---|---|
opaque.Version | String |
|
opaque.Upgrade | OpaqueUpgrade |
参考 delegate signature |
opaque.Stream | Stream |
|
opaque.CallCancelled | CancellationToken |
WebSocket v0.3.0
键 | 值 (类型) | 描述 |
---|---|---|
websocket.Version | String |
|
websocket.Accept | WebSocketAccept |
参考 delegate signature |
websocket.AcceptAlt | 没有规定 | |
websocket.SubProtocol | String |
参考 RFC6455 Section 4.2.2 Step 5.5 |
websocket.SendAsync | WebSocketSendAsync | 参考 delegate signature |
websocket.ReceiveAsync | WebSocketReceiveAsync | 参考 delegate signature |
websocket.CloseAsync | WebSocketCloseAsync | 参考 delegate signature |
websocket.CallCancelled | CancellationToken |
|
websocket.ClientCloseStatus | int |
可选项 |
websocket.ClientCloseDescription | String |
可选项 |