これまでに作成したFileMaker Webアプリケーションのうち、今回はPHPファイル群のデプロイ方法、そしてFileMaker Serverのカスタマイズすべき設定箇所を紹介しよう。引き続き第2回目で紹介したファイルをそのまま使用する。作成したWebアプリケーションをホームディレクトリ以下の「サイト」(Sites)以下にデプロイする。

fxphp_testディレクトリの中にPHPファイルが格納されている

デプロイ後、FX.phpではserver_data.phpといったファイルの修正をおこなう。あたらしく作成した環境ではFileMaker Server、Apacheともに一台のマシンに格納されているため、FileMaker Serverへのアドレスはlocalhost(127.0.0.1)となる。

修正例1: サーバIPを直接入力している場合

$data = new FX('localhost', 80, 'FMPro9', 'http');

修正例2: server_data.phpを使用している場合

<?php

// IPアドレスを指定
$serverIP = 'localhost'; // 127.0.0.1 でも可能
$webCompanionPort = 80;
$dataSourceType = 'FMPro9';
$scheme = 'http';

// FileMaker ファイル情報
$databaseFileName = 'fxphp_test'; // FileMaker ファイル名
$webUN = 'admin'; // ファイルを開くためのユーザ名
$webPW = 'admin'; // ファイルを開くためのパスワード

?>

FileMaker Serverへの通信経路が確保されたら、Webブラウザで実際にアクセスしてみる。今回の場合はhttp://localhost/~hiroaki/fxphp_test/fm_new.phpとなる。

正常にFileMakerにレコードが作成された。もしトラブルがおきたとき、見るべきは「errorCode」の1点だ。表示された結果がFX Error Objectだった場合は、正常にFileMakerと通信できていない可能性が高い

うまく展開・表示・FileMakerにレコードが作成できただろうか。ここでErrorCodeが0以外だったり、FX Error Objectが表示できない場合は、次の点を確認してみよう。

  1. FileMaker Serverへのホスト名またはIPアドレスが間違っていないか
  2. PHPでcURLが有効になっているか
  3. 不正な証明書(自己署名など)を使用したhttps経由で通信をおこなおうとしていないか

社内インフラ内などといった開発環境で、3の場合でも使用したい場合は直接FX.phpを修正する。FX.php内部でcurlを使っている箇所で、CURLOPT_SSL_VERIFYPEERとCURLOPT_SSL_VERIFYHOSTをFALSEに設定する。なお、この2つのオプションをFALSEに設定することで「サーバ証明書の検証」はおこなわれなくなってしまう。使用する場合は充分に注意されたい。

FX.php 修正例(diff)

--- FX.php.orig 2008-02-28 10:28:10.000000000 +0900
+++ FX.php  2009-04-21 15:36:47.000000000 +0900
@@ -536,6 +536,8 @@
         } elseif ($this->isPostQuery) {
             if ($this->useCURL && defined("CURLOPT_TIMEVALUE")) {
                 $curlHandle = curl_init(str_replace($this->dataURLParams, '', $this->dataURL));
+                curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, FALSE);
+                curl_setopt($curlHandle, CURLOPT_SSL_VERIFYHOST, FALSE);
                 curl_setopt($curlHandle, CURLOPT_POST, 1);
                 curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $this->dataURLParams);
                 ob_start();
@@ -641,6 +643,8 @@
         } elseif ($this->isPostQuery) {
             if ($this->useCURL && defined("CURLOPT_TIMEVALUE")) {
                 $curlHandle = curl_init(str_replace($this->dataURLParams, '', $this->dataURL));
+                curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, FALSE);
+                curl_setopt($curlHandle, CURLOPT_SSL_VERIFYHOST, FALSE);
                 curl_setopt($curlHandle, CURLOPT_POST, 1);
                 curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $this->dataURLParams);
                 ob_start();

直接ライブラリファイルを修正したくない場合は、FXクラスのRetrieveFMDataおよびRetrieveFM7Dataをオーバライドするクラスを作成すればOKだ。

FXクラスをオーバライドするPHPファイル例 - fx_mod.php

<?php

include_once('FX.php');

class FX_mod extends FX
{

