Windows Azure 上で F# の Web ロールを実行

Windows Azure 上で単純な F# の Web ロール用アプリを実行してみました。

id:fits:20100906 の F# による ASP.NET のサンプルをそのまま使ってみるつもりだったのですが、Compute Emulator でのページ表示時に以下のようなエラーが発生し、解決策が見つからなかったので、今回は動的コンパイルを諦めて F# のソース部分を事前にビルドする方法をとりました。(FSharp.Core.dll と同じ場所に FSharp.Core.sigdata 等を配置してみても駄目でした)

コンパイル エラー メッセージ: 0: error FS1221: FSharp.Core.sigdata not found alongside FSharp.Core

今回使用した環境は以下の通りです。

また、今のところ Windows Azure では .NET Framework 2.0 がデフォルトで使われるようなので、.NET Framework 4.0 を使うには以下のようなファイルが必要になる点に注意が必要です。

properties.txt (.NET Framework のバージョンを指定する設定ファイル)
TargetFrameWorkVersion=v4.0

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20110417/

アプリケーション作成

まず、F# でページのイベント処理を定義します。

DefaultPage.fs (ページのイベント処理)
namespace Fits.Sample

open System
open System.Web
open System.Web.UI
open System.Web.UI.WebControls

type DefaultPage() =
    inherit Page()

    [<DefaultValue>] val mutable InfoText : TextBox
    [<DefaultValue>] val mutable InfoButton : Button
    [<DefaultValue>] val mutable InfoLabel : Label

    //ページロード時の処理
    member this.Page_Load(sender : obj, e : EventArgs) =
        this.InfoLabel.Text <- "hello"

    //ボタンクリック時の処理
    member this.InfoButton_Click(sender : obj, e : EventArgs) = 
        this.InfoLabel.Text <- "入力: " + this.InfoText.Text

次は、ASP.NET ページです。F# の動的コンパイルは使わない(使えなかった)ので Language を C# にし、F# で定義した Fits.Sample.DefaultPage を Inherits に指定しています。

Default.aspx (ページの内容)
<%@ Page Language="C#" AutoEventWireup="true" Inherits="Fits.Sample.DefaultPage"%>
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>F# Sample</title>
</head>
<body>
  <form runat="server">
    <div>
      <asp:TextBox runat="server" id="InfoText" />
      <asp:Button runat="server" id="InfoButton" text="Button" onClick="InfoButton_Click" />
    </div>
    <div>
      <asp:Label runat="server" id="InfoLabel" />
    </div>
  </form>
</body>
</html>

あとは、クラウドアプリケーション用の設定ファイルを作成します。

サービス定義ファイルは以下のようにしました。ポート番号は 8080 を使うように設定しています。

ServiceDefinition.csdef(サービス定義ファイル)
<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="Fits Sample F# ASP.net" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
  <WebRole name="WebRole1">
    <Endpoints>
      <InputEndpoint name="HttpIn" protocol="http" port="8080" />
    </Endpoints>
  </WebRole>
</ServiceDefinition>

ちなみに、上記のように 要素が未定義だと cspack の実行時に Warning CloudServices078 : ・・・ と表示されますが、今回は無視しています。

サービス設定ファイルは以下の通りです。Web ロールのインスタンス数を 1 にしています。

ServiceConfiguration.cscfg (サービス設定ファイル)
<?xml version="1.0" encoding="utf-8"?>
<ServiceConfiguration serviceName="Fits Sample F# ASP.net" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="1" osVersion="*">
  <Role name="WebRole1">
    <Instances count="1" />
  </Role>
</ServiceConfiguration>

最後に、.NET Framework 4 を使用するためのファイルも作成しておきます。

properties.txt(設定ファイル)
TargetFrameWorkVersion=v4.0

なお、今回は F# の動的コンパイルを使わないので、Web.config は特に設定する必要ありません。

ビルド

DefaultPage.fs をビルドして bin ディレクトリに配置します。(.NET 4 用の fsc.exe を使う点に注意)

DefaultPage.fs のビルド
> fsc DefaultPage.fs /target:library /out:bin\Fits.Sample.dll

bin ディレクトリに F# 2.0 November 2010 Community Technology Preview 内の v4.0\bin\gac\FSharp.Core.dll ファイルをコピーします。(必要に応じて他のアセンブリも bin ディレクトリに配置します)

ファイル構成は以下のようになります。

ファイル構成
  • fsharp_aspnet
    • bin
      • Fits.Sample.dll
      • FSharp.Core.dll
    • Default.aspx
    • properties.txt
    • ServiceConfiguration.cscfg
    • ServiceDefinition.csdef
    • Web.config

なお、サービス定義ファイル(ServiceDefinition.csdef)やサービス設定ファイル(ServiceConfiguration.cscfg)を別のディレクトリに配置してあっても特に問題ありません。

Compute Emulator 上での実行

それでは、ローカル環境の Compute Emulator 上で実行してみます。

まず、cspack.exe を使って Compute Emulator 用にパッケージングします。
/role でロールの指定、/rolePropertiesFile で設定ファイル(properties.txt)の指定を行います。

  • /role:ロール名;ディレクトリ
  • /rolePropertiesFile:ロール名;ファイル

/copyonly を指定する事で zip 化されずに、パッケージング用のディレクトリ・ファイル構成がそのまま残ります。

なお、cspack はアプリ用に作成したディレクトリの上位ディレクトリで実行します。

Compute Emulator 用のパッケージング実行
> cd ..
> cspack fsharp_aspnet\ServiceDefinition.csdef /role:WebRole1;fsharp_aspnet /out:output /rolePropertiesFile:WebRole1;fsharp_aspnet\properties.txt /copyonly

Warning  CloudServices078 : The web role 'WebRole1' is configured using
 a legacy syntax that specifies that it runs in Hostable Web Core. 
・・・

Warning が表示されますが、今回は無視します。

次に、csrun.exe を使って Compute Emulator 上でアプリを実行します。

Compute Emulator 上での実行
> csrun /run:output;fsharp_aspnet\ServiceConfiguration.cscfg

Windows(R) Azure(TM) Desktop Execution Tool version 1.4.0.0
for Microsoft(R) .NET Framework 3.5
Copyright (c) Microsoft Corporation. All rights reserved.

Using session id 1
Created: deployment(18)
Started: deployment(18)
Deployment input endpoint HttpIn of role WebRole1 at http://127.0.0.1:8080/.

http://127.0.0.1:8080/ にアクセスすればページが表示されます。

クラウド上での実行

それでは、クラウド環境用にパッケージングします。
Compute Emulator の時との違いは /copyonly をつけずに、/out で出力ファイルを指定する点です。

クラウド用パッケージング
> cspack fsharp_aspnet\ServiceDefinition.csdef /role:WebRole1;fsharp_aspnet /out:fsharp_aspnet.cspkg /rolePropertiesFile:WebRole1;fsharp_aspnet\properties.txt

Windows Azure 上で新規の Hosted Service を作成し、上記で作成されたパッケージファイルとサービス設定ファイル(ServiceConfiguration.cscfg)をアップロードします。

アップロードが完了し Web ロールのステータスが Ready になった後、http://xxxxx.cloudapp.net:8080/ にアクセスすればページが表示されます。(ステージング環境の場合、xxxxx には Azure で設定された Guid を指定します)

なお、Web ロールのステータスが Initializing(初期化中)から Ready に変わるまで、思ったよりもずっと時間がかかるのでご注意ください。