2017.10.26
Chris Ringer 著

PHPの勝利への道のり、パート 2

この記事の「パート1」では 、PHPリクエストが、外部SQLストアード プロシージャーの不思議な力によってバックエンドのRPGプログラムまでたどり着く道のりについてお話ししました。ここで先に進む前に、「パート1」のSQL、PHP、およびRPGコードについて見直しをしておくとよいかもしれません。コードをまだIBM iへダウンロードしていない場合は、ここで、「パート1」に記載のリンクからダウンロードするとよいでしょう。

「パート2」は、このPHP環境で想定外のことが発生した際にトラブルシューティングを行う方法について説明します。そして、大企業の開発者であるとしたら、アプリケーションをインストールする作業は、システムが制限状態の場合は、実際には運用部門の担当になるかもしれません。その部門にしても、一部の構成はセキュリティに関わるため、この情報から得られるメリットがあるかもしれません。

損傷報告

エラーおよび警告がリストされる場所は、それぞれ異なる以下の4つがあります。それぞれの場所を以下のラベルで示します。

  • Chrome: ブラウザ自体、Apacheサーバー、またはPHPスクリプトからブラウザへ出力されるメッセージ。Microsoft Internet Explorer(MSIE)で表示されるエラーは、Chromeのエラーとは異なる場合がありますので、そのエラーもリストしています。
  • Apacheログ: IFS上でApacheサーバーによって記録されたメッセージ。
  • PHPログ: Zend ServerによってIFS上でPHPのログ ファイルに記録されたメッセージ。
  • ジョブ ログ: IBM iのジョブ ログへ書き込まれたメッセージ。

「パート1」で触れたように、PHPリクエストが処理されるためには、一定のサブシステムおよび関連するジョブがアクティブでなければなりません。ApacheサーバーのZENDSVR6ジョブがアクティブでない(図1)、またはZENDSVR6サブシステムがアクティブでない(図2)場合、PHPリクエストは失敗し、次のようなエラー メッセージが表示されます。

  • Chrome: This site can’t be reached. YourSystem.com refused to connect.(このサイトにアクセスできません。YourSystem.comで接続が拒否されました。)
  • MSIE: This page can’t be displayed. Make sure the web address “http://YourSystem.com:10080” is correct.(このページは表示できません。Webアドレス http://YourSystem.com:10080 が正しいか確かめてください。)

技術情報code01 技術情報code02

対応策は、Zend Serverメニュー(図3)のオプション1および5を用いてそれらのジョブを開始することです。また、SQLステートメントを実行するためには、もちろん、QSYSWRKサブシステムでQSQSRVRジョブがアクティブになっている(図4)必要もあります。

技術情報code03 技術情報code04

もうひとつの可能性として、URLで間違ったポート番号(電話でいう「内線番号」)を使用しているケースも考えられます。Zend Serverのデフォルトは10080番ポートですが、「http://YourSystem.com:10999/cust600.php」と入力すると、次のエラーを受け取ります。

  • Chrome: This site can’t be reached. YourSystem.com took too long to respond.(このサイトにアクセスできません。YourSystem.comからの応答時間が長すぎます。)
  • MSIE: This page can’t be displayed – Make sure the web address http://YourSystem.com:10999 is correct.(このページは表示できません。Webアドレス http://YourSystem.com:10999 が正しいか確かめてください。)

Display File(DSPF)コマンドを使用すると、Apacheがどのポートをリッスンしているか確認できます。

技術情報code05

以下に示す2番目のポート番号を見てみてください。10081番ポートは、通常、Zend Server Adminインターフェースで使用されます。

技術情報code06

ブラウザで以下の2つのURLを試す場合、「Congrats(おめでとう)」メッセージをChromeへ出力するポート番号(httpd.confから)を使用します。 http://YourSystem.com:10080 Chrome: Congrats! Zend Server is now up and running(おめでとうございます。現在、Zend Serverは稼働中です) http://YourSystem.com:10081 Chrome: Login to your account(アカウントへログインします) Apacheが特定のポート番号でリッスンしていることを確認するには、IBM iのコマンド ラインから次の操作を行います。

  1. 「NETSTAT *CNN」と入力します。
  2. F14を押してポート番号を表示します。
  3. F13を押して列でソートし、ローカル ポートを選択します。
  4. リストをページダウンしてローカル ポート(おそらく10080番)を見つけます(図5)。

技術情報code07

ターンオーバーを最小限に抑える