    function RetrieveFMData ($action)
    {
        $data = '';
        if ($this->DBPassword != '') {                                      // Assemble the Password Data
            $this->userPass = $this->DBUser . ':' . $this->DBPassword . '@';
        }
        if ($this->layout != "") {                                          // Set up the layout portion of the query.
            $layRequest = "&-lay=" . urlencode($this->layout);
        }
        else {
            $layRequest = "";
        }
        if ($this->currentSkip > 0) {                                       // Set up the skip size portion of the query.
            $skipRequest = "&-skip=$this->currentSkip";
        } else {
            $skipRequest = "";
        }
        $currentSort = $this->CreateCurrentSort();
        $currentSearch = $this->CreateCurrentSearch();
        $this->dataURL = "http://{$this->userPass}{$this->dataServer}{$this->dataPortSuffix}/FMPro"; // First add the server info to the URL...
        $this->dataURLParams = $this->AssembleCurrentSearch($layRequest, $skipRequest, $currentSort, $currentSearch, $action);
        $this->dataURL .= '?' . $this->dataURLParams;

        if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
            $currentDebugString = "<p>Using FileMaker URL: <a href=\"{$this->dataURL}\">{$this->dataURL}</a></p>\n";
            $this->lastDebugMessage .= $currentDebugString;
            if (defined("DEBUG") and DEBUG) {
                echo $currentDebugString;
            }
        }

