連載:コードから触るIIS 8

連載:コードから触るIIS 8

オート・スタートによるアプリケーションの初期化処理とメンテナンスページ

2013年10月25日

IISによるWebサイト起動時に何らかの処理を行うための方法を解説。また、メンテナンスページをIISにより実現する方法も紹介。

株式会社グラニ 田中 孝佳
  • このエントリーをはてなブックマークに追加

 今回はやや実践的な話題を扱う。Webサイト起動時に何らかの処理を行うための方法として、アプリケーションをオート・スタートさせたうえで、Application Initialization Module、もしくはクラスのプリロードの利用という2つの方法を紹介したい。また、併せてURL書き換えを使ったメンテナンスページの実装も紹介したいと思う。

 また、今回紹介しているコードを実行するには、第3回の記事のとおりにIISをセットアップして、Webサイトを作成しておいてほしい。

アプリケーションの初回アクセスを速くするために

 ASP.NETのアプリケーションをIIS上でWebサイトとして動かしている際に、ときどきアクセスの応答が遅いという事象に遭遇したことはないだろうか。設定にもよるが、アクセスが来ない間にWebサイトが休止状態にあり、再起動処理が走っていたり、初回アクセス時にコンパイルが走ったりするためである。

 コンパイルについては、ビューの部分をあらかじめプリコンパイルしておいたものをデプロイしておくことで対応が可能であったりするが、それでもWebサイトの起動時、つまり初めてのアクセスが来る前に何かしらの処理をしておきたいことはあるだろう。今回はそのようなユースケースのために、Application Initialization Moduleとクラスのプリロードという2つの方法を紹介する。

Application Initialization Moduleの利用

 Application Initialization Moduleを使って、Webサイトの起動時にページへのリクエストを内部的に発生させることによって、そのページにおける処理をあらかじめ実行しておくことができる。IIS 7.5では、外部モジュールとして提供されていたが、IIS 8で標準機能として提供されるようになった。第1回のセットアップの手順を行っていれば、IISの機能は全てインストールされているはずなので、確認してみよう。以下のPowerShellコードを実行してもらいたい。

PowerShell
Get-WindowsFeature -Name Web-AppInit
Application Initializationのインストール状態を確認するPowerShellコマンド

 下の画面のように「Installed」と表示されればインストールされている。

Application Initialization Moduleがインストールされている状態

サンプルアプリケーションの作成と実行

 では、実際に設定してみよう。今回は第3回の連載で作成したWebサイトを使うことにする。もしくは自分で作成したWebサイトやDefault Web Siteでも構わない。その場合は以下のコードで出てくるWebサイト名を適宜置き換えてほしい。

 まずは単純なASP.NETアプリケーションをWebサイトにデプロイしよう。

 以下のようにメッセージを表示するだけのアプリである。今回はASP.NET MVCで作成した。サンプル(.zipファイル内のAppInit_1フォルダー)GitHub上に用意しておいたので利用してほしい。

 IISマネージャーで、AppInit_1フォルダー内のWebサイトを追加する(本稿では「BuildInsider-pool」という名前のアプリケーションプールを作成し、その後、「BuildInsider」というサイト名で、先ほどのアプリケーションプールを指定したWebサイトを追加した。Webサイトの場所は「C:\www\BuildInsider」に配置した)。このWebサイトの実行を開始して、「http://localhost/」(=Webサイトを80番ポートでバインディングし、Webサイトを実行しているサーバーのブラウザーで表示する場合。以下同様)にアクセスできることを確認しておこう(次の画面)。

サンプルアプリケーションを表示させた状態

Application Initialization Moduleの設定: アプリケーション側の設定

 それでは、このアプリケーションにApplication Initialization Moduleの設定を追加しよう。

 まず、アプリケーション側の変更として、初期化処理中にアクセスがあった場合に表示するページを追加する。今回は「Loading.html」という静的ファイルをプロジェクトのルートに追加し、そのページを表示することにした。

 初期化処理についてだが、今回は「/Initialization/」という相対URLに対して内部的なGETアクセスを発生させるものとし、InitializationControllerクラスを追加する。このController内部では、単純に5秒待機する処理を記述した(次のコードを参照)。実際にはここに必要な初期化処理を記述することになるだろう。