PHPスクリプトがZend ServerのPHPエンジンによって解析され、実行されたとき、問題が発生したらどこを調べたらよいでしょうか。その答えは、php.logです。このファイルの内容は、QSYSOPRメッセージおよびQHSTヒストリー ログに似ていますが、ここでは エラーおよび警告 はたいていファイルの終端(末尾)に記録されます。ログを表示するには、Display File(DSPF)コマンドを使用します。

技術情報code08

たとえば、「パート1」のサンプルPHPスクリプトに、次の行があります。

技術情報code09

その行をアンコメントするとした場合、echoは宣言されていない変数を参照することになります。このURLを介してスクリプトを実行した後には、php.logの末尾に次の警告が記録されています。

技術情報code10

一部のエラーは自分自身でトラップおよび記録する必要があります。このサンプルPHPスクリプトは、「ゾッとさせるような」診断エラーを直接ブラウザへエコー バックします。より確かなやり方としては、クライアント(ブラウザ)には一般的なエラー メッセージをエコー バックし、詳細なエラー メッセージはphp.logに自身で書き込むようにすることです。

技術情報code11

登録抹消

存在しないPHPスクリプトまたはHTMLドキュメントをブラウザが要求した場合、応答は、あの悪名高い HTTP 404 の「Not found(未検出)」エラーということになります。このエラーを生成するために、次のような存在しないPHPスクリプトのURLをブラウザに入力してみます。

技術情報code12

これは静的HTMLドキュメントを要求しているものではないため、ブラウザ出力は予期していたものと異なる場合があります。また、Apacheのアクセス ログでも、404応答を確認することができます。

  • Chrome: No input file specified.(入力ファイルが指定されていません。)
  • MSIE: HTTP 404 Not Found – The webpage cannot be found(HTTP 404 未検出 – Webページが見つかりません。)
  • Apacheアクセス ログ: “GET /cust600Bad.php HTTP/1..1” 404

Apacheのログは、/www/zendsvr6/logsディレクトリーに保存されています。毎日、新しいファイルが自動的に作成されます。ファイル名には、YYMMDD形式で現在のシステム日付が含まれます。YYは西暦年の下2桁、MMは月、DDは日付です(図6)。アクセス ログは、各着信HTTPリクエストのリストです。エラー ログには、エラーおよび警告が記録されています。ログを表示するには、Work with Links(WRKLNK)コマンドを使用します。たとえば、2017年2月10日のログを表示するには、次のようにします。

技術情報code13

オプション5を使用してファイルを表示します。それぞれのファイルの最後に新しいメッセージが保存されています。

技術情報code14

ショットが弾かれる

サンプルのPHPリクエストが到達すると、Apacheは、ユーザー プロファイルQTMHHTTPIFSに対して、このIFSパス、/www/zendsvr6/htdocs/cust600.phpについて5つ以上の権限のチェックを行います。それらの権限のチェックのいずれかが失敗した場合、PHPリクエストは実行されません。

ユーザー プロファイルQTMHHTTPは、次の4つのディレクトリーに対する*X(実行)権限を必要とするため、次の順序でチェックされます。

  1. /
  2. /www
  3. /www/zendsvr6
  4. /www/zendsvr6/htdocs

実行権限は、ユーザー プロファイルがディレクトリーを使用することを許可します。権限がない場合、上記の最初の3つのディレクトリーに対するエラーは、次のようになります。

  • Chrome: Forbidden: You do not have permission to access /cust600.php on this server.(アクセス禁止: このサーバー上の/cust600.phpにアクセスする権限がありません。)
  • MSIE: HTTP 403 Forbidden – The website declined to show this webpage(HTTP 403 アクセス不可 – Web サイトによってこのページの表示を拒否されました。)
  • Apache エラー ログ: [error] (3401) Permission denied.: ZSRV_MSG064B: access to /cust600.php denied([エラー] (3401) アクセス許可が否認されました。: ZSRV_MSG064B: /cust600.phpへのアクセスが拒否されました。)

