Worker Role で Perl を実行する方法があったので、Web Role でも試してみました。

Running Perl scripts in Windows Azure is trivial
http://blogs.msdn.com/b/chgeuer/archive/2010/08/30/running-perl-scripts-in-windows-azure-is-trivial.aspx

 

Perl は Azure で(まだ?)サポートされていないみたいなので、Perlを処理する カスタム HTTP ハンドラを作って使います。また、Perl は XCopy で動く Strawberry Perl を使用しています。

1.「CGI Web Role」を選択して、プロジェクトを作成します。


2.Strawberry Perl をダウンロードして展開し、プロジェクトにコピーします。

Strawberry Perl
http://strawberryperl.com/
※今回は、strawberry-perl-5.12.1.0.zip を使用しています。


3.カスタム HTTP ハンドラの作成と設定

新しいプロジェクトとして クラスライブラリ(MyHandlers)を追加して、参照設定に System.Web を追加します。

作ったプロジェクトに Perl を処理する カスタム Handler クラス(PerlHandler)を追加します。

using System;
using System.Diagnostics;
using System.Web;

namespace MyHandlers
{
    public class PerlHandler : IHttpHandler
    {
        public bool IsReusable
        {
            get
            {
                return true;
            }
        }
	
	
        public void ProcessRequest(HttpContext context)
        {
            try
            {
                string fn = context.Server.MapPath("~/Perl/bin/perl.exe");
	
                ProcessStartInfo psi = new ProcessStartInfo();
	
                // Perl実行の設定
                psi.FileName = fn;
                psi.RedirectStandardInput = true;
                psi.RedirectStandardOutput = true;
                psi.UseShellExecute = false;
                psi.CreateNoWindow = true;
                // エンコード指定しないと日本語で文字化け
                psi.StandardOutputEncoding = System.Text.Encoding.UTF8;
	
                // 実行するPerlファイルを渡す
                string path = context.Request.PhysicalPath;
                psi.Arguments = string.Format("\"{0}\"", path);
	
                System.Diagnostics.Process p = System.Diagnostics.Process.Start(psi);
	
                // 結果取得
                string results = p.StandardOutput.ReadToEnd();
	
                p.WaitForExit();
	
                //出力された結果を表示
                context.Response.ContentType = "text/html; charset=utf-8";
	
                context.Response.Write(results);
	
            }
            catch (Exception ex)
            {
                context.Response.ContentType = "text/html; charset=utf-8";
                context.Response.Write(ex.ToString());
            }
        }
    }
}

CGI Web Role の参照設定にクラスライブラリのプロジェクトを追加します。

CGI Web Role の Web.config にある handlers に作成したハンドラを追加します。

	<system.webServer>
		<handlers>
      <add name="Perl" verb="*" path="*.cgi" type="MyHandlers.PerlHandler, MyHandlers" resourceType="Unspecified"/>
         </handlers>
	</system.webServer>

 
4.CGI Web Role に実行する Perl のプログラムを追加します。

例)hello.cgi

print "こんにちわ<br />";
exit;

そのままだと拡張子 cgi はコピーされないので、ファイルのプロパティを下記のように修正します。

出力ディレクトリにコピー 新しい場合はコピーする
ビルドアクション コンテンツ

 
上記で、動かすことはできました。

20100903-01

Worker Role で C# WebServer と C# FTP Server を使ってみたので、二つを組み合わせてホスティングっぽく使ってみます。

1.「ASP.NET Web Role」プロジェクトを作成します。

 

2.Worker ロールのプロパティページを開き、「Settings」「Endpoints」「Local Srorage」に設定を追加します。

[Settings]
今回は、StorageConnectionString という名前で追加しています。

[Endpoints]
WorkerRole に Web(Name=WebEndpoint) と FTP(Name=FtpEndpoint) で使う2つのエンドポイントを追加します

[Local Srorage]
今回は、LocalStorage1 という名前で、サイズを1,000MBで追加しています。

 

3.HttpServer.dll, Assemblies.Ftp.dll, Assemblies.General.dll と Microsoft.WindowsAzure.CloudDrive.dll を参照設定に追加します。

 

