LAMP開発者のためのWindows Azure講座

第9回Azure上にPHP+MySQLでアプリを作ってみよう[その2]

PHPアプリケーションのデプロイ

今回は、前回作成したプログラムをAzureにデプロイして動作を確認してみます。

ファイル構成は、図1のようになっています。

図1 Visual Web Developerのソリューションエクスプローラ画面
図1 Visual Web Developerのソリューションエクスプローラ画面

図中のphpフォルダ、adminerフォルダ、WebRole.cs, Web.config, Web.roleconfig, startup.cmd, ServiceConfiguration.cscfg, ServiceDefinition.csdefは, 第7回で作成したものです。

また、filesフォルダは、アップロードしたファイルを一時的に保存しておく場所です。Classesフォルダ、libフォルダは、それぞれPHPExcelWindows Azure SDK for PHP(PHPAzure)のライブラリです。解凍してできるClasses以下、library/Microsoft以下を図2のように配置しておきます。

図2 PHPExcel、PHPAzureのライブラリを配置
図2 PHPExcel、PHPAzureのライブラリを配置

今回は、MySQLのデータをAzureドライブに保存し、アップロードしたファイルをBlobストレージに保存することにします。PHPAzureは、PHPからBlobストレージにアクセスするためのライブラリです。具体的には、以下のようにして利用します。

リスト1 PHPAzureの使い方

<?php
///PHPAzureのライブラリの読み込み
set_include_path(ini_get('include_path') . PATH_SEPARATOR . './lib');
require_once 'Microsoft/WindowsAzure/Storage/Blob.php';
require_once 'Microsoft/WindowsAzure/RetryPolicy/NoRetry.php';

//PHPAzureで利用するストレージアカウント情報の記述
$storageUrl = Microsoft_WindowsAzure_Storage::URL_CLOUD_BLOB;
$storageAccount = 'samplestorage001';
$storageKey = '3YrKN*************==';

//Blobインスタンスの初期化とストリームラッパの登録
$blob_client = new Microsoft_WindowsAzure_Storage_Blob(
  $storageUrl,
  $storageAccount,
  $storageKey,
  false,
Microsoft_WindowsAzure_RetryPolicy_RetryPolicyAbstract::retryN(10, 250)
);
$blob_client->registerStreamWrapper();
?>

ストリームラッパとして登録することで、Blobストレージ上のファイルを指定してfopenなどの関数でファイルを操作できるようになります。ただ、fopenで存在しないファイルを指定すると、新規作成ではなくエラーになるなど、使い勝手がLAMP環境とまったく同じではありません。

そのため、ここでは、アップロードしたファイルをローカルで処理してfilesに保存し、その後にBlobストレージに保存する方法をとることにします。Blobに保存するには、以下のようにputBlob()を利用します。

リスト2 Blobストレージへの保存

<?php
//Blobストレージに保存
  $blob_client->putBlob('files', $xlsfile, $updir.$xlsfile);
  $blob_client->putBlob('files', $csvfile, $updir.$csvfile);
?>

Blobストレージには、アップロード先となるコンテナを作成しておきます。ここではfilesとしています。仮想ディスクを格納するmysqlコンテナと合わせ、ストレージの構成は以下のようになります。なお、仮想ディスクは、1GBの容量で作成し、MySQLの実行ファイルやdataを格納しています。

図3 CloudXplorerの画面
図3 CloudXplorerの画面

もっとも、くどくど説明されるより、ソースコードを見たほうが早いという方も多いと思うので、以下にindex.phpのソースコード(全部)を掲載しておきます。LAMP環境からAzureへの移行は、既存プログラムに大きく手を加える必要がないことがわかると思います。

リスト3 システムのトップページ(index.php)

<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>経費精算サンプル</title>
</head>
<body>
<h1>経費精算サンプル</h1>

<?php

//PHPAzureのライブラリの読み込み
set_include_path(ini_get('include_path') . PATH_SEPARATOR . './lib');
require_once 'Microsoft/WindowsAzure/Storage/Blob.php';
require_once 'Microsoft/WindowsAzure/RetryPolicy/NoRetry.php';

//PHPAzureで利用するストレージアカウント情報の記述
$storageUrl = Microsoft_WindowsAzure_Storage::URL_CLOUD_BLOB;
$storageAccount = 'samplestorage001';
$storageKey = '3YrKN*************==';

//PHPExcelで利用するライブラリの読み込み
require_once 'Classes/PHPExcel.php';
require_once 'Classes/PHPExcel/IOFactory.php';

//MySQLにアクセスするための情報
$hostname = "localhost";
$database = "sample_db";
$username = "sample_db_user";
$password = "*********";

//ファイルをアップロードするディレクトリ
$updir = "./files/";

