ASP.NET Core.NET开放Web接口(OWIN)

原文:Open Web Interface for .NET (OWIN)
作者:Steve SmithRick 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 中间件

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.RequestSchemeString
owin.RequestMethodString
owin.RequestPathBaseString
owin.RequestPathString
owin.RequestQueryStringString
owin.RequestProtocolString
owin.RequestHeadersIDictionary<string,string[]>
owin.RequestBodyStream

请求数据 (OWIN v1.1.0)

响应数据 (OWIN v1.0.0)

owin.ResponseStatusCodeint可选项
owin.ResponseReasonPhraseString可选项
owin.ResponseHeadersIDictionary<string,string[]>
owin.ResponseBodyStream

其他数据 (OWIN v1.0.0)

owin.CallCancelledCancellationToken
owin.VersionString

通用键值

ssl.ClientCertificateX509Certificate
ssl.LoadClientCertAsyncFunc<Task>
server.RemoteIpAddressString
server.RemotePortString
server.LocalIpAddressString
server.LocalPortString
server.IsLocalbool
server.OnSendingHeadersAction<Action<object>,object>

发送文件 v0.3.0

Opaque v0.3.0

opaque.VersionString
opaque.UpgradeOpaqueUpgrade参考 delegate signature
opaque.StreamStream
opaque.CallCancelledCancellationToken

WebSocket v0.3.0