4.OnStart に CloudStorageAccount の設定を追加します。内容は、Worker Role で C# FTP Server を使う と同じです。

 

5.後は 前回記載した FTP Server のときのコードに、前々回の WebServer のコードを Windows Azure Drive からファイルを読むように少し変更したものを追加するだけです。

        private Assemblies.Ftp.FtpServer ftpServer = null;
        
        public override void Run()
        {
            // 仮想ディスク作成
            const int DRIVE_SIZE = 1000;
            const int CACHE_SIZE = 500;
            string driveName = "WebAndFtp.vhd";
            string driveLetter = string.Empty;
            CloudStorageAccount account = CloudStorageAccount.FromConfigurationSetting("StorageConnectionString");
        
            // コンテナがなかったら作成
            string containerName = "vhd";
            CloudBlobClient client = account.CreateCloudBlobClient();
            CloudBlobContainer container = client.GetContainerReference(containerName);
            container.CreateIfNotExist();
        
            // 仮想ディスク作成
            LocalResource localCache = RoleEnvironment.GetLocalResource("LocalStorage1");
            CloudDrive.InitializeCache(localCache.RootPath + "cache", localCache.MaximumSizeInMegabytes);
            CloudDrive drive = account.CreateCloudDrive(container.GetPageBlobReference(driveName).Uri.ToString());
        
            try
            {
                drive.Create(DRIVE_SIZE);
            }
            catch (CloudDriveException)
            {
            }
            driveLetter = drive.Mount(CACHE_SIZE, DriveMountOptions.None);
        
            // FTP設定
            string user = "user1";
            string passwd = "passwd";
            string dir = driveLetter + @"\" + user;
            System.IO.Directory.CreateDirectory(dir);
            IPEndPoint ftpendpoint = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["FtpEndpoint"].IPEndpoint;
        
            Assemblies.Ftp.UserData.Get().AddUser(user);
            Assemblies.Ftp.UserData.Get().SetUserPassword(user, passwd);
            Assemblies.Ftp.UserData.Get().SetUserStartingDirectory(user, dir);
            Assemblies.Ftp.FtpServerMessageHandler.Message += new Assemblies.Ftp.FtpServerMessageHandler.MessageEventHandler(MessageHandler_Message);
        
            ftpServer = new Assemblies.Ftp.FtpServer(new Assemblies.Ftp.FileSystem.StandardFileSystemClassFactory());
            ftpServer.Start(ftpendpoint.Port);
            ftpServer.ConnectionClosed += new Assemblies.Ftp.FtpServer.ConnectionHandler(ftpServer_ConnectionClosed);
            ftpServer.NewConnection += new Assemblies.Ftp.FtpServer.ConnectionHandler(ftpServer_NewConnection);
        
            // Web設定 
            IPEndPoint webendpoint = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["WebEndpoint"].IPEndpoint;
            var server = new HttpServer.Server();
            var module = new HttpServer.Modules.FileModule();
            module.Resources.Add(new HttpServer.Resources.FileResources("/", driveLetter + @"\\"));
            server.Add(module);
            server.Add(HttpServer.HttpListener.Create(IPAddress.Any, webendpoint.Port));
            server.Start(5);
        
            while (true)
            {
                Thread.Sleep(10000);
            }
        }

前回、Worker Role で Web Server を試したので、今回は、FTP Server を試してみます。

FTP Server は「C# FTP Server」を使い、ファイルの保存先は Windows Azure Drive を使います。

C# FTP Server
http://www.codeguru.com/csharp/csharp/cs_network/sockets/article.php/c7409

 

1.「ASP.NET Web Role」プロジェクトを作成します。

 

2.Worker ロールのプロパティページを開き、「Settings」「Endpoints」「Local Srorage」に設定を追加します。

[Settings]

今回は、StorageConnectionString という名前で追加しています。

 

[Endpoints]

エンドポイント情報を追加し、公開するポート番号などを設定します。

設定例)
Name Endpoint1
Type Input
Protocol TCP
Port 21
SSL Certificate Name (not applicable)

 