C#
using System;
using System.Threading;
using System.Web.Mvc;

namespace AppInit.Controllers
{
  public class InitializationController : Controller
  {
    //
    // GET: /Initialization/

    public ActionResult Index()
    {
      Thread.Sleep(TimeSpan.FromSeconds(5));
      return View();
    }

  }
}
InitializationControllerクラスのコード(InitializationController.cs)

5秒待機した後、Viewを表示するコードである。

 さらに、アプリケーションの中にある、Web.configファイル内の<configuration>-<system.webServer>要素の中に、以下のコードで示す<applicationInitialization>要素を追加する。

XML
<system.webServer>
  <!-- 省略 -->

  <applicationInitialization remapManagedRequestsTo="Loading.html" skipManagedModules="true">
    <add initializationPage="/Initialization/"/>
  </applicationInitialization>

</system.webServer>
Application Initializationの設定を追加

 以上の設定を追加したサンプルを、先ほどダウンロードしていただいた.zipファイル内のAppInit_2フォルダーとして提供している。ここで再度、ビルド&デプロイしておく。

 アプリケーション側の設定が終わったら、IIS本体の設定を変更しよう。

Application Initialization Moduleの設定: IIS本体の設定

 これはIISの環境情報ファイルapplicationHost.config(通常、「C:\Windows\System32\inetsrv\config\applicationHost.config」に置かれている)の変更が必要になり、GUIツールであるIISマネージャーからは変更できない。そこで、C#コードから設定を変更してみよう。

 これには、ASP.NETアプリケーションとは別に設定変更用のソリューションを作成する。作成方法は第3回の記事を参考にして、ソリューションおよびプロジェクト名を「AppInitConfigure」とし、Microsoft.Web.Administration.dllを参照に追加してほしい。作成したら、以下のコードを記述してプログラムを実行しよう。このときアプリケーションプール名を指定する必要があるため、適宜各自の環境のプール名を指定してほしい。

C#
using Microsoft.Web.Administration;

static void Main(string[] args)
{
  // Webサイトの名前
  var siteName = "BuildInsider";

  // Webサイトで使うアプリケーションプールの名前
  var appPoolName = "BuildInsider-pool";

  using (var serverManager = new ServerManager())
  {
    var site = serverManager.Sites[siteName];
    var app = site.Applications["/"];
    app["preloadEnabled"] = true;

    // オート・スタートを適用する
    var mainApplicationPool = serverManager.ApplicationPools[appPoolName];
    mainApplicationPool["startMode"] = "AlwaysRunning";

    // 変更を確定
    serverManager.CommitChanges();
  }

}
Application Initializationの設定を行うコンソール・アプリケーションのプログラム・コード

 このコードの中で、ServerManagerオブジェクトのApplicationPoolsプロパティを使って、<applicationPools>要素に「startMode="AlwaysRunning"」という属性を追加している。この箇所が、IISがアプリケーションを最初にロードしたらすぐに起動するようにする「オート・スタート」を設定している場所である。

 このコードを実行したら、すぐにアプリケーションプールのリサイクルが実行されるはずである。すぐに、Webブラウザーで「http://localhost/」を見てみよう。もしくは、IISマネージャーからIISを再起動してからWebサイトを見てみよう。すると、Loading.htmlファイルに記述したコンテンツが表示されるはずである。初期化処理が終わるように5秒以上、待って、ブラウザーを更新すると、今度は設定する前に表示されていた元のコンテンツが表示されるはずである。

 Application Initialization Moduleを使うと、内部的にアクセスを発生させ、特定のページの処理を起動時に行えることが分かった。

