原文: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 |
可選項 |