それでは、htdocsディレクトリーについては何に違いがあるのでしょうか。さて、現在、Apacheのドキュメント ルートの領域にいます(「パート1」参照)。Apacheでは、ディレクトリー内にオプションの .htaccessという設定オーバーライド ファイルが許可されていますが、QTMHHTTPはhtdocsディレクトリーでそのファイルを検索する権限を持っていません。このケースでは、エラー メッセージの内容がそれぞれ異なっていますが、共通点として大事なのは、QTMHHTTPがhtdocsフォルダーに対する*X(実行)権限を必要とするという点です。また、.htaccessがある場合、ユーザー プロファイルQTMHHTTPは、そのファイルに対する*R(読み取り)権限を必要とします。

  • Chrome: Forbidden – access to file is never allowed [no ACL file].(アクセス禁止 – ファイルへのアクセスが許可されていません [ACLファイルがありません]。)
  • MSIE: HTTP 403 Forbidden – The website declined to show this webpage(HTTP 403 アクセス不可 – Web サイトによってこのページの表示を拒否されました。)
  • Apacheエラー ログ: [crit] (3401)Permission denied.: ZSRV_MSG0014: Error opening htaccess file /www/zendsvr6/htdocs/.htaccess. Error: Permission denied.([重大](3401)アクセス許可が否認されました。: ZSRV_MSG0014: htaccessファイル/www/zendsvr6/htdocs/.htaccessのオープンでエラーが発生しました。エラー: アクセス許可が否認されました。)

権限の編集は、Work with Authority(WRKAUT)コマンドを使って行うことができます(図7)。

技術情報code15 技術情報code16

最後の権限のチェックは、htdocsディレクトリー内のPHPスクリプトcust600.phpに対してです。ユーザー プロファイルQTMHHTTPは、そのファイルを開くために*R(Read)権限を必要とします。以下に示すApacheアクセス ログのメッセージの「403」は、Forbidden(アクセス禁止)を意味するHTTPステータス コードです。

  • Chrome: Access denied.(アクセスが拒否されました。)
  • MSIE: HTTP 403 Forbidden – The website declined to show this webpage(HTTP 403 アクセス不可 – Web サイトによってこのページの表示を拒否されました。)
  • Apacheアクセス ログ: “GET /cust600.php HTTP/1.1” 403

パス ミス

ここで、DB2へ接続します。SQLストアード プロシージャーを呼び出すために必要とされるダウンストリームです。

技術情報code17

db2_pconnect() が失敗する場合は、ユーザー プロファイルおよびパスワード認証情報が正しくないか、ユーザー プロファイルが無効化されている可能性があります。この関数の2番目と3番目のパラメーターは、それぞれユーザー プロファイルとパスワードです。

  • Chrome: /www/zendsvr6/htdocs/cust600.php getConn ERROR! Problem connecting: 08001 SQLSTATE=08001 SQLCODE=-30082 Authorization failure on distributed database connection attempt.(/www/zendsvr6/htdocs/cust600.php getConn ERROR! 接続で問題が発生しました: 08001 SQLSTATE=08001 SQLCODE=-30082 分散データベースの接続の試行の際の許可の障害。)
  • ログ : このPHPスクリプトがそのメッセージを作成したので、Chromeと同じです。
  • QHTTPSVR ZENDSVR6ジョブ ログ: SQ30082 Authorization failure on distributed database connection attempt.(SQ30082 分散データベースの接続の試行の際の許可の障害。)

ユーザー プロファイルQTMHHTTPとして自動的に接続するためには、ユーザー プロファイルおよびパスワード パラメーターは、空文字列でなければなりません。この定義済みIBMプロファイルは、*DISABLEDのときでも、実にうまく接続するようです。これは理にかなったことです。誰かがサインオン画面でQTMHHTTPに対する誤ったパスワードを入力してPHPスクリプトをシャットダウンできる、といった事態は誰も望まないでしょう。

成功のための準備

ここでPHPスクリプトは、後で我々の外部ストアード プロシージャーを呼び出すためにSQLステートメントを 準備 します。

技術情報code18

ストアード プロシージャーの定義は、SYSROUTINEシステム表に保存され、ライブラリー(スキーマ)およびストアード プロシージャー名でキー化されます。この例では、それらの値はYOURLIBおよびCUST600SPです。しかし、上記のCALLステートメントは、ライブラリーに権限を与えません。したがって、ライブラリー リストの各ライブラリー名は、SYSROUTINEで一致するかチェックされます(図8)。一致するものが見つからない場合、エラーは次のようになります。

  • Chrome: Preparing Statement Failed: 42704 SQLSTATE=42704 SQLCODE=-204 CUST600SP in *N type *N not found.(ステートメントの準備が失敗しました: 42704 SQLSTATE=42704 SQLCODE=-204 *N のタイプ *NのCUST600SPが見つかりませんでした)
  • ログ: PHP Warning: db2_prepare() Statement Prepare Failed in /www/zendsvr6/htdocs/cust600.php on line 47(PHP 警告: db2_prepare() Prepareステートメントの準備が/www/zendsvr6/htdocs/cust600.phpの47行目で失敗しました。)

