Tag Archives: OWIN

Helios を使うとワーカースレッド数が制限されていた話

まだ、Version 1.0.0 – alpha 1 ですが、Project Helios を使った Web アプリケーションでは、リクエストの実行に使用するワーカースレッドと完了ポートスレッドの数が制限されていました。

気が付いたきっかけは、Azure Web サイトにデプロイして動かした際に、「System.InvalidOperationException:There were not enough free threads in the ThreadPool to complete the operation.」の例外が発生したことでした。

次のようなテスト用の Web API を Azure Web サイトにデプロイして、確認してみました。

public class Test
{
    public int MaxWorkerThreads { get; set; }
    public int MaxCompletionPortThreads { get; set; }
    public int ProcessorCount { get; set; }
}

[Route("api/test")]
public class TestController : ApiController
{
    public IHttpActionResult Get()
    {
        int workerThreads = 0;
        int completionPortThreads = 0;
        ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);

        int processorCount = Environment.ProcessorCount;

        return base.Ok(new Test {
                            MaxWorkerThreads = workerThreads,
                            MaxCompletionPortThreads = completionPortThreads,
                            ProcessorCount = processorCount });
    }
}
無料・共有(Heliosなし) 無料・共有(Heliosあり) 基本・標準(Heliosなし) 基本・標準(Heliosあり)
MaxWorkerThreads 8191 16 8191 2
MaxIoThreads 1000 16 1000 2
ProcessorCount 8 8 1 1

基本と標準は、Sインスタンスです。上記の結果の通り、Helios を使うと、論理コア数あたり2個が設定されています。なお、Visual Studio 14 CTP 4 の ASP.NET vNext でも、Helios と同様の値が設定されていました。

ワーカースレッド数を変更するとしたら、こちらの推奨値を参考にすると、このような感じでしょうか?

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        int processorCount = Environment.ProcessorCount;
        ThreadPool.SetMaxThreads(100 * processorCount, 100 * processorCount);

        var config = new HttpConfiguration();
        config.MapHttpAttributeRoutes();
        app.UseWebApi(config);

        app.UseWelcomePage();
    }
}

Helios の正式版がリリースされる可能性は低いですが、ASP.NET vNext は今後のアップデートで変わってくると思いますので、あくまで現時点の情報ということになります。

 

 

OWIN で Nancy を使うときに必要な追加実装

今更感がありますが、OWIN で Nancy を使って Web API を実装してみました。その際に、少しハマったことがありますので、共有しておきます。

プロジェクトの作成

空の WebApplication プロジェクトに NuGet パッケージをインストールします。

  • Install-Package Nancy.Owin
  • Install-package Microsoft.Owin.Host.SystemWeb
  • Install-Package Nancy.Serialization.JsonNet

最後のパッケージは必須ではありませんが、Json.NET を使いたかったので、追加しています。このパッケージを追加しなければ、Nancy 独自の Json シリアライザが使われます。

Module の実装

“/api/person” の URL にリクエストすると、Json 形式の Person オブジェクトを返す Web API を実装します。

using Nancy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Nancy.ModelBinding;

namespace WebApplication2
{
    public class Person
    {
        public int ID { get; set; }
        public string Name { get; set; }
    }

    public class PersonModule : NancyModule
    {
        public PersonModule()
            : base("/api/person")
        {
            Get["/"] = _ =>
            {
                var result = new List<Person>();
                result.Add(new Person { ID = 10, Name = "Jack Wilshere" });
                result.Add(new Person { ID = 16, Name = "Aaron Ramsey" });
                return Response.AsJson(result);
            };

            Get["/{id}"] = parameters =>
            {
                var person = new Person { ID = parameters.id, Name = "Aaron Ramsey" };
                return Response.AsJson(person);
            };

            Post["/"] = _ =>
            {
                var person = this.Bind<Person>();
                return Response.AsJson(person);
            };

            Put["/{id}"] = parameters =>
            {
                var person = this.Bind<Person>();
                return Response.AsJson(person);
            };

            Delete["/{id}"] = parameters =>
            {
                var id = parameters.id;
                return HttpStatusCode.OK;
            };
        }
    }
}

