読者です 読者をやめる 読者になる 読者になる

しばやん雑記

ASP.NET とメイドさんが大好きなフリーランスのプログラマーのブログ

Azure Web Apps に Flat-File CMS な Grav をインストールする

Azure Web Apps PHP

f:id:shiba-yan:20160722232417p:plain

Azure Web Apps 上に Umbraco をインストールして shibayan.jp を公開してましたが、PHP 製の Flat-File CMS な Grav に移行したので簡単に手順を残しておきます。

Grav - A Modern Flat-File CMS | Grav

Grav は YAML と Markdown だけで書いていけるので、覚えることが少なくて簡単です。そして標準で IIS への対応ファイルが入っているので、とてもインストールは簡単です。

本体のダウンロード

f:id:shiba-yan:20160722232817p:plain

Downloads | Grav

ダウンロードページは Grav Core と Admin Plugin が含まれた 2 つが公開されてますが、後から Admin Plugin はインストールできるので、管理画面が必要という以外は Grav Core を選べばいいです。

Web Apps の準備

Grav は実行に PHP 5.5.9 以上が必要になるので、Azure Web Apps のデフォルト設定のままだと動作しないので、作成したら真っ先にバージョンを上げておきましょう。折角なので PHP 7 にしました。

f:id:shiba-yan:20160722233114p:plain

設定はたったのこれだけで終わりです。必要に応じて Always On を有効にしても良いと思います。

ソース管理を使う

Grav はファイルを置けばページが完成しますが、シンプルな分だけバージョン管理的なものが用意されてないです。なので基本的には Git などと組み合わせて使うのが便利だと思いました。

公式ブログでも Azure Web Apps に Bitbucket からデプロイする方法が紹介されてます。

手順はこっちを参照して貰えれば大体 OK だと思います。ざっくり説明するとダウンロードした zip を展開して Git に突っ込んでコミットするだけです。.gitignore の設定だけ気を付けたいです。

展開すると webserver-configs というディレクトリがあるはずなので、Web.config をコピーして 1 階層上に持って行きます。そうしないと静的ファイル系が全滅します。

f:id:shiba-yan:20160723000226p:plain

ここまで終われば、あとはデプロイして URL を叩くと勝手にインストールされます。

実際に shibayan.jp は GitHub の Private Repository から Azure にデプロイしています。

f:id:shiba-yan:20160722232729p:plain

Web Apps は直近 10 コミットまではポータルから簡単に戻すことが出来るので、間違った内容をコミットしてしまった時でもすぐに対応が出来るの安心ですね。

f:id:shiba-yan:20160722232318p:plain

まだ Grav 使い始めなので、あまりコミットは行っていないです。今後頑張ります。

Kudu / FTP を使う

ソース管理が必要ないという場合は、直接 Kudu の Debug Console や FTP を使って展開したファイルを wwwroot にコピーするだけで、同様にインストールを行うことが出来ます。

Web Apps のストレージは多重化されているので、履歴が要らないという場合には選択肢の一つとしてありかもしれないです。手軽ですし。

コンテンツを編集する

これで Grav のセットアップは完了なので、あとは site.yaml を編集したり、実際に Markdown でページを作成していく形になりますが、shibayan.jp では Visual Studio Code を使っています。

IIS Express と組み合わせることで、簡単にローカルでもページを確認できるので、安心して GitHub にコミットしてデプロイという流れを実行できます。

Visual Studio Code と IIS Express を使って PHP 7 の実行環境を整える

Visual Studio IIS

これまで Umbraco で管理してた shibayan.jp を、もっと軽量で書きやすい Flat-file CMS である Grav に移行するために、Visual Studio Code と IIS Express で快適にローカルで確認できる環境を作ろうと思いました。

少し前まではこういう場合 WebMatrix を使っていましたが、公式サイトを久しぶりに見ると Visual Studio Code を全力でお勧めしていました。

f:id:shiba-yan:20160722013520p:plain

手軽だったので少し寂しいですが、Visual Studio Code の方が機能豊富で使いやすいので仕方ないですね。

しかし Visual Studio Code でも IIS Express を使ってコード書けるようにしたいわけです。Grav については今回紹介しないですが、気が向けばインストール方法とか書くかもしれません。まずは環境を作ります。

PHP 7 をインストール

Web PI を使うと IIS Express 向けの各 PHP ランタイムをインストール出来るので、必要なランタイムを選んでインストールします。今回は PHP 7 の x86 版を選びました。

f:id:shiba-yan:20160721234348p:plain