        if (defined("HAS_PHPCACHE") and defined("FX_USE_PHPCACHE") and strlen($this->dataURLParams) <= 510 and (substr_count($this->dataURLParams, '-find') > 0 || substr_count($this->dataURLParams, '-view') > 0 || substr_count($this->dataURLParams, '-dbnames') > 0 || substr_count($this->dataURLParams, '-layoutnames') > 0)) {
            $data = get_url_cached($this->dataURL);
            if (! $data) {
                return new FX_Error("Failed to retrieve cached URL in RetrieveFMData()");
            }
            $data = $data["Body"];
        } elseif ($this->isPostQuery) {
            if ($this->useCURL && defined("CURLOPT_TIMEVALUE")) {
                $curlHandle = curl_init(str_replace($this->dataURLParams, '', $this->dataURL));
                curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, FALSE);
                curl_setopt($curlHandle, CURLOPT_SSL_VERIFYHOST, FALSE);
                curl_setopt($curlHandle, CURLOPT_POST, 1);
                curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $this->dataURLParams);
                ob_start();
                if (! curl_exec($curlHandle)) {
                    $this->lastDebugMessage .= "<p>Unable to connect to FileMaker.  Use the DEBUG constant and try connecting with the resulting URL manually.<br />\n";
                    $this->lastDebugMessage .= "You should also double check the user name and password used, the server address, and Web Companion configuration.</p>\n";
                    return new FX_Error("cURL could not retrieve Post data in RetrieveFMData(). A bad URL is the most likely reason.");
                }
                curl_close($curlHandle);
                $data = trim(ob_get_contents());
                ob_end_clean();
                if (substr($data, -1) != '>') {
                    $data = substr($data, 0, -1);
                }
            } else {
                $dataDelimiter = "\r\n";
                $socketData = "POST /FMPro HTTP/1.0{$dataDelimiter}";
                if (strlen(trim($this->userPass)) > 1) {
                    $socketData .= "Authorization: Basic " . base64_encode($this->DBUser . ':' . $this->DBPassword) . $dataDelimiter;
                }
                $socketData .= "Host: {$this->dataServer}:{$this->dataPort}{$dataDelimiter}";
                $socketData .= "Pragma: no-cache{$dataDelimiter}";
                $socketData .= "Content-length: " . strlen($this->dataURLParams) . $dataDelimiter;
                $socketData .= "Content-type: application/x-www-form-urlencoded{$dataDelimiter}";
                // $socketData .= "Connection: close{$dataDelimiter}";
                $socketData .= $dataDelimiter . $this->dataURLParams;

                $fp = fsockopen ($this->dataServer, $this->dataPort, $this->errorTracking, $this->fxError, 30);
                if (! $fp) {
                    $this->lastDebugMessage .= "<p>Unable to connect to FileMaker.  Use the DEBUG constant and try connecting with the resulting URL manually.<br />\n";
                    $this->lastDebugMessage .= "You should also double check the user name and password used, the server address, and Web Companion configuration.</p>\n";
                    return new FX_Error( "Could not fsockopen the URL in retrieveFMData" );
                }
                fputs ($fp, $socketData);
                while (!feof($fp)) {
                    $data .= fgets($fp, 128);
                }
                fclose($fp);
                $pos = strpos($data, chr(13) . chr(10) . chr(13) . chr(10)); // the separation code
                $data = substr($data, $pos + 4) . "\r\n";
            }
        } else {
            $fp = fopen($this->dataURL, "r");
            if (! $fp) {
                $this->lastDebugMessage .= "<p>Unable to connect to FileMaker.  Use the DEBUG constant and try connecting with the resulting URL manually.<br />\n";
                $this->lastDebugMessage .= "You should also double check the user name and password used, the server address, and Web Companion configuration.</p>\n";
                return new FX_Error("Could not fopen URL in RetrieveFMData.");
            }
            while (!feof($fp)) {
                $data .= fread($fp, 4096);
            }
            fclose($fp);
        }
        $data = str_replace($this->invalidXMLChars, '', $data);
        return $data;
    }

    function RetrieveFM7Data ($action)
    {
        $data = '';
        if ($this->DBPassword != '' || $this->DBUser != 'FX') {             // Assemble the Password Data
            $this->userPass = $this->DBUser . ':' . $this->DBPassword . '@';
        }
        if ($this->layout != "") {                                          // Set up the layout portion of the query.
            $layRequest = "&-lay=" . urlencode($this->layout);
            if ($this->responseLayout != "") {
                $layRequest .= "&-lay.response=" . urlencode($this->responseLayout);
            }
        }
        else {
            $layRequest = "";
        }
        if ($this->currentSkip > 0) {                                       // Set up the skip size portion of the query.
            $skipRequest = "&-skip={$this->currentSkip}";
        } else {
            $skipRequest = "";
        }
        $currentSort = $this->CreateCurrentSort();
        $currentSearch = $this->CreateCurrentSearch();
        if ($action == '-view') {
            $FMFile = 'FMPXMLLAYOUT.xml';
        } else {
            $FMFile = 'FMPXMLRESULT.xml';
        }
        $this->dataURL = "{$this->urlScheme}://{$this->userPass}{$this->dataServer}{$this->dataPortSuffix}/fmi/xml/{$FMFile}"; // First add the server info to the URL...
        $this->dataURLParams = $this->AssembleCurrentSearch($layRequest, $skipRequest, $currentSort, $currentSearch, $action, 7);
        $this->dataURL .= '?' . $this->dataURLParams;

        if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
            $currentDebugString = "<p>Using FileMaker URL: <a href=\"{$this->dataURL}\">{$this->dataURL}</a></p>\n";
            $this->lastDebugMessage .= $currentDebugString;
            if (defined("DEBUG") and DEBUG) {
                echo $currentDebugString;
            }
        }

        if (defined("HAS_PHPCACHE") and defined("FX_USE_PHPCACHE") and strlen($this->dataURLParams) <= 510 and (substr_count($this->dataURLParams, '-find') > 0 || substr_count($this->dataURLParams, '-view') > 0 || substr_count($this->dataURLParams, '-dbnames') > 0 || substr_count($this->dataURLParams, '-layoutnames') > 0)) {
            $data = get_url_cached($this->dataURL);
            if (! $data) {
                return new FX_Error("Failed to retrieve cached URL in RetrieveFM7Data()");
            }
            $data = $data["Body"];
        } elseif ($this->isPostQuery) {
            if ($this->useCURL && defined("CURLOPT_TIMEVALUE")) {
                $curlHandle = curl_init(str_replace($this->dataURLParams, '', $this->dataURL));
                curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, FALSE);
                curl_setopt($curlHandle, CURLOPT_SSL_VERIFYHOST, FALSE);
                curl_setopt($curlHandle, CURLOPT_POST, 1);
                curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $this->dataURLParams);
                ob_start();
                if (! curl_exec($curlHandle)) {
                    $this->lastDebugMessage .= "<p>Unable to connect to FileMaker.  Use the DEBUG constant and try connecting with the resulting URL manually.<br />\n";
                    $this->lastDebugMessage .= "You should also double check the user name and password used, the server address, and WPE configuration.</p>\n";
                    return new FX_Error("cURL could not retrieve Post data in RetrieveFM7Data(). A bad URL is the most likely reason.");
                }
                curl_close($curlHandle);
                $data = trim(ob_get_contents());
                ob_end_clean();
                if (substr($data, -1) != '>') {
                    $data = substr($data, 0, -1);
                }
            } else {
                $dataDelimiter = "\r\n";
                $socketData = "POST /fmi/xml/{$FMFile} HTTP/1.0{$dataDelimiter}";
                if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
                    $currentDebugString = "<p>Using socket [$socketData] - FileMaker URL: <a href=\"{$this->dataURL}\">{$this->dataURL}</a></p>\n";
                    $this->lastDebugMessage .= $currentDebugString;
                    if (defined("DEBUG") and DEBUG) {
                        echo $currentDebugString;
                    }
                }
                if (strlen(trim($this->userPass)) > 1) {
                    $socketData .= "Authorization: Basic " . base64_encode($this->DBUser . ':' . $this->DBPassword) . $dataDelimiter;
                }
                $socketData .= "Host: {$this->dataServer}:{$this->dataPort}{$dataDelimiter}";
                $socketData .= "Pragma: no-cache{$dataDelimiter}";
                $socketData .= "Content-length: " . strlen($this->dataURLParams) . $dataDelimiter;
                $socketData .= "Content-type: application/x-www-form-urlencoded{$dataDelimiter}";
                $socketData .= $dataDelimiter . $this->dataURLParams;

                // Check if SSL is required
                if ($this->useSSLProtocol) {
                    $protocol = "ssl://";
                } else {
                    $protocol = "";
                }

                // debug to see what protocol is being used
                if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
                    $currentDebugString = "<p>Domain and Protocol are {$protocol}{$this->dataServer}</p>\n";
                    $this->lastDebugMessage .= $currentDebugString;
                    if (defined("DEBUG") and DEBUG) {
                        echo $currentDebugString;
                    }
                }

                $fp = fsockopen ($protocol . $this->dataServer, $this->dataPort, $this->errorTracking, $this->fxError, 30);
                if (! $fp) {
                    $this->lastDebugMessage .= "<p>Unable to connect to FileMaker.  Use the DEBUG constant and try connecting with the resulting URL manually.<br />\n";
                    $this->lastDebugMessage .= "You should also double check the user name and password used, the server address, and WPE configuration.</p>\n";
                    return new FX_Error( "Could not fsockopen the URL in retrieveFM7Data" );
                }
                fputs ($fp, $socketData);
                while (!feof($fp)) {
                    $data .= fgets($fp, 128);
                }
                fclose($fp);
                $pos = strpos($data, chr(13) . chr(10) . chr(13) . chr(10)); // the separation code
                $data = substr($data, $pos + 4) . "\r\n";
            }
        } else {
            $fp = fopen($this->dataURL, "r");
            if (! $fp) {
                $this->lastDebugMessage .= "<p>Unable to connect to FileMaker.  Use the DEBUG constant and try connecting with the resulting URL manually.<br />\n";
                $this->lastDebugMessage .= "You should also double check the user name and password used, the server address, and WPE configuration.</p>\n";
                return new FX_Error("Could not fopen URL in RetrieveFM7Data.");
            }
            while (!feof($fp)) {
                $data .= fread($fp, 4096);
            }
            fclose($fp);
        }
        $data = str_replace($this->invalidXMLChars, '', $data);
        return $data;
    }
}