[Local Srorage]

設定例)
Name LocalStorage1
Size 1000

 

3.ダウンロードした C# FTP Server と Microsoft.WindowsAzure.CloudDrive.dll を参照設定に追加します。

 

4.OnStart に CloudStorageAccount の設定を追加します。

        public override bool OnStart()
        {
            CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
            {
                configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));

                RoleEnvironment.Changed += (sender, arg) =>
                {
                    if (arg.Changes.OfType<roleenvironmentconfigurationsettingchange>()
                        .Any((change) => (change.ConfigurationSettingName == configName)))
                    {
                        if (!configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)))
                        {
                            RoleEnvironment.RequestRecycle();
                        }
                    }
                };
            });

            RoleEnvironment.Changing += RoleEnvironmentChanging;

            return base.OnStart();
        }

 

5.後は実行時に Windows Azure Drive 作り、FTPの設定を行って待つだけです。

        private Assemblies.Ftp.FtpServer ftpServer = null;

        public override void Run()
        {
            // Windows Azure Drive 設定値
            const int DRIVE_SIZE = 1000;
            const int CACHE_SIZE = 500;
            string driveName = "drive2.vhd";

            CloudStorageAccount account = CloudStorageAccount.FromConfigurationSetting("StorageConnectionString");
        
            // コンテナがなかったら作成
            string containerName = "vhd";
            CloudBlobClient client = account.CreateCloudBlobClient();
            CloudBlobContainer container = client.GetContainerReference(containerName);
            container.CreateIfNotExist();
        
            // 仮想ディスク作成
            LocalResource localCache = RoleEnvironment.GetLocalResource("LocalStorage1");
            CloudDrive.InitializeCache(localCache.RootPath + "cache", localCache.MaximumSizeInMegabytes);
            CloudDrive drive = account.CreateCloudDrive(container.GetPageBlobReference(driveName).Uri.ToString());
        
            try
            {
                drive.Create(DRIVE_SIZE);
            }
            catch (CloudDriveException)
            {
            }
            string driveLetter = drive.Mount(CACHE_SIZE, DriveMountOptions.None);
        
            // FTP設定
            string user = "user1";
            string passwd = "passwd";
            string dir = driveLetter + @"\" + user;
            System.IO.Directory.CreateDirectory(dir);
        
            IPEndPoint endpoint = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["Endpoint1"].IPEndpoint;
        
            Assemblies.Ftp.UserData.Get().AddUser(user);
            Assemblies.Ftp.UserData.Get().SetUserPassword(user, passwd);
            Assemblies.Ftp.UserData.Get().SetUserStartingDirectory(user, dir);
            Assemblies.Ftp.FtpServerMessageHandler.Message += new Assemblies.Ftp.FtpServerMessageHandler.MessageEventHandler(MessageHandler_Message);
        
            ftpServer = new Assemblies.Ftp.FtpServer(new Assemblies.Ftp.FileSystem.StandardFileSystemClassFactory());
            ftpServer.Start(endpoint.Port);
            ftpServer.ConnectionClosed += new Assemblies.Ftp.FtpServer.ConnectionHandler(ftpServer_ConnectionClosed);
            ftpServer.NewConnection += new Assemblies.Ftp.FtpServer.ConnectionHandler(ftpServer_NewConnection);

            while (true)
            {
                Thread.Sleep(10000);
            }
        }

        private void MessageHandler_Message(int nId, string sMessage)
        {
        }

        private void ftpServer_ConnectionClosed(int nId)
        {
        }

        private void ftpServer_NewConnection(int nId)
        {
        }

 

※アクティブモードでのみ接続可能です。

Windows Azure の Worker Role でTCPサービスを公開し「C# WebServer」を使ってみます。「C# WebServer」は、名前の通り C# で書かれた WebServer です。

C# WebServer - CodePlex
http://webserver.codeplex.com/

1.WorkerRoleにエンドポイントを追加します。

