人力検索はてな
モバイル版を表示しています。PC版はこちら
i-mobile

PHPでファイルのアップロード機能を作るためには、以下のURLに記載されている方法で実現すると思うのですが、実際に、内部の処理はどうなっているのでしょうか?例えば、Javaで実装する場合はストリーミングの手法を使って、サーバで使われるメモリ消費量を極力小さくしますが、ちゃんとこのような実装がされているのでしょうか?それとも、アップロードされたファイルの情報は一旦すべてメモリ上にロードされてしまうのでしょうか(その後、一時ファイルに出力される)?もし、すべて一旦メモリ上にロードされるとなると、大きなファイルのアップロードを受け付けると、サーバのメモリが一気になくなってしまう可能性があることを懸念しているため、質問させて頂いています。

http://php.net/manual/ja/features.file-upload.post-method.php

●質問者: indybark
●カテゴリ:ウェブ制作
○ 状態 :終了
└ 回答数 : 3/3件

▽最新の回答へ

1 ● a-kuma3

<input type="file" /> で指定したファイルは、リクエストのボディ部に MIME の multipart という形式で格納されてサーバに送信されます。
http://www.atmarkit.co.jp/ait/articles/0104/18/news002.html
# 上記はメールの添付ファイルについて書かれていますが、<input type="file" /> で送信するときも同じです。

バイナリのファイルは BASE64 という形式でテキスト化されます。
BASE64 でエンコードされたデータは、大体 4割くらい元のデータよりもサイズが大きくなります。
http://ja.wikipedia.org/wiki/Base64

PHP の最新は 5.6.6 でしょうか。
ダウンロードページから、ソースを落として読んでみました。
http://php.net/downloads.php

BASE64 のデコード、つまりリクエストで送られた MIME の multipart 形式で格納されたデータを境界で切り分けて、ここのファイルをバイナリに戻す時に呼び出される関数(のはず)です。

php-5.6.6/ext/standard/base64.c

/* {{{ proto string base64_decode(string str[, bool strict])
 Decodes string using MIME base64 algorithm */
PHP_FUNCTION(base64_decode)
{
 char *str;
 unsigned char *result;
 zend_bool strict = 0;
 int str_len, ret_length;

 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|b", &str, &str_len, &strict) == FAILURE) {
 return;
 }
 result = php_base64_decode_ex((unsigned char*)str, str_len, &ret_length, strict);
 if (result != NULL) {
 RETVAL_STRINGL((char*)result, ret_length, 0);
 } else {
 RETURN_FALSE;
 }
}
/* }}} */

パラメータ領域からデータを切り出して、str が指す領域に格納し、php_base64_decode_ex という関数でデコード(バイナリに戻す)したデータのアドレスを戻します。


少なくとも、リクエストパラメータはメモリ領域にすべて展開されること、BASE64 でエンコードされたパラメータが保持されたままで、ひとつのファイルはデコードされる、ということが分かります。

つまり、1Mbyte のファイルを 5つ送った場合には、

の、計 8Mbyte は 少なくともメモリ上に確保されます。
# MIME multipart の境界やヘッダ、その他のリクエストパラメータは、誤差の範囲

アップロードされたファイルの実体は $_FILES['userfile']['tmp_name'] でアクセスされるということは、一旦、テンポラリの領域に実データとして保存されているということなので、送信した全てのファイルがデコードされた状態でメモリに展開されるわけではなく、ひとつのファイルをデコードしてテンポラリ領域に保存する、というふうに処理されていると思います。


indybarkさんのコメント
詳しい解説ありがとうございます。大変、助かりました。 送られてきたデータをメモリに蓄えるのではなく、どんどんテンポラリディレクトリのファイルに書き込んでいってしまえば良い気がするのですが、、、 PHPでは大きさなサイズのファイルのアップロードを受け付けるWebサイトを作るのは諦めたほうが良いのでしょうか?なにか、もっと良いアップロードを受け付ける方法はあるのでしょうか?