OWIN パイプラインに Nancy を追加

Startup クラスで、OWIN パイプラインに Nancy を追加します。

using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(WebApplication2.Startup))]

namespace WebApplication2
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseNancy();
        }
    }
}

ここまでの実装で動作確認すると、GET と POST は成功するのですが、PUT と DELETE で 404 エラーが返されてしまいます。

GitHub の Wiki をよく読むと、OWIN 上で Nancy を動かすには、追加実装が必要でした。

  • Startup クラスに、Microsoft.Owin.Extensions 名前空間の UseStageMarker メソッドを追加
  • Web.config に、runAllManagedModulesForAllRequests を追加
public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.UseNancy();
        app.UseStageMarker(PipelineStage.MapHandler);
    }
}
<system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
</system.webServer>

上記の実装を追加することで、PUT と DELETE のリクエストが成功します。

OWIN の Middleware をサブディレクトリに適用する

先週、OWINって何?Microsoft MVPに聞いてみよう! に参加してきました。サブディレクトリに、OWIN の Middleware を適用する方法を教えて頂いたので、試してみました。Web API へのリクエストだけに ロギング Middleware を追加するシナリオで実装しました。

ロギングの Middleware をクラスライブラリに実装する

ClassLibrary プロジェクトに NuGet パッケージをインストールします。

  • Install-Package Microsoft.Owin

ロギングの Middleware を実装します。

public class CustomLoggerMiddleware : OwinMiddleware
{
    public CustomLoggerMiddleware(OwinMiddleware next)
        : base(next)
    {

    }

    public override async Task Invoke(IOwinContext context)
    {
        Trace.TraceInformation("Web API Start");
        await base.Next.Invoke(context);
        Trace.TraceInformation("Web API End");
    }
}

Invoke の前後にトレースを入れるだけのシンプルなロギングです。

Webアプリケーションを実装する

空のWebApplication プロジェクトに NuGet パッケージをインストールします。

  • Install-Package Microsoft.AspNet.WebApi.Owin
  • Install-package Microsoft.Owin.Host.SystemWeb

OWIN パイプラインに Middleware を追加します。

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.Map("/api", subApp =>
        {
            subApp.Use<CustomLoggerMiddleware>();

            var config = new HttpConfiguration();
            config.MapHttpAttributeRoutes();
            subApp.UseWebApi(config);
        });
    }
}

引数の app の Use メソッドに Middleware を追加すると、すべてのリクエストに対して Middleware が動きます。IAppBuilder の拡張メソッドである Map メソッドを使っているのがポイントです。subApp の Use メソッドに Web API と CustomLogger の Middleware を追加することで、”/api” から始まる URL のリクエストに対して Middleware が動きます。

Web API の Controller を追加します。

// φ(..) "api/test" では、404 になるので注意!
[Route("test")]
public class TestController : ApiController
{
    // GET api/test
    public string Get()
    {
        Trace.TraceInformation("TestController.Get()");
        return "Hello OWIN !";
    }
}

GETで “api/test” にリクエストすると、「Hello OWIN !」を返すシンプルな Web API です。Route 属性で “api/test” と設定すると、404 エラーが発生して Get メソッドを呼び出せません。“/api” から始まる URL のリクエストに対して Web API が追加されているので、”/api” を省略して “test” と設定する必要があります。

実行結果

GETで “api/test” にリクエストすると、Visual Studio の出力ウィンドウには、以下のようにログが表示されます。

logger

まとめ

OWIN の複数の Middleware を組み合わせたときに、機能の重複を回避したり、特定のリクエストのみに限定したい場合などは、この方法が使えそうです。ただ、今回のシナリオのように、Web API のリクエストだけをロギングするのであれば、OWIN の Middleware でなく、Web API の ActionFilter で作るのもアリです。

OWIN で ASP.NET Web API を Windows Azure Active Directory 認証する