同時に WinCache が入りますが、自動で設定されるので特に気にする必要はないです。PHP 7 のインストールはたったこれだけで完了です。Web PI は便利ですね。

IIS Express 拡張をインストール

f:id:shiba-yan:20160721234549p:plain

IIS Express | Visual Studio Marketplace

IIS Express 拡張をインストールすると、コマンドパレットから IIS Express の起動と終了が実行出来るようになります。IIS と入力すると候補が出てくるので簡単ですね。

f:id:shiba-yan:20160721234655p:plain

まず Run Website を選ぶと、さらにメニューが続く形になっているので最初は戸惑いました。

f:id:shiba-yan:20160721234730p:plain

これで IIS Express の Start と Stop が出来るようになりましたが、このままだと PHP アプリケーションが動かないので、FastCGI とハンドラーの設定を追加します。

applicationHost.config を修正

WebMatrix を使っていた場合にはドキュメント以下に IIS Express ディレクトリが作られて、そこに applicationHost.config が存在していますが この拡張は Application Server Mode で IIS Express を起動するので、設定場所が少し異なっています。

Application Server Mode については以下のブログにしか書いてない気がします。貴重な記事です。

Working with the Different IIS Express Modes and HTTPS – Robert McMurray's Blog [MSFT]

とりあえず PHP 7 を動かすためには FastCGI の設定と php 拡張子に対するハンドラーの追加だけです。このあたりは PHP 公式サイトに情報が載っているので参考にして進めます。

PHP: Windows 上での PHP の手動インストール - Manual

PHP 7 for IIS Express は Program Files の IIS Express 以下にランタイムがインストールされるので、PHP のパスは読み替える必要があります。

Application Server Mode では IIS Express 以下の AppServer\applicationhost.config が使われるので、appcmd のパラメータでこの config を編集対象にします。管理者権限が必要なので少し注意です。

REM Clear current PHP handlers
appcmd clear config /section:system.webServer/fastCGI /apphostconfig:"C:\Program Files (x86)\IIS Express\AppServer\applicationhost.config"
appcmd set config /section:system.webServer/handlers /-[name='PHP_via_FastCGI'] /apphostconfig:"C:\Program Files (x86)\IIS Express\AppServer\applicationhost.config"

REM Set up the PHP handler
appcmd set config /section:system.webServer/fastCGI /+[fullPath='"C:\Program Files (x86)\IIS Express\PHP\v7.0\php-cgi.exe"'] /apphostconfig:"C:\Program Files (x86)\IIS Express\AppServer\applicationhost.config"
appcmd set config /section:system.webServer/handlers /+[name='PHP_via_FastCGI',path='*.php',verb='*',modules='FastCgiModule',scriptProcessor='"C:\Program Files (x86)\IIS Express\PHP\v7.0\php-cgi.exe"',resourceType='Unspecified'] /apphostconfig:"C:\Program Files (x86)\IIS Express\AppServer\applicationhost.config"
appcmd set config /section:system.webServer/handlers /accessPolicy:Read,Script /apphostconfig:"C:\Program Files (x86)\IIS Express\AppServer\applicationhost.config"

REM Configure FastCGI Variables
appcmd set config -section:system.webServer/fastCgi /+[fullPath='"C:\Program Files (x86)\IIS Express\PHP\v7.0\php-cgi.exe"'].instanceMaxRequests:10000 /apphostconfig:"C:\Program Files (x86)\IIS Express\AppServer\applicationhost.config"
appcmd set config -section:system.webServer/fastCgi /+[fullPath='"C:\Program Files (x86)\IIS Express\PHP\v7.0\php-cgi.exe"'].environmentVariables.[name='PHP_FCGI_MAX_REQUESTS',value='10000'] /apphostconfig:"C:\Program Files (x86)\IIS Express\AppServer\applicationhost.config"
appcmd set config -section:system.webServer/fastCgi /+[fullPath='"C:\Program Files (x86)\IIS Express\PHP\v7.0\php-cgi.exe"'].environmentVariables.[name='PHPRC',value='"C:\Program Files (x86)\IIS Express\PHP\v7.0\php.ini"'] /apphostconfig:"C:\Program Files (x86)\IIS Express\AppServer\applicationhost.config"

実行すると PHP 7 の実行に必要な設定が書き込まれます。

今後のアップデートでカスタムの applicationHost.config を指定できるようになるかもしれないですが、今はこれしか方法が無いと思います。アップデートに期待したいところです。

IIS Express でサイトを起動

全ての設定が完了した後に、コマンドパレットから Start Website を実行すると IIS Express が起動されて、自動的にブラウザが立ち上がります。