技術情報code19

SYSROUTINE表でストアード プロシージャー名が見つかったが、それらと同じパラメーター定義がない(すなわち、対応するSYSPARMSシステム表に一致するパラメーター シグニチャーがない)場合、エラーは次のようになります。

  • Chrome: Preparing Statement Failed: 42884 SQLSTATE=42884 SQLCODE=-440 Routine CUST600SP in *N not found with specified parameters.(ステートメントの準備が失敗しました: 42884 SQLSTATE=42884 SQLCODE=-440 *NのルーチンCUST600SPは指定されたパラメーターで見つかりませんでした。)
  • ログ: PHP Warning: db2_prepare(): Statement Prepare Failed in /www/zendsvr6/htdocs/cust600.php on line 47(PHP 警告: db2_prepare(): Prepareステートメントの準備が/www/zendsvr6/htdocs/cust600.phpの47行目で失敗しました。)

このエラーを発生させるには、次のように5番目のパラメーター マーカーを追加するだけです。

技術情報code20

凡プレー

これでSQLストアード プロシージャーを 実行 する準備ができました。

技術情報code21

外部プログラム(ここではRPGプログラムCUST600SP)がライブラリー リストで見つからなかった場合、エラーは次のようになります。

  • Chrome: SQL Execute error: 42724 SQLSTATE=42724 SQLCODE=-444 External program CUST600SP in *LIBL not found.(SQL実行エラー: 42724 SQLSTATE=42724 SQLCODE=-444 *LIBLの外部プログラムCUST600SPが見つかりませんでした。)
  • ログ: PHP Warning: db2_execute(): Statement Execute Failed in /www/zendsvr6/htdocs/cust600.php on line 69(PHP 警告: db2_execute(): Executeステートメントの実行が/www/zendsvr6/htdocs/cust600.phpの69行目で失敗しました。)

このシナリオを作成するために、CUST600SPプログラムの名前を変更し、ブラウザを更新してみます。

そして、接続しているユーザー プロファイルがプログラムに対する権限を持っていない場合、エラーは次のようになります。

  • Chrome: SQL Execute error: 42501 SQLSTATE=42501 SQLCODE=-551 Not authorized to object CUST600SP in *LIBL type *PGM.(SQL実行エラー: 42501 SQLSTATE=42501 SQLCODE=-551 *LIBLのタイプ*PGMのオブジェクトCUST600SPに対する権限がありません)
  • ログ: PHP Warning: db2_execute(): Statement Execute Failed in /www/zendsvr6/htdocs/cust600.php on line 69(PHP警告: db2_execute(): Executeステートメントの実行が/www/zendsvr6/htdocs/cust600.phpの69行目で失敗しました。)

このシナリオを作成するためには、CUST600SP *PGMに対する権限を、接続しているユーザー プロファイルで*EXCLUDEに設定します。通常、この権限は、*USE(または、最低でも「実行」)権限である必要があります。

技術情報code22

PHPの値が大き過ぎて、ストアード プロシージャーのパラメーターに収まらない場合、これもエラーになります。ソート順パラメーターは、10aとして定義されています。ユーザーがURLにsort=unitedstates(12文字長)とソート値を入力した場合、それを禁止しておかなければ、これはランタイム エラーを引き起こすことがあります。文字列長は次のようにしてチェックします。

技術情報code23

そのようにしない場合、エラーは次のようになります。

  • Chrome: SQL Execute error: 22001 SQLSTATE=22001 SQLCODE=-302 Conversion error on variable or parameter *N.(SQL実行エラー: 22001 SQLSTATE=22001 SQLCODE=-302 変数またはパラメーター*Nで変換エラーが発生しました。)
  • ログ: PHP Warning: db2_execute(): Statement Execute Failed in /www/zendsvr6/htdocs/cust600.php on line 69(PHP警告: db2_execute(): Executeステートメントの実行が/www/zendsvr6/htdocs/cust600.phpの69行目で失敗しました。)

アウト オブ バウンズ

ストアード プロシージャーを見直してみると、4つのパラメーターが必要であることが分かると思われます。そのためPHPは同じ数および種類のパラメーターとバインドする必要があります。PHPスクリプトで4番目のパラメーターのバインディングをコメント アウトする場合は、次のようにします。

