Привет, Хабр! ASP.NET Core поддерживает открытый веб-интерфейс для .NET (OWIN), а OWIN позволяет отвязывать веб-приложения от веб-серверов. Он определяет стандартный способ использования связующего ПО при обработке запросов и соответствующих ответов. Приложения ASP.NET Core и связующее ПО совместимы с приложениями, серверами и связующим ПО на базе OWIN. Подробнее об этой паре читайте под катом.



Просмотрите или скачайте образец кода

Выполнение связующего ПО в процессе ASP.NET


Поддержка OWIN со стороны ASP.NET Core развертывается в рамках пакета Microsoft.AspNetCore.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 с помощью метода расширения 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.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();
        }
    }
}

IServer — это интерфейс, который требует свойство Features и метод Start.

Start отвечает за настройку и запуск сервера. Для этого используется серия вызовов API, настраивающих адреса, которые были проанализированы из IServerAddressesFeature. Обратите внимание: конфигурация переменной _builder указывает, что запросы будет обрабатывать параметр appFunc, ранее настроенный в методе. Эта функция вызывается по каждому запросу для обработки входящих запросов.

Также мы добавим расширение 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();
        }
    }
}

Затем необходимо вызвать расширение в Program.cs, чтобы выполнить приложение ASP.NET с помощью этого пользовательского сервера:

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-серверы в ASP.NET Core — получить доступ к функциям типа WebSockets. Веб-сервер .NET OWIN из предыдущего примера поддерживает встроенные веб-сокеты, которые можно использовать в приложении ASP.NET Core. В примере ниже показано простое веб-приложение, которое поддерживает веб-сокеты и возвращает отправителю все данные, отправленные на серверы через веб-сокеты.

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. Тест с помощью простого клиента веб-сокета демонстрирует приложение:



Среда OWIN


Среду OWIN можно создать с помощью HttpContext.

var environment = new OwinEnvironment(HttpContext);
   var features = new OwinFeatureCollection(environment);

Ключи OWIN


Для передачи информации через обмен данными HTTP-запрос/ответ OWIN необходим объект IDictionary<string,object>. ASP.NET Core реализует ключи, указанные ниже. См. основную спецификацию, расширения и Основные правила работы с OWIN.

Запрос данных (OWIN v1.0.0)

Ключ Значение (тип) Описание
owin.RequestScheme Строка
owin.RequestMethod Строка
owin.RequestPathBase Строка
owin.RequestPath Строка
owin.RequestQueryString Строка
owin.RequestProtocol Строка
owin.RequestHeaders IDictionary<string,string[]>
owin.RequestBody Поток

Запрос данных (OWIN v1.1.0)

Ключ Значение (тип) Описание
owin.RequestId Строка Необязательно

Ответные данные (OWIN v1.0.0)

Ключ Значение (тип) Описание
owin.ResponseStatusCode int Необязательно
owin.ResponseReasonPhrase Строка Необязательно
owin.ResponseHeaders IDictionary<string,string[]>
owin.ResponseBody Поток

Другие данные (OWIN v1.0.0)

Ключ Значение (тип) Описание
owin.CallCancelled CancellationToken
owin.Version Строка

Общие ключи

Ключ Значение (тип) Описание
ssl.ClientCertificate X509Certificate
ssl.LoadClientCertAsync FuncTask
server.RemoteIpAddress Строка
server.RemotePort Строка
server.LocalIpAddress Строка
server.LocalPort Строка
server.IsLocal bool
server.OnSendingHeaders ActionActionobject,object

SendFiles v0.3.0

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

Opaque v0.3.0

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

WebSocket v0.3.0

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

Дополнительные ресурсы


Комментарии (9)


  1. RomanGL
    06.12.2017 13:11
    +2

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

    Это законно, делать что-то в методе после return? Кажется, у вас ошибочка.


    1. RouR
      06.12.2017 20:20

      Скорее всего речь о том, что потом будет выполнен следующий пайплайн, в котором будет «do something after»


    1. uterr
      06.12.2017 20:43
      +1

      этот кусок кода создает анонимную функцию и передает ее как параметр в функцию pipeline, а не вызывает тело функции


  1. unsafePtr
    06.12.2017 15:34
    +1

    Что интересно, по факту есть всего несколько имплементаций под OWIN интерфейс а именно Kestrel и Katana(IIS). Может кто знает проекты или попытки написать свой сервер под OWIN?


    1. stepank
      06.12.2017 15:57
      +2

      Katana — это не только IIS, есть ещё self host вариант, пакет Microsoft.Owin.SelfHost


  1. TakinosaJi
    06.12.2017 18:33

    Зачем использовать OWIN как middleware с Core, когда ASP.NET Core и есть middleware?


    1. caballero
      07.12.2017 04:58

      Проект OWIN проще переносить со стандартного фреймворка на Core и наоборот Другое дело — зачем в Core вообще изобретали своe middleware a не взяли за основу OWIN и не путали людей похожими интерфейсами.


      1. dotnetdonik
        10.12.2017 12:51

        Что бы развивать asp.net core быстрее чем стандарт. Owin это просто интерфейс/адаптер, через которые можно подключать больше компонентов совместимых с owin спецификацией. Middleware это просто ещё один способ обозвать всем известный паттерн chain of responsibility, он был и до появления owin — в веб формах, классическом mvc(http modules), в web api — message handlers/delegating handlers.


  1. kefirr
    07.12.2017 00:07

    project.json

    Это мёртвый механизм, зачем его упоминать? От него отказались в пользу csproj:
    https://stackoverflow.com/questions/38536978/is-project-json-deprecated
    https://docs.microsoft.com/en-us/dotnet/core/tools/project-json-to-csproj