//Blobインスタンスの初期化とストリームラッパの登録
$blob_client = new Microsoft_WindowsAzure_Storage_Blob(
  $storageUrl,
  $storageAccount,
  $storageKey,
  false,
Microsoft_WindowsAzure_RetryPolicy_RetryPolicyAbstract::retryN(10, 250)
);
$blob_client->registerStreamWrapper();

//MySQLに接続
$link = mysql_connect($hostname, $username, $password);
if (!$link) { die(mysql_error()); }

//ファイルのアップロード処理
if (is_uploaded_file($_FILES["uploadfile"]["tmp_name"])) {
  if (move_uploaded_file($_FILES["uploadfile"]["tmp_name"], $updir.$_FILES["uploadfile"]["name"])) {
    echo "アップロードしました: ".$_FILES["uploadfile"]["name"];
  } else {
    echo "アップロードできませんでした";
  }
}

//XLSからデータ取得、CSVに加工
$xlsfile = $_FILES["uploadfile"]["name"];

if ($xlsfile == "") {
  echo "経費精算書をアップロードしてください";
} else {
//Excel5(Excel97-2003形式)の読み込み
  $xlsReader = PHPExcel_IOFactory::createReader('Excel5'); 
  $xlsObject = $xlsReader->load($updir.$xlsfile); 
//アクティブなシートを選択
  $xlsObject->setActiveSheetIndex(0);
  $sheet = $xlsObject->getActiveSheet();
//経費精算書のセルからデータを取得
  $month = strftime("%Y/%m/%d",PHPExcel_Shared_Date::ExcelToPHP($sheet->getCell('A4') ->getValue()));
  $kosai = $sheet->getCell('C23')->getCalculatedValue();
  $kaigi = $sheet->getCell('D23')->getCalculatedValue();
  $kotsu = $sheet->getCell('E23')->getCalculatedValue();
  $tusin = $sheet->getCell('F23')->getCalculatedValue();
  $shomo = $sheet->getCell('G23')->getCalculatedValue();
  $tosho = $sheet->getCell('H23')->getCalculatedValue();
  $other = $sheet->getCell('I23')->getCalculatedValue();
  $gokei = $sheet->getCell('C24')->getCalculatedValue();
//CSVファイルに加工
  $csvfile = basename($xlsfile,"xls")."csv";
  $fp = fopen($updir.$csvfile,"w");
  $str =
  '"2110",,"","'.$month.'","","","","対象外",0,0,"未払費用","立替経費","","対象外","'.$gokei.'",0,"","","",3,"","","0","0","no"'."\n".
  '"2100",,"","'.$month.'","交際費","","","課対仕入込","'.$kosai.'",0,"","","","対象外",0,0,"","","",3,"","","0","0","no"'."\n".
  '"2100",,"","'.$month.'","会議費","","","課対仕入込","'.$kaigi.'",0,"","","","対象外",0,0,"","","",3,"","","0","0","no"'."\n".
  '"2100",,"","'.$month.'","旅費交通費","","","課対仕入込","'.$kotsu.'",0,"","","","対象外",0,0,"","","",3,"","","0","0","no"'."\n".
  '"2100",,"","'.$month.'","通信費","","","課対仕入込","'.$tusin.'",0,"","","","対象外",0,0,"","","",3,"","","0","0","no"'."\n".
  '"2100",,"","'.$month.'","消耗品費","","","課対仕入込","'.$shomo.'",0,"","","","対象外",0,0,"","","",3,"","","0","0","no"'."\n".
  '"2100",,"","'.$month.'","新聞図書費","","","課対仕入込","'.$tosho.'",0,"","","","対象外",0,0,"","","",3,"","","0","0","no"'."\n".
  '"2101",,"","'.$month.'","雑費","","","課対仕入込","'.$other.'",0,"","","","対象外",0,0,"","","",3,"","","0","0","no"'."\n";
  $str = mb_convert_encoding($str,"SJIS","UTF-8");
  fwrite($fp,$str);
  fclose($fp);
//MySQLにデータ登録
  $result = mysql_db_query($database, "INSERT INTO sample_table (
    month, kosai, kaigi, kotsu, tusin, shomo, tosho, other, gokei, xlsfile, csvfile
    ) VALUES (
    '$month','$kosai','$kaigi','$kotsu','$tusin','$shomo','$tosho','$other','$gokei','$xlsfile','$csvfile'
    )" );
  if (!$result) { die(mysql_error()); }
//Blobストレージに保存
  $blob_client->putBlob('files', $xlsfile, $updir.$xlsfile);
  $blob_client->putBlob('files', $csvfile, $updir.$csvfile);
}

//テーブル作成
$result = mysql_db_query($database, "SELECT id FROM sample_table", $link);
$numrows = mysql_numrows($result);