a-kuma3さんのコメント
>> 送られてきたデータをメモリに蓄えるのではなく、どんどんテンポラリディレクトリのファイルに書き込んでいってしまえば良い気がするのですが、、、 << 気持ちは分かりますが、データを細切れに読み込んで処理をすると、今度はパフォーマンスが落ちます。 PHP(だけではないですが)は、リソースやパフォーマンスの有効活用に特化した言語ではないので、自身の実装も含めて扱いやすさを優先させているのでしょう。 パフォーマンスの最適化を優先させるような場合には、ライブラリでの処理と同等にメモリやCPUのチューニングも行われるので、通常は「メモリを積めば良いじゃない」という選択をされるのが常です。 その方がコストが安いので。 レンタルサーバなどで使えるメモリの上限がある場合には厳しいですが、仕方ありません。

a-kuma3さんのコメント
PHP の場合は、Apache との組み合わせになるので、POST のボディを細切れで読む術がありません。

TransFreeBSDさんのコメント
そのコードはphpの関数のbase64_decodeのコードでないでしょうか。 http://php.net/manual/ja/features.file-upload.post-method.php >> php.ini の upload_tmp_dir ディレクティブで 他の場所を指定しない限り、ファイルはサーバーにおけるデフォルトの テンポラリディレクトリに保存されます。 << とあるのでテンポラリファイルに書きだされているのではないでしょうか。 phpの内部構造と処理の流れについてのスライドがありました。 http://www.slideshare.net/do_aki/php-and-sapi-and-zendengine2-and これ参考にたどろうと思いましたが https://github.com/php/php-src/blob/master/main/main.c#L1565 よくわからないので感で https://github.com/php/php-src/blob/master/main/php_variables.c#L315 https://github.com/php/php-src/blob/master/main/SAPI.c#L268 https://github.com/php/php-src/blob/master/sapi/apache2handler/sapi_apache2.c#L187 って感じで、SAPI.cがテンポラリファイルに書きだしてる気がします。

a-kuma3さんのコメント
>> そのコードはphpの関数のbase64_decodeのコードでないでしょうか。 << POST されたデータは、BASE64 でデコードされて、テンポラリディレクトリに保存される、という想定でした。 違うのかな...

TransFreeBSDさんのコメント
mime multipartはこの辺っぽい? https://github.com/php/php-src/blob/master/main/rfc1867.c#L685 データの流れがいまいち追えてないないけど。

a-kuma3さんのコメント
>> mime multipartはこの辺っぽい? << 長いっ! BASE64 でエンコードされたまま、テンポラリファイルに書き出してるっぽいですね。

2 ● ヨネちゃん

共有のレンタルサーバーであれば、一度にアップロード出来る容量は、サーバ側で2?5Mbyte程度に制限されています。
また、1つのPHPが使用できるメモリの容量も128Mbyteになっているのが通常です。
実行時間も通常30秒以内に限られています。


3 ● caramelopardalis

PHP を触り始めて 1 年にも満たないのでそれほど詳しいわけではないですが、最近同じことを調べたので参考になればと思います。

テンポラリファイルが作成され終わるまでの間の処理で、アップロードされたファイルがそのままメモリに展開されることはないようです。

参考 URL
http://stackoverflow.com/questions/14714226/large-php-file-upload

ただし、テンポラリファイルが作成された後、PHP のコードでファイルを移動したりする際には、そのメソッドがメモリをどのように消費するのか、max_execution_time の対象となる処理なのかを気にする必要があります。

巨大なファイルをアップロードする場合に影響する php.ini などの設定は以下の項目だと思っています。

PHP の最大実行時間を制限するものです。

この項目を 5 秒に設定し、数百 KB のファイルをアップロードした場合は
問題ありませんでしたが、4 GB 程度のファイルをアップロードした場合は
エラーが発生しました。
60 秒に設定した場合、どちらの場合でも問題ありませんでした。
4 GB のファイルアップロードに用した時間は 120 秒程度です。