Webサイトのオート・スタートとクラスのプリロード

 Application Initialization Moduleを使うと、設定した特定のWebページへのアクセスを内部的に発生させることができた。しかし、UI(ユーザーインターフェイス)を持たないようなアプリケーション、例えばWCFなどの場合では、アクセス対象として都合のいいページがないかもしれない。また、UIを持つASP.NETアプリケーションであっても、アプリケーションの設定であるマスターデータを起動時にキャッシュさせてメモリに乗せておきたい場合などでは、特定のページには関連しないクラスに初回起動処理を記述した方がコード内容として分かりやすいこともある。

 そこで次に、Webサイトのオート・スタート機能と組み合わせて、特定のクラスを起動時にプリロードさせる方法を紹介する。

特定のクラスを起動時にプリロードさせる方法: プリロードさせるクラスの記述

 プリロードさせるクラスは、IProcessHostPreloadClientインターフェイス(System.Web.Hosting名前空間)を実装させ、Preloadメソッドに初期化処理を記述する。なお、Preloadメソッドはstring型の配列をパラメーターとして引数に取るが、このパラメーターを外部から渡す方法は残念ながら確認できなかった。今回は、「PreWarmer」というクラスを(プロジェクト・ルートに新規.csファイルとして)作成し、HttpRuntimeクラス(System.Web名前空間)の静的プロパティCacheに、文字列のKey-Value値を入れておくことにした。併せて、これを表示するためにHomeController.csファイルとIndex.cshtmlファイルにコードを追加している(以下のコード)。

C#(PreWarmer.cs)
using System.Web;
using System.Web.Hosting;

namespace AppInit
{
  public class PreWarmer : IProcessHostPreloadClient
  {

    public void Preload(string[] parameters)
    {
      HttpRuntime.Cache["IsPreWarmed"] = "Pre-Warmed";
    }
  }
}
C#(HomeController.cs)
using System.Web;
using System.Web.Mvc;

namespace AppInit.Controllers
{
  public class HomeController : Controller
  {
    //
    // GET: /Home/

    public ActionResult Index()
    {
      ViewBag.PreWarmed = HttpRuntime.Cache["IsPreWarmed"] ?? "NOT Pre-Warmed";
      return View();
    }

  }
}
HTML(Index.cshtml)
@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Index</title>
</head>
<body>
  <div>
    ようこそ Build Insider のサンプルへ。<br/>
    このアプリのプリロードの状態: @ViewBag.PreWarmed
  </div>
</body>
</html>
プリロードして初期化処理するクラス(PreWarmer.cs)と、初期化処理でキャッシュに入れたデータを取得するコントローラークラス(HomeController.cs)と、ビュー(Index.cshtml)

 いったん、この状態でアプリをデプロイして実行してみてほしい。もしくは、ローカルの環境でデバッグ実行してほしい。次のように表示され、Preloadメソッドは実行されていないことが分かる。

ダウンロードしたサンプルのAppInit_2フォルダーのWebサイトをプリロードの設定をせずに実行して「http://localhost/」にアクセスした状態。

特定のクラスを起動時にプリロードさせる方法: IIS側の設定

 プリロードさせるクラスが記述できたら、IIS側の設定を行う。

 こちらもC#コードで設定してみよう。前掲の「Application Initializationの設定を行うコンソール・アプリケーションのプログラム・コード」を、以下のように変更して実行してみてほしい。

C#
using Microsoft.Web.Administration;