f:id:shiba-yan:20160721234924p:plain

ちゃんと PHP で書かれている Grav も実行できました。shibayan.jp は GitHub でファイルを管理して、Web App と連携してあるので git push すると数秒後に反映されるようになってます。

なので、自分のマシン上でどのように見えるのか、事前に確認しながら書ける環境が必要なのでした。

開発環境でも ASP.NET Core MVC の RequireHttps 属性を使えるようにする

ASP.NET Core MVC

ASP.NET MVC の RequireHttps 属性を使うと、HTTP で来た場合には HTTPS にリダイレクトしてくれるのですが、開発環境で 443 以外のポートを使っている場合、正しく動作しないという欠点がありました。

[RequireHttps]
public ActionResult Index()
{
    return View();
}

本番環境などの 80 / 443 で動作している場合には正しく動作しますが、IIS Express ではポートが動的に割り当てられるので URL のスキーマを変えるだけだとアクセス出来なくなります。

aspnetwebstack/RequireHttpsAttribute.cs at master · ASP-NET-MVC/aspnetwebstack · GitHub

実装を見てもスキーマを HTTPS に変えているだけなので、MVC 5 までは対応方法が 80 / 443 で動かすしかありませんでしたが、ASP.NET Core MVC ではオプションで SSL のポートを渡せるようになりました。

Core MVC のオプションは AddMvc の引数に指定したデリゲートの中で行います。

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc(options =>
    {
        options.SslPort = 44357;
    });
}

SslPort というプロパティが用意されているので、開発環境で使用する SSL のポートを指定します。

ちなみに ASP.NET Core アプリケーションで SSL を有効にするには、アプリケーションのプロパティを開いて Web サーバーの設定から行います。IIS Express の場合のみ設定可能です。

f:id:shiba-yan:20160718011838p:plain

これで終わればいいのですが、ConfigureService の中に SSL のポート番号を直接書くのは、開発環境以外で問題になります。そして ConfigureService では IHostingEnvironment を受け取れないので、環境ごとに設定を切り替えることはこのままでは無理です。

方法はいくつかあると思いますが、分かりやすそうな 2 つの方法を紹介しておきます。

appsettings.Development.json に定義して読み込む

SSL のポート番号は基本的に開発環境でしか指定することはないと思うので、appsettings.Development.json を追加してその中にポート番号を追加しておきます。

f:id:shiba-yan:20160718013525p:plain

開発環境のみでしか読み込まれないファイルなので、他の環境には影響しません。

そして ConfigureServices では Configuration を使ってポート番号を読み込み、SslPort に設定します。

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc(options =>
    {
        options.SslPort = Configuration.GetValue<int?>("SslPort", null);
    });
}

キーが存在しない場合には null が返されるので、デフォルトの 443 が使われます。

launchSettings.json を直接読み込む

Visual Studio で設定した Web サーバー周りの設定は Properties 以下に保存されている launchSettings.json に書き込まれているので、このファイルを読み込んでしまえば、SSL のポート番号を直接取得できます。

f:id:shiba-yan:20160718013715p:plain

開発環境のみで読み込む必要があるので、IsDevelopment を使って環境を判別する必要があります。

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
        .AddEnvironmentVariables();

    if (env.IsDevelopment())
    {
        builder.AddJsonFile("Properties/launchSettings.json", optional: true);
    }

    Configuration = builder.Build();
}

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc(options =>
    {
        options.SslPort = Configuration.GetValue<int?>("iisSettings:iisExpress:sslPort", null);
    });
}

読み込んでしまえば、あとは同じように SslPort に設定するだけです。GetSection や GetValue は : を使うとネストされていても 1 回で読み込めるので結構便利です。

f:id:shiba-yan:20160718014955p:plain

リダイレクトを確認してみると、SSL のポート番号がちゃんと反映されていることが確認できます。

そして今回の話とは関係ないですが、MVC 5 の RequireHttps には GET 以外で来た場合に例外を投げるので 500 エラーになるという、非常にいやらしい問題もあります。

ASP.NET Core MVC では、この問題も例外を投げるのではなく 403 を返すことで対応されました。

Mvc/RequireHttpsAttribute.cs at dev · aspnet/Mvc · GitHub

全体的に Core MVC では MVC 5 までの問題のある挙動が、大幅に改善されているように感じます。存在しないルーティングで 500 を返すのではなく、404 を返すのもその一つです。

とりあえず意図しない入力があった場合に、無条件で例外を投げていた MVC 5 が悪いだけですが。