?>

修正をおこなったFX.phpを使うか、中身をオーバライドしたFX_mod.phpを使うことで自己署名といった証明書で運用している開発環境でも利用が可能となる。繰り返しになるが、使用する場合・場所には充分に注意されたい。

押さえておきたいFileMaker Serverの設定項目

FileMakerを使ったWebアプリケーションということで、ここまではWebアプリケーションに関連する設定項目しか取りあげていない。実際に運用のフェーズに入る前に、カスタマイズすべき設定項目をひととおり紹介しよう。

  • バックアップスケジュール
  • ログの表示方法、活用方法について
  • 電子メールの通知
  • データベースサーバのメンテナンス(クライアント最大接続数, ホストファイル数, キャッシュサイズ, セキュリティについて)

バックアップスケジュールをカスタマイズ - 環境に応じて要調節

FileMaker Serverをインストールしただけでも、次の図のように「毎日」のバックアップがおこなわれるようになっている。

バックアップディレクトリ以下。1週間分のデータが保持されている

デフォルトのバックアップの設定。23時にバックアップ処理が実施、7日分まで保存する設定となっている

FileMaker Server 10では、デフォルトのバックアップでもタイムスタンプごとのディレクトリを作成し、1週間分のバックアップを保持してくれるようになった。開発環境の場合はこれで事足りることが多いが、エンド環境ではストレージの容量とも相談しつつ、2週間分や1ヶ月分のバックアップをおこなうように調節しておこう。備えあれば憂いなしだ。