echo "<table border='1'>\n";
echo "<tr><th>月</th><th>交際費</th><th>会議費</th><th>交通費</th><th>通信費</th><th>消耗品費</th><th>図書費</th><th>その他</th><th>合計</th><th>xls</th><th>csv</th></tr>\n";

$result = mysql_db_query($database, "SELECT
  month, kosai, kaigi, kotsu, tusin, shomo, tosho, other, gokei, xlsfile, csvfile
  FROM sample_table", $link);

for ($i=0; $i<$numrows; $i++) {
  $row = mysql_fetch_array($result);
  $_month = $row["month"];
  $_kosai = $row["kosai"];
  $_kaigi = $row["kaigi"];
  $_kotsu = $row["kotsu"];
  $_tusin = $row["tusin"];
  $_shomo = $row["shomo"];
  $_tosho = $row["tosho"];
  $_other = $row["other"];
  $_gokei = $row["gokei"];
  $_xlsfile = $row["xlsfile"];
  $_csvfile = $row["csvfile"];
  echo "<tr><td>$_month</td><td>$_kosai</td><td>$_kaigi</td><td>$_kotsu</td><td>$_tusin</td><td>$_shomo</td><td>$_tosho</td><td>$_other</td><td>$_gokei</td><td><a href='$updir$_xlsfile'>$_xlsfile</a></td><td><a href='$updir$_csvfile'>$_csvfile</a></td></tr>";
}
echo "</table>\n";

?>

<!-- //アップロードフォーム -->
<br />
<form action="index.php" method="post" enctype="multipart/form-data">
  <input type="file" name="uploadfile" size="20" />
  <input type="submit" value="upload" />
</form>

</body>
</html>

IPによるアクセス制限

もっとも、このままデプロイすると全公開されてしまうので、簡単なアクセス制限をかけることにします。LAMP環境の.htaccessに近い働きをするのが、IISのWeb.configです。

Web.configだけでベーシック認証などを行うことはできません。ただ、IPやホスト名によるアクセス制限は可能です。ここでは、以下のようにして、特定のIP(111.222.333.444と555.666.777.888)以外からのアクセスは、403エラーが表示されるようにリライトしています。

リスト4 Web.config

<configuration>

  <!-- 中略  -->

  <system.webServer>

  <!-- 中略  -->

    <rewrite>
      <rules>
        <rule name="AccessControl" stopProcessing="true">
          <match url="^(.*)$" ignoreCase="false" />
          <conditions logicalGrouping="MatchAny">
            <add input="{REMOTE_ADDR}" pattern="111\.222\.333\.444" negate="true" />
            <add input="{REMOTE_ADDR}" pattern="555\.666\.777\.888" negate="true" />
          </conditions>
          <action type="CustomResponse" url="index.php" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

図4のようなエラーが表示されます。

図4 特定のIP以外からは403エラーを表示させる
図4 特定のIP以外からは403エラーを表示させる

なお、上記のREMOTE_ADDR部分は、URL、QUERY_STRINGなどのサーバ変数が利用できます。またpatternは正規表現なので、特定のURLに対するアクセスなどを細かく制御することができます。negate以下を削除すると、特定のIPからのアクセスについて、403コードを返す動作になります。

リモートデスクトップ接続の設定

また、デプロイ後にリモートデスクトップ接続ができるように設定しておきましょう。Azureでは、LAMP環境のようにSSHを使ってログを確認したりファイルを修正、管理したりといった手段がありません。Azure上でサーバの挙動を確認するてっとりばやい方法は、やはりリモートデスクトップ接続です。

リモートデスクトップ接続のセットアップは、Visual StudioなどのGUIを用いる方法が用意されています。ただ、SSHの鍵作成などと比べるとやや面倒なので、できるだけコマンドラインでセットアップしたほうがいいと思います。そのガイドがこちらです。

デプロイ後にリモートデスクトップ接続すると、図5のように、Fドライブがローカルドライブとしてマウントされ、また、Eドライブのapproot以下にindex.phpなどのファイルが展開されていることがわかります。また、通常のローカルサーバのように、タスクマネージャでプロセスの動作確認を行なったり、メモ帳でエラーログの確認などを行なったりできます。

図5 リモートデスクトップ接続でWebRoleにアクセスした画面
図5 リモートデスクトップ接続でWebRoleにアクセスした画面

ちなみに、ブラウザ上での表示は図6になります。

図6 経費精算アプリのサンプル画面
図6 経費精算アプリのサンプル画面

以上で、サンプルアプリのAzure上での動作確認は終わりです。もっとも、このレベルの小さなアプリなら社内サーバやVPSで十分です。Azureを利用するなら、もっと目に見えるかたちでのメリット、コスト効果がほしいところです。次回は、そのあたりを整理したいと思います。

おすすめ記事

記事・ニュース一覧