前回の投稿では OWIN でマルチホストしましたが、今回はミドルウェアの Windows Azure Active Directory(WAAD)認証ライブラリを利用して、ASP.NET Web API を OAuth 2.0 認証してみます。元ネタはこちらですが、Visual Studio のテンプレート機能を使わずに、手動で構築します。

また、ネイティブ クライアント アプリからの認証では、Active
Directory Authentication Library(ADAL)がいい感じなので、使ってみました。ADAL は、WAAD 向けの認証ライブラリで、以前は Windows Azure Authentication Library として提供されていたライブラリが名称変更されています。

Web API アプリを作成

Web アプリのテンプレートから Empty を選択し、Web API のみチェックして WebApiApp プロジェクトを新規作成します。GETで”api/test”にリクエストすると、ユーザー名を返すシンプルな Web API を追加し、忘れずに Authorize 属性を付けます。デバッグ実行し、レスポンスに 401 が返ることを確認しつつ、localhost のポート番号をメモしておきます。

[Authorize]
public class TestController : ApiController
{
    public string Get()
    {
        return base.User.Identity.Name;
    }
}

Web API アプリを WAAD に登録

ポータルから WAAD に、Web API アプリを追加します。ここでは、WAAD のドメイン名を「example.onmicrosoft.com」とします。新規に WAAD を作成する場合、一度作成すると削除できないようなので、ご注意ください。ウィザード画面で必要な情報を入力します。

add-webapp

ここでは、以下のように設定しました。

  • NAME:WebApiApp
  • SIGN-ON URL:http://localhost:61249/
  • APP ID URI:https://example.onmicrosoft.com/WebApiApp
  • Directory access:SINGLE SIGN-ON

Web API アプリに WAAD 認証を追加

WebApiApp プロジェクトに NuGet パッケージをインストールします。

  • Install-package Microsoft.Owin.Host.SystemWeb
  • Install-package Microsoft.Owin.Security.ActiveDirectory

OWIN パイプラインに WAAD 認証のライブラリを追加します。Audience には、ポータルに登録した WebApiApp の APP ID URI を設定します。

[assembly: OwinStartup(typeof(WebApiApp.Startup))]
namespace WebApiApp
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseWindowsAzureActiveDirectoryBearerAuthentication(
                new WindowsAzureActiveDirectoryBearerAuthenticationOptions
                {
                    Audience = "https://example.onmicrosoft.com/WebApiApp",
                    Tenant = "example.onmicrosoft.com"
                });
        }
    }
}

ネイティブ クライアント アプリを WAAD に登録

ポータルから WAAD に、ネイティブ クライアント アプリを追加します。ウィザード画面で必要な情報を入力します。

add-wpfapp

ここでは、以下のように設定しました。

  • NAME:WpfApp
  • REDIRECT URI:https://example.onmicrosoft.com/WpfApp

WpfApp を登録後、さきほど登録した WebApiApp との関連付けを行っておきます。

webapis

ネイティブ クライアント アプリを作成

WPFアプリとして、WpfApp プロジェクトを新規作成します。ネイティブ クライアント アプリの認証では、Webブラウザコントロールを利用した面倒な実装が必要となるのですが、Active Directory Authentication Libraryを使うと、このあたりの実装をライブラリが行ってくれます。WpfApp プロジェクトに NuGet パッケージをインストールします。

  • Install-package Microsoft.IdentityModel.Clients.ActiveDirectory
  • Install-Package Microsoft.AspNet.WebApi.Client

ボタンクリックイベントで WAAD 認証して、WebAPI を呼び出すシンプルなアプリです。9行目のコードで ADAL が認証してくれますので、取得した Bearer トークンをHTTPヘッダーに設定すれば OK です。WpfApp の Client ID は、ポータルから取得できます。