デフォルトのバックアップの保持数を30に引き上げ。なにかトラブルが起こった際に発見が遅れても、最高30日前までにはロールバックが可能だ

また日ごとのほかに、毎時ごとにバックアップスケジュールを走らせるといった設定も可能だ。ミッションクリティカルなファイルの場合は、パフォーマンスに影響を与えない程度にバックアップスケジュールを設定をおこなう。

毎時バックアップの設定例。平日9時から22時までに1時間おきにバックアップをおこない、7日分を保持する

以前のバージョンではバックアップディレクトリを先に作りそれぞれのバックアップスケジュールを作成する必要があった。10以降では「スケジュール名」+「スケジュールを実行した日時」でディレクトリを自動生成してくれる

なにかあったらまずログを - ログの表示・活用方法

なにかあればまずはログだ。Admin Consoleの「ログビューア」から、あらかじめ記録対象としている範囲内のログを閲覧することが可能だ。

ログのカテゴリ(モジュール)は次のとおり。

  • サーバイベント
  • サーバーアクセス
  • 公開エンジン
  • 公開エンジンアクセス
  • Webサーバー
  • Web公開コア

ログビューア動作イメージ。各種絞込みのほかに、ログ情報のエクスポートが可能

各種ログ情報は/Library/FileMaker Server/Logs/に保管されている

ある程度の絞り込みや、ログの範囲を指定してエクスポートといった処理も可能だ。なお、これらのログは設定したディレクトリに出力される。シェルスクリプトに精通したユーザならば、grepといったコマンドと組みあわせてサクサク解析をおこなうことも可能だ。トラブルシュートにかかる時間は、常に短くするように心がけよう。

電子メールの通知

何かトラブルがあったとき、どのタイミングで気づくかは重要な話だ。サービスが落ちてニッチもサッチもいかない状態で知るときと、あらかじめ予兆をメールで受け取っているときとでは、原因の切り分けといった対応速度も変わってくる。FileMaker Serverにはメールのセットアップをおこなっておけば、「エラー」や「警告」としてログに残るような処理が発生してしまった場合に自動的に設定したメールアドレス当てに通知をしてくれる。

電子メール通知の設定例

バックアップの容量が不足していていつのころからかバックアップが取れていなかったという状況に心あたりのあるユーザは、ぜひ設定しておこう。

そのほかの各種コンフィギュレーションについて

「構成」メニューの「データベースサーバー」には、データベースのパフォーマンスやセキュリティに直結する重要な設定項目がずらりと並んでいる。運用前に十二分なテストをおこない、可能な限りシステムは最小構成を取るように心がけておきたいところだ。

  • FileMaker Pro クライアント > FileMaker Pro接続の最大数: デフォルトでは250。必要のない場合は下げておく
  • データベース > ホストする最大のファイル数: デフォルトでは50。必要に応じて調節、必要ない場合はやはり下げておく
  • データベース > データベースキャッシュに予約されているRAM: デフォルトでは64MB。他の機能の兼ね合いもあるため調節がむずかしいが、FileMaker Serverのパフォーマンスを上げたい場合はやや多めにとるように設定する
  • セキュリティ > ファイル表示フィルタ: デフォルトでは「すべてのデータベースをリスト表示」。ファイル名その他といった情報を関係のないユーザに知られたくない場合は「各ユーザがアクセスを許可されているデータベースのみをリスト表示」にしておく
  • セキュリティ > 接続の保護: デフォルトではチェックがついていない。事情がある場合はチェックをつける

環境によって変動させなければならない値のため、調節がほかの項目と比較するとやや面倒だが、一度時間をじっくりとってそれぞれの環境にマッチした数値をセットできるように訓練しておこう。詳細のベンチマークなどについてはまた追って紹介していく。