第5回 HTTP_Download クラス - ダウンロード支援ライブラリ (2) - PEAR講座
くまっち先生のPEAR講座
Lecutures on PHP
第5回 HTTP_Download クラス - ダウンロード支援ライブラリ (2) (その1)
第5回のPEAR講座は、前回取り上げたHTTP_Downloadパッケージを引き続き紹介していきます。前回はファイルをダウンロードさせるためのクラス初期化、設定を行う機能を紹介しました。今回はいよいよデータの送信する機能の紹介です。以下のリスト1が、データ送信機能であるsendメソッドの全貌です。
リスト1. sendメソッド全体コード
1 function send($autoSetContentDisposition = true)
2 {
3 if (headers_sent()) {
4 return PEAR::raiseError(
5 'Headers already sent.',
6 HTTP_DOWNLOAD_E_HEADERS_SENT
7 );
8 }
9
10 if (!ini_get('safe_mode')) {
11 @set_time_limit(0);
12 }
13
14 if ($autoSetContentDisposition &&
15 !isset($this->headers['Content-Disposition'])) {
16 $this->setContentDisposition();
17 }
18
19 if ($this->cache) {
20 $this->headers['ETag'] = $this->generateETag();
21 if ($this->isCached()) {
22 $this->HTTP->sendStatusCode(304);
23 $this->sendHeaders();
24 return true;
25 }
26 } else {
27 unset($this->headers['Last-Modified']);
28 }
29
30 if (ob_get_level()) {
31 while (@ob_end_clean());
32 }
33
34 if ($this->gzip) {
35 @ob_start('ob_gzhandler');
36 } else {
37 ob_start();
38 }
39
40 $this->sentBytes = 0;
41
42 if ($this->isRangeRequest()) {
43 $this->HTTP->sendStatusCode(206);
44 $chunks = $this->getChunks();
45 } else {
46 $this->HTTP->sendStatusCode(200);
47 $chunks = array(array(0, $this->size));
48 if (!$this->gzip && count(ob_list_handlers()) < 2) {
49 $this->headers['Content-Length'] = $this->size;
50 }
51 }
52
53 if (PEAR::isError($e = $this->sendChunks($chunks))) {
54 ob_end_clean();
55 $this->HTTP->sendStatusCode(416);
56 return $e;
57 }
58
59 ob_end_flush();
60 flush();
61 return true;
62 }
これまでのPEAR講座に登場してきたコードリストと比べて非常に長いものになっています。よく見ると別のクラス内メソッドを実行している場所もあったりとなかなかボリューム満点な内容です。
この長い62行のコードですが良く見ると、空白行で区切られたブロックが複数あるのがお分かりでしょうか。このブロックそれぞれで、データ送信を行うために必要となる様々な処理を行っています。長いコードもこのブロック単位で捉えることで理解がしやすくなります。それではブロック単位でこのコードを順に読み進めていき、その内容を確認していきましょう。果たしてダウンロードデータを送信するための様々な処理とはどのようなものなのでしょうか。
初期確認処理および初期設定処理
まず始めにチェックするのは3行目から12行目までのコードです。ここでは2つのブロックがあります。
リスト2. sendメソッド 初期処理
3 if (headers_sent()) {
4 return PEAR::raiseError(
5 'Headers already sent.',
6 HTTP_DOWNLOAD_E_HEADERS_SENT
7 );
8 }
9
10 if (!ini_get('safe_mode')) {
11 @set_time_limit(0);
12 }
3~8行目の1番目のブロックは《HTTP通信を行うためのヘッダのチェック》を行っています。第2回、第3回の講座にも同様の確認処理がありましたね。これから行うHTTP通信に先駆けて、既に別のHTTPヘッダ情報が出力されていないかどうかの確認を行っています。既に出力されてしまっていた場合は、エラーを発生させるためにraizeErrorメソッドを実行し処理を終了しています。これまでHTTP通信について紹介してきましたので、エラー発生と共に指定されているメッセージ「Headers already sent.」(ヘッダは既に送信した)が意図するものも理解できるのではないでしょうか。
10~12行目は、これから行うダウンロードデータ送信処理に先駆けて《PHPの実行時間を無制限にする》処理を行っています。これは、データをダウンロードするためにかかる時間が長時間になることを想定しての振る舞いです。
通常Webページを閲覧する場合は数秒で処理が完了しますが、データダウンロードを行う場合、その対象となるデータは大きい可能性があります。この容量の大きいデータをPHPプログラムから排出するわけですが、実はPHPプログラムが動作して処理できる時間には制限が設けられています。この制限時間はphp.iniの設定におけるmax-execution-timeの値で決定し、初期値は30秒になっています。(*1)
set_time_limit関数は、このプログラム動作時間の制限を変更することのできる関数で、0を与えることで無制限にすることができます。ここではダウンロード処理に先駆け、思わぬ形でプログラムが中断しないよう無制限設定を行っています。
if文による条件分岐が行われていますが、これはPHPがセーフモード(*2)に設定されている場合はset_time_limit関数による制限変更が行えないことを受けてのコードでしょう。セーフモードに設定されている場合にset_time_limit関数を実行すると、Warningによる警告が発生してしまいます。
またset_time_limit関数の先頭に付与されている「@」はエラー制御演算子(*3)と呼ばれるものです。目的の式、および関数実行の際にこのエラー制御演算子を付与することで、その実行結果に伴って発生するエラーが無視されます。 今回の場合ですと、セーフモードが設定されているにも関わらずにset_time_limit関数を実行すると前述のとおりWarningが発生しますが、これを無視します。エラーログとしての出力も行われません。ただし直前にif条件分岐文がありますので恐らくこのWarningが発生することはないのではないでしょうか。
ダウンロードデータファイルの名称を指定
続いて14行目から17行目までのブロックのコードは、《ダウンロードデータファイルの名称を指定》する処理を行っています。
リスト3. sendメソッド ファイル名決定
14 if ($autoSetContentDisposition &&
15 !isset($this->headers['Content-Disposition'])) {
16 $this->setContentDisposition();
17 }
「前回の講座で送信ファイルを指定するsetFileメソッドがあったじゃないか」と思われた方はなかなか鋭いです。ですがこちらはそういったものではなく「何というファイル名で保存させるか」を指定する処理になります。ブラウザを使ってデータをダウンロードする際、ポップアップする保存用ウィンドウと共にファイル名が表示されますが、このファイル名はサーバ側(レスポンス側)から以下のような「Content-Disposition」というHTTPヘッダフィールドが送信された結果によるものです。以下の場合ですと、example.txtとして保存するよう、保存ウィンドウに表示されます。
Content-Disposition: attachment; filename="example.txt"
このブロックでは、クラスのプロパティの1つであるheaders配列のContent-Dispositionキーの内容がブランクならば、setContentDispositionメソッドを実行しています。リスト4がそのメソッドの内容です。
リスト4. setContentDispositionメソッド
function setContentDisposition( $disposition = HTTP_DOWNLOAD_ATTACHMENT,
$file_name = null)
{
$cd = $disposition;
if (isset($file_name)) {
$cd .= '; filename="' . $file_name . '"';
} elseif ($this->file) {
$cd .= '; filename="' . basename($this->file) . '"';
}
$this->headers['Content-Disposition'] = $cd;
}
先ほどのContent-Dispositionヘッダフィールドを文字列の内容として作成し、クラスプロパティheaders配列のContent-Dispositionキーの値として登録しています。
なおこのsetContentDispositionメソッドは、第4回の講座でも登場したsetXXXメソッド群の1つにもあたります。よってクラスのコンストラクタ、もしくはsetParamsメソッドを利用したり、直接このメソッドを利用するなどして予めContent-Dispositionヘッダフィールドの値を作成しておくことも可能です。sendメソッド14~15行目のif条件分岐文によって確認しているのもこれが理由で、既にContent-Dispositionヘッダフィールドが完成している可能性があるためです。
----*1)
http://jp.php.net/manual/ja/ref.info.php#ini.max-execution-time
*2)
http://jp.php.net/manual/ja/features.safe-mode.php
*3)
http://jp.php.net/manual/ja/language.operators.errorcontrol.php
- 1
- 2





ページのトップへ


GETのままでは検索エンジンのロボットが拾ってくれなかったためにSEO対策として有効だと言われていますね。