private async void Button_Click(object sender, RoutedEventArgs e)
{
    var authority = "https://login.windows.net/example.onmicrosoft.com";
    var appId = "https://example.onmicrosoft.com/WebApiApp";                // WebApiApp の APP ID URI
    var clientId = "c3d3af4a-1682-4e49-abd8-4a8e580cebf1";                   // WpfApp の Client ID
    var redirectUri = new Uri("https://example.onmicrosoft.com/WpfApp"); // WpfApp の REDIRECT URI

    // Windows Azure Active Directory 認証
    var authRes = new AuthenticationContext(authority).AcquireToken(appId, clientId, redirectUri);

    // 取得したトークンで、Web API を呼び出し
    var client = new HttpClient();
    client.DefaultRequestHeaders.Add(HttpRequestHeader.Authorization.ToString(), authRes.CreateAuthorizationHeader());
    var result = await client.GetStringAsync("http://localhost:61249/api/test");
    this.textBlock1.Text = result;
}

実行結果

まず、ネイティブ クライアント アプリからサインインするユーザーを、ポータルから登録しておきます。ここでは、WAAD のユーザー名を「tony@example.onmicrosoft.com」とします。

WpfApp のボタンをクリックすると、サインイン画面が表示され、認証に成功すると WebAPI から結果を取得できます。

signin

wpfresult

まとめ

クラウドで Web API を公開するなら、セキュリティは避けては通れない道です。OWIN のミドルウェアで提供されるフレームワークやライブラリを自由に組み合わせて、Web アプリを構築できる時代が始まっているのだと実感しました。

OWIN で ASP.NET Web API をマルチホストする

OWIN を少し理解できたので、ASP.NET Web API をマルチホストしてみました。コンソールアプリ、IIS の Webアプリ、Windows Azure の WebRole の3つでホスティングします。

ソリューション構成は、こんな感じです。

solution

Web API のロジックをクラスライブラリにする

WebApiLibrary プロジェクトに NuGet パッケージをインストールします。

  • Install-Package Microsoft.AspNet.WebApi.Owin

GETで”api/test”にリクエストすると、「Hello OWIN !」を返すシンプルな Web API です。

public class TestController : ApiController
{
    public string Get()
    {
        return "Hello OWIN !";
    }
}
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

コンソールアプリでホストする

ConsoleApplication プロジェクトに NuGet パッケージをインストールします。

  • Install-Package Microsoft.AspNet.WebApi.Owin
  • Install-Package Microsoft.Owin.Host.HttpListener
  • Install-Package Microsoft.Owin.Hosting

OWIN パイプラインに Web API を追加します。

[assembly: OwinStartup(typeof(ConsoleApplication.Startup))]
namespace ConsoleApplication
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var config = new HttpConfiguration();
            WebApiConfig.Register(config);
            app.UseWebApi(config);
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        using (WebApp.Start("http://localhost:8080/"))
        {
            Console.WriteLine("Press Enter to quit.");
            Console.ReadLine();
        }
    }
}

IIS の Webアプリでホストする

空のWebApplication プロジェクトに NuGet パッケージをインストールします。

  • Install-Package Microsoft.AspNet.WebApi.Owin
  • Install-package Microsoft.Owin.Host.SystemWeb

OWIN パイプラインに Web API を追加します。

[assembly: OwinStartup(typeof(WebApplication.Startup))]
namespace WebApplication
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var config = new HttpConfiguration();
            WebApiConfig.Register(config);
            app.UseWebApi(config);
        }
    }
}

Windows Azure の WebRole でホストする

上記の WebApplication と同じです。

実行結果

Google Chrome アプリの Postman で実行結果を確認してみます。

result

コンソールアプリ以外のURLでも、同じ結果が返されます。

まとめ

OWIN を利用することで、何でホストしても画一的な実装で書けるのは、大きなメリットです。サーバーとアプリを抽象化するだけでなく、ミドルウェアとして機能を追加できるところも面白いです。Web API 以外では、SignalR や Nancy のほかに、認証系のライブラリもありますし、第3のホストライブラリである Helios にも注目したいです。これからの One ASP.NET を語るうえで、OWIN は欠かせない技術であることは間違いなく、今後の進化に期待が高まります。