以上のことから、アップロード中の通信待ちの時間はこの対象には
含まれないが、ファイルの移動処理の一部の時間は
対象になっているのかもしれません。それ以上詳しくは調べませんでした。

POST データに許可される最大サイズを設定します。

これは単純にサイズを制限するので、想定される最も大きいサイズを指定すれば
良いと思います。

PHP や Apache などが 32 bit 版だと整数の最大値を気にしなければならない
かもしれません。
ちなみに、私の場合 Windows 7、PHP、Apache のいずれも 64 bit 版の環境で
4G と指定したらエラーが出ました。原因はわかりませんが、
4294967296 のように記述するとエラーがでなくなりました。

アップロードできる 1 ファイルあたりの最大サイズです。

アップロードされるファイルのデータは POST データに含まれるので、
post_max_size で指定した値よりも小さくなければなりません。

この設定も post_max_size と同様に整数の制限があります。




色々ググってできた情報と実際に試してみたのとを考えると、max_input_time および
memory_limit は、アップロードできるファイルのサイズとは関係ないように思えます。

以下の 2 つのファイルからなるコードで試しました。



// index.php
?>

<html>
<head>


<title>アップロードテスト</title>
</head>
<body>




</body>
</html>




// upload.php
// http://qiita.com/mpyw/items/939964377766a54d4682

header('Content-Type: text/html; charset=utf-8');
header('X-Content-Type-Options: nosniff');

try {

// 未定義である・複数ファイルである・$_FILES Corruption 攻撃を受けた
// どれかに該当していれば不正なパラメータとして処理する
if (
!isset($_FILES['user_upload_file']['error']) ||
!is_int($_FILES['user_upload_file']['error'])
) {
throw new RuntimeException('パラメータが不正です');
}

// $_FILES['upfile']['error'] の値を確認
switch ($_FILES['user_upload_file']['error']) {
case UPLOAD_ERR_OK: // OK
break;
case UPLOAD_ERR_NO_FILE: // ファイル未選択
throw new RuntimeException('ファイルが選択されていません');
case UPLOAD_ERR_INI_SIZE: // php.ini定義の最大サイズ超過
case UPLOAD_ERR_FORM_SIZE: // フォーム定義の最大サイズ超過
throw new RuntimeException('ファイルサイズが大きすぎます');
default:
throw new RuntimeException('その他のエラーが発生しました');
}

// $_FILES['upfile']['mime']の値はブラウザ側で偽装可能なので
// MIMEタイプに対応する拡張子を自前で取得する
$finfo = new finfo(FILEINFO_MIME_TYPE);
if (!$ext = array_search(
$finfo->file($_FILES['user_upload_file']['tmp_name']),
array(
'gif' => 'image/gif',
'jpg' => 'image/jpeg',
'png' => 'image/png',
),
true
)) {
throw new RuntimeException('ファイル形式が不正です');
}

// ファイルデータからSHA-1ハッシュを取ってファイル名を決定し、保存する
if (!move_uploaded_file(
$_FILES['user_upload_file']['tmp_name'],
$path = sprintf('../../upload-test/%s.%s',
sha1_file($_FILES['user_upload_file']['tmp_name']),
$ext
)
)) {
throw new RuntimeException('ファイル保存時にエラーが発生しました');
}

// ファイルのパーミッションを確実に0644に設定する
chmod($path, 0644);

echo 'ファイルは正常にアップロードされました';

} catch (RuntimeException $e) {

echo $e->getMessage();

}
?>
アップロードページに戻る
関連質問

●質問をもっと探す●



0.人力検索はてなトップ
8.このページを友達に紹介
9.このページの先頭へ
対応機種一覧
お問い合わせ
ヘルプ/お知らせ
ログイン
無料ユーザー登録
はてなトップ