static void Main(string[] args)
{
  // Webサイトの名前
  var siteName = "BuildInsider";

  // Webサイトで使うアプリケーションプールの名前
  var appPoolName = "BuildInsider-pool";

  using (var serverManager = new ServerManager())
  {
    var site = serverManager.Sites[siteName];
    var app = site.Applications["/"];
    app["serviceAutoStartEnabled"] = true;
    app["serviceAutoStartProvider"] = "PreWarmerProvider";

    var serviceAutoStartProvidersSection = serverManager.GetApplicationHostConfiguration().GetSection("system.applicationHost/serviceAutoStartProviders");
    var serviceAutoStartProvidersCollection = serviceAutoStartProvidersSection.GetCollection();
    var addElement = serviceAutoStartProvidersCollection.CreateElement("add");
    // serviceAutoStartProviderで指定した名前
    addElement["name"] = @"PreWarmerProvider";
    // プリロード対象の「クラス名, アセンブリ名」
    addElement["type"] = @"AppInit.PreWarmer, AppInit";
    serviceAutoStartProvidersCollection.Add(addElement);

    // オート・スタートを適用する
    var mainApplicationPool = serverManager.ApplicationPools[appPoolName];
    mainApplicationPool["startMode"] = "AlwaysRunning";

    // 変更を確定
    serverManager.CommitChanges();
  }

}
プリロードする設定を行うコード

 アプリケーションをオート・スタートさせる設定は、Application Initialization Moduleのときと同じである。そのうえで、アプリケーションプールがどのアプリケーションをオート・スタートさせるかの設定を行うために、<application>構成にserviceAutoStartEnabled="true"属性を追加することで指定している。また、そのときにプリロードさせるクラスを指定するために、serviceAutoStartProvider="PreWarmerProvider"属性を追加し、<serviceAutoStartProviders>構成で「PreWarmerProvider」という名前の要素を定義し、その要素でプリロード対象のクラスをアセンブリ名とクラス名を指定している。

 上記のプログラムを実行して設定が完了したら、Webブラウザーで「/Home/」にアクセスしてみよう。次のように表示され、PreWarmerクラスのPreWarm処理が実行されていることが確認できる。

AppInit_2フォルダーのWebサイトを、プリロードの設定をした後で実行して、「http://localhost/Home/」にアクセスした状態

 なお、注意点として、プリロードクラスに記述されている初期化処理を実行している間、アプリケーションはリクエストを受け付けない。初期化処理に時間がかかる場合は、処理が完了するまでリクエストを受け付けない仕組みが必要である。

メンテナンスページの表示

 アプリケーションを更新する場合などに、アプリケーション本体はテストなどのために動作させたり停止させたりしたいが、ユーザーのリクエストに対してはメンテナンス中を知らせるページを表示させたい場合がある。例えば、上記のプリロードによる初期化処理を行うときに、初期化処理が完了してからメンテナンスを解除して公開したい、といった場合である。

 IISより上位のロード・バランサーなどで、そのような機能を持つものもあるが、IIS単体でも実現できる方法として、Application Request Routing(ARR)を使ってアプリケーションの手前にリバースプロキシさせるWebサイトを作成しておき、リバースプロキシする際にメンテナンスかどうかを示すフラグを持たせ、メンテナンス中であればURL書き換えにより静的なメンテナンスページを表示させる、という方法がある。この方法を紹介したい。

 今回は、リバースプロキシのWebサイトだけを再現する。

 まず、第2回の記事を参考に Application Request Routingのインストールをしてもらいたい。今まで使っていたWebサイト「BuildInsider」の中身を全て削除するが、同様のWebサイトをもう1つ作成してほしい。このWebサイトにメンテナンスの場合は静的ページを表示するようにする。

メンテナンス状態を判断するためのテキストファイルの作成

 メンテナンス状態かどうかの判断はファイルの有無で行うことにする。このWebサイト直下に「maintenance.txt」というファイルが存在すればメンテナンス中、存在しなければ(厳密には別名にリネームされていれば)メンテナンスではない、すなわち公開中とする。

 まずは空のファイルで構わないので、Webサイト直下に「maintenance.txt」というファイルを作成しよう。

メンテナンス中を表示する静的HTMLファイルの作成

 次に、メンテナンス中に表示する静的ページを作成する。これもWebサイト直下に「maintenance.html」というファイル名で作成しよう。

 中身はHTMLであれば何でもよい。以下にサンプルコードを示す。また、公開中の状態で表示するコンテンツのサンプルとして、「Index.html」ファイルも作成しておく。これもサンプルコードを示す。

HTML(maintenance.html)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <title>メンテナンス中</title>
</head>
<body>
  メンテナンス中です。しばらくお待ちください。
</body>
</html>
HTML(index.html)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <title>公開中</title>
</head>
<body>
  動いています。
</body>
</html>
メンテナンス中に表示するHTMLページのサンプル

URL書き換えルールの設定

 それでは、実際にURL書き換えのルールを設定しよう。これもC#によるプログラムで設定する(ここでも前掲の「Application Initializationの設定を行うコンソール・アプリケーションのプログラム・コード」を書き換えればよい)。

