Open Web Interface for .NET (OWIN)

Steve Smith и Rick Anderson

ASP.NET Core поддерживает OWIN, (Открытый веб интерфейс для .NET, Open Web Interface for .NET), который позволяет отделять приложения от веб серверов. Кроме того, OWIN определяет стандартный способ использования связующего ПО в потоке для обработки отдельных запросов и соответствующих ответов. ASP.NET Core приложения и связующее ПО могут работать с приложениями, серверами и связующим ПО, основанными на OWIN.

Просмотрите или скачайте пример с GitHub.

Запуск связующего ПО OWIN в потоке ASP.NET

ASP.NET Core поддерживает OWIN, и это является частью пакета Microsoft.AspNet.Owin. Вы можете импортировать OWIN в проект, добавив этот пакет в качестве зависимости в файл project.json:

  },

  "dependencies": {
    "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
    "Microsoft.AspNetCore.Owin": "1.0.0"

Связующее ПО OWIN согласуется с OWIN спецификацией, требующей интерфейс Func<IDictionary<string, object>, Task>, а также нам требуется настроить некоторые ключи (например, owin.ResponseBody). Вот простой пример, который следует OWIN спецификации, для отображения “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);
}

В примере выше возвращается Task и принимается IDictionary<string, object>, как и требует OWIN.

В следующем примере показано, как добавить OwinHello в поток ASP.NET с помощью метода расширения :dn:method:`~Microsoft.AspNetCore.Builder.OwinExtensions.UseOwin`.

public void Configure(IApplicationBuilder app)
{
    app.UseOwin(pipeline =>
    {
        pipeline(next => OwinHello);
    });
}

Конечно, вы можете совершать и другие действия в потоке OWIN.

Примечание

Заголовки ответа нужно менять до первой записи в поток ответа.

Примечание

Не стоит вызывать несколько раз UseOwin по соображениям производительности. OWIN компоненты работают лучше всего, если их сгруппировать вместе.

app.UseOwin(pipeline =>
{
    pipeline(next =>
        {
            // do something before
            return OwinHello;
            // do something after
        });
});

Использование ASP.NET хостинга на сервере, основанном на OWIN

Серверы OWIN могут размещать ASP.NET приложения. Одним таким сервером является Nowin, .NET OWIN веб сервер. В пример для этой статьи я включил проект, в котором используется Nowin для создания IServer, который сам может хостить ASP.NET Core.

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 отвечает за настройку и запуск сервера, что в данном случае реализуется путем вызовов текущего API, который устанавливает адреса, полученные от IServerAddressesFeature. Обратите внимание, что конфигурация переменной _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();
        }
    }
}
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 Серверы.

Запуск ASP.NET Core на сервере, основанном на OWIN, и использование поддержки WebSockets

С помощью OWIN серверов вы можете получить доступ к таким элементам, как WebSockets. У .NET OWIN веб сервера, который использовался в предыдущем примере, есть встроенная поддержка WebSockets, которые работают с ASP.NET Core приложением. В примере снизу вы увидите простое приложение, которое поддерживает WebSockets и просто отображает то, чтобы было отправлено на сервер, с помощью WebSockets.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
{
    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 показывает, что приложение работает, как и ожидалось:

../_images/websocket-test.png

Ключи OWIN

OWIN сильно зависит от IDictionary<string,object>, который перегоняет информацию через HTTP запросы/ответы. ASP.NET Core реализует все требуемые и дополнительные ключи. См primary specification, extensions, and 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 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>  

SendFiles v0.3.0

Ключ Значение (тип) Описание
sendfile.SendAsync См. сигнатура делегата В зависимости от запроса

Opaque v0.3.0

Ключ Значение (тип) Описание
opaque.Version String  
opaque.Upgrade OpaqueUpgrade См. сигнатура делегата
opaque.Stream Stream  
opaque.CallCancelled CancellationToken  

WebSocket v0.3.0

Ключ Значение (тип) Описание
websocket.Version String  
websocket.Accept WebSocketAccept См. сигнатура делегата.
websocket.AcceptAlt   Не указано
websocket.SubProtocol String См. RFC6455 Section 4.2.2 Шаг 5.5
websocket.SendAsync WebSocketSendAsync См. сигнатура делегата.
websocket.ReceiveAsync WebSocketReceiveAsync См. сигнатура делегата.
websocket.CloseAsync WebSocketCloseAsync См. сигнатура делегата.
websocket.CallCancelled CancellationToken  
websocket.ClientCloseStatus int Дополнительный
websocket.ClientCloseDescription String Дополнительный
Поделись хорошей новостью с друзьями!
Следи за новостями!