技術情報code24

ブラウザを更新すると、エラーは次のようになるでしょう。

  • Chrome: SQL Execute error: 07001 SQLSTATE=07001 SQLCODE=-313 Number of host variables not valid.(SQL実行エラー: 07001 SQLSTATE=07001 SQLCODE=-313 ホスト変数の数が無効です。)
  • ログ: PHP Warning: db2_execute(): More parameters bound than present in /www/zendsvr6/htdocs/cust600.php on line 69(PHP警告: db2_execute(): /www/zendsvr6/htdocs/cust600.phpの69行目で存在しているよりも多くのパラメーターがバインドされています。)
  • ログ: PHP Warning: db2_execute(): Statement Execute Failed in /www/zendsvr6/htdocs/cust600.php on line 69(PHP警告: db2_execute(): Executeステートメントの実行が/www/zendsvr6/htdocs/cust600.phpの69行目で失敗しました。)
  • QHTTPSVR ZENDSVR6ジョブ ログ: SQ99999 Error occurred in SQL Call Level Interface. 9 — Argument value not valid.(SQ99999 SQL Call Level Interfaceでエラーが発生しました。9 — 引数の値が無効です。)

そして、逆に言えば、追加の5番目のバインドされたパラメーターをコーディングしても、警告が生成されますが、それでもPHPスクリプトは実行され、顧客データが返されます。

技術情報code25

生成されるエラーは次のようになります。

  • log: PHP Warning: db2_bind_param(): Describe Param Failed in /www/zendsvr6/htdocs/cust600.php on line 60(PHP Warning: db2_bind_param(): Describe Paramが/www/zendsvr6/htdocs/cust600.phpの60行目で失敗しました。)
  • QHTTPSVR ZENDSVR6ジョブ ログ: SQ99999 Error occurred in SQL Call Level Interface. 9 — Argument value not valid.(SQ99999 SQL Call Level Interfaceでエラーが発生しました。9 — 引数の値が無効です。)

フル タイムアウト

PHPスクリプトがクライアントに応答を返す時間には、秒数の制限があります。この値は、 php.ini ファイルで定義されます。このファイルをPHPのシステム値とみなします。フルパスは/usr/local/zendsvr6/etc/php.iniで、値は、次のステートメントで、秒数で定義します。

技術情報code26

PHPスクリプトは set-time-limit() 関数を使ってこの値をオーバーライドすることができます。この関数はその最大実行時間に追加したい秒数を追加します。ただし実際には、システム コールは全体の実行時間に含まれないため、PHPスクリプトは通常はタイム アウトしません。この例では、QSQSRVRヘルパー ジョブの最大の実行秒数は、/www/zendsvr6/conf/fastcgi.confファイルの別の構成設定によって制御されます。

技術情報code27

CUST600SP RPGプログラムをSTRSRVJOB/STRDBGでデバッグし、ブレークポイントを設定してPHPスクリプトを実行すると、そのブレークポイントで停止します。他に何もしなければ、約60秒後に、次のようなエラーが表示されます。

  • Chrome: 408 Request Time-out. Server timeout waiting for the HTTP request from the client.(408 リクエストがタイムアウトしました。クライアントからのHTTPリクエストの待機中にサーバーがタイムアウトしました。)
  • MSIE: HTTP 408/409 – The website is too busy to show the webpage(HTTP 408/409 – Web サイトはビジー状態のためWebページを表示できません。)
  • ログ: PHP Fatal error: Maximum execution time of 60 seconds exceeded in /www/zendsvr6/htdocs/cust600.php on line 69(PHP致命的エラー: /www/zendsvr6/htdocs/cust600.phpの69行目で60秒の最大実行時間を超過しました。)

60秒は永遠のようにも思えますが、誰かがその値を小さく設定し過ぎた場合や、ストアード プロシージャーがエラーで停止した場合に、このエラーを受け取ることがあるかもしれません。RequestTimeoutの値を変更する場合は、QHTTPSVRサブシステムのZENDSVR6ジョブを再開始します。

ボックス スコア

言うまでもなく、起こり得るすべてのエラー条件をカバーできたわけではありませんが、これらの個々の問題を処理するのに十分な知識は身に付いたはずです。新たなPHPの問題があなたの身に降り懸かっても、うまくいけばログに現われるでしょう。

ページトップ

ボタン