C#
using Microsoft.Web.Administration;
using System.IO;

static void Main(string[] args)
{
  // Webサイトの名前
  var siteName = "BuildInsider";

  // Webサイトのディレクトリ
  var siteDir = @"C:¥www¥BuildInsider";

  using (var serverManager = new ServerManager())
  {
    // Default Web Siteの設定を取得
    var config = serverManager.GetWebConfiguration(siteName);

    // URL書き換えルールの設定を取得
    var rulesSection = config.GetSection("system.webServer/rewrite/rules");
    var rulesCollection = rulesSection.GetCollection();

    // メンテナンスファイルがあればメンテナンスページへ書き換え
    var rule = rulesCollection.CreateElement("rule");
    rule["name"] = "Maintenance";
    rule["stopProcessing"] = true;

    // どのリクエストURLにマッチさせるかの<match>要素を設定
    var match = rule.GetChildElement("match");
    match["url"] = "(.*)";

    // メンテナンスファイルがあるという条件を追加
    var conditions = rule.GetCollection("conditions");
    var condition = conditions.CreateElement("add");
    condition["input"] = Path.Combine(siteDir, "maintenance.txt");
    condition["matchType"] = "IsFile";
    conditions.Add(condition);

    // どのURLに書き換えるかの<action>要素を設定
    var actionElement = rule.GetChildElement("action");
    actionElement["type"] = "Rewrite";
    actionElement["url"] = @"/maintenance.html";
    rulesCollection.Add(rule);

    serverManager.CommitChanges();
  }
}
URL書き換えのルールを設定するプログラムのコード

 URL書き換えのルールで、パスは正規表現により全てを対象とし、条件として指定したパスがファイルであるとき、というものを指定している。このパスを「maintenance.txt」の絶対パスを指定することで、このファイルが存在したときだけ、maintenance.htmlを表示するようにしている。

メンテナンスページ表示の実行結果

 それでは、Webブラウザーで「http://localhost/」にアクセスしてみよう。最初は、maintenance.txtファイルが存在しているので、maintenance.htmlの中身が表示されるはずだ(次の画面)。

maintenance.txtファイルが存在する状態でWebブラウザーでアクセスした状態 maintenance.htmlの内容が表示されている。

 次に、maintenance.txtファイルを「_maintenance.txt」などにリネームしてみよう。すると、index.htmlのサイトが表示されるはずだ(次の画面)。

maintenance.txtファイルが存在しない状態でWebブラウザーでアクセスした状態

index.htmlの内容が表示されている。

 これで、テキストファイルの有無をメンテナンス状態のON/OFFとして、メンテナンスページを表示できるようになった。

 次回は、IIS 8でWebSocketプロトコルをサポートしたSignalRについて紹介する。

※以下では、本稿の前後を合わせて5回分(第1回~第5回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

1. PowerShellによるIIS 8のインストール

Windows Server 2012に搭載されているWebサーバー「IIS 8」の機能を「コードから触って動かす」をテーマにした連載スタート。今回はPowerShellスクリプトでIISのセットアップなどを実行する方法を紹介する。

2. C#コードによるARR 3.0 RTMのインストールと設定

7月26日に正式版がリリースされた「ARR(Application Request Routing) 3.0」とは? そのARRを、C#コードによりインストール&設定する方法を説明する。

3. C#でアプリケーション・プールの設定とWebサイトの作成

IISでのWebサイトの作成をC#コードから行ってみよう。一連の開発手順とコードを分かりやすく解説。

4. 【現在、表示中】≫ オート・スタートによるアプリケーションの初期化処理とメンテナンスページ

IISによるWebサイト起動時に何らかの処理を行うための方法を解説。また、メンテナンスページをIISにより実現する方法も紹介。

5. IIS 8でさわるSignalR 2.0

SignalR 2.0をIIS 8(WebSocket)で動かす方法を説明。Windows Server/IIS、.NET/Visual Studioに関するアップデートについても簡単に紹介。

サイトからのお知らせ

Twitterでつぶやこう!