まずは、普通に Workerロールで TCPサービスを公開します。Worker ロールのプロパティページを開き、Endpoint タブを選択しエンドポイント情報を追加します。このエンドポイントで設定したポート番号でサービスが公開されます。

20100805-01

設定例)
Name Endpoint1
Type Input
Protocol TCP
Port 8888
SSL Certificate Name (not applicable)

 

2.CodePlex からダウンロードした HttpServer を参照に追加します。
※今回は、Source Code をダウンロードしてビルドしたものを使っています。

 

3.WorkerRole.csを修正します。

HttpListener を使う場合は、こんな感じになります。内部的に使用されるポート番号を取得、HttpListener.Create で引数に指定します。

        public override void Run()
        {
            IPEndPoint endpoint = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["Endpoint1"].IPEndpoint;

            HttpListener listener = HttpListener.Create(IPAddress.Any, endpoint.Port);
            listener.RequestReceived += OnRequest;
            listener.Start(5);

            while (true)
            {
                Thread.Sleep(10000);
            }
        }

        private static void OnRequest(object sender, RequestEventArgs e)
        {
            e.Response.Connection.Type = ConnectionType.Close;
            byte[] buffer = Encoding.UTF8.GetBytes("<html><body>Hello wordl!</body></html>");
            e.Response.Body.Write(buffer, 0, buffer.Length);
        }

 

4.実行するとエンドポイントで指定したポート8888で公開されます。

20100805-02

非同期コントローラ使ってみた - まめしば雑記
http://d.hatena.ne.jp/shiba-yan/20100715/1279204349

上記のブログで非同期コントローラーが面白そうだったので、前回の ASP.NET MVC で複数ファイルのアップロード を少し修正して試してみました(※ASPX部分は そのまま使用しています)。

基本的には、基本クラス(Controller → AsyncController) とアクション名(Index → IndexAsync) を変えて、完了時のメゾット(IndexCompleted) を定義しているだけですが、アップロードされたファイルをそのまま保存しては、非同期にする意味がほとんどないので、保存後にメールでファイルを送る処理を追加しています。

    [HandleError]
    public class HomeController : AsyncController
    {
        [HttpPost]
        public ActionResult IndexAsync(HttpPostedFileBase[] fileUpload)
        {
            foreach (var f in fileUpload)
            {
                AsyncManager.OutstandingOperations.Increment();
                Thread t = new Thread(new ParameterizedThreadStart(Upload));
                t.Start(f);
            }
            return View();
        }

        private void Upload(object o)
        {
            HttpPostedFileBase f = (HttpPostedFileBase)o;
            if (f != null)
            {
                string filePath = Path.Combine(
                    HttpContext.Server.MapPath("~/App_Data/Uploaded/"),
                    Path.GetFileName(f.FileName));
                f.SaveAs(filePath);
				
                MyMail.Send(filePath);
            }
            AsyncManager.OutstandingOperations.Decrement();
        }

        public ActionResult IndexCompleted()
        {
            ViewData["msg"] = "アップロードが完了しました。";
            return View();
        }

        public ActionResult About()
        {
            return View();
        }
    }

 

メールを送ってる部分は、こんな感じです。

    public class MyMail
    {
        public static void Send(string fineName)
        {
            string from = "差出人メールアドレス";
            string to = "送信先メールアドレス";
            using (System.Net.Mail.MailMessage msg = new System.Net.Mail.MailMessage(from, to))
            {
                //添付ファイル
                System.Net.Mail.Attachment attachment;
                attachment = new System.Net.Mail.Attachment(fineName);
                attachment.ContentType = new System.Net.Mime.ContentType("image/jpeg");
                msg.Attachments.Add(attachment);

                System.Net.Mail.SmtpClient sc = new System.Net.Mail.SmtpClient();
                sc.Host = "メールサーバ";

                sc.Send(msg);
            }
        }
    }

 

参考

非同期コントローラ使ってみた - まめしば雑記
http://d.hatena.ne.jp/shiba-yan/20100715/1279204349

ASP.NET MVC での非同期コントローラーの使用 - MSDN
http://msdn.microsoft.com/ja-jp/library/ee728598.aspx