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


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

回答の条件
  • 1人5回まで
  • 登録:
  • 終了:2015/03/15 13:25:03

回答3件)

id:a-kuma3 No.1

回答回数4971ベストアンサー獲得回数2153

<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つ送った場合には、

  • リクエストパラメータとして、1Mbyte × 1.4 × 5 = 7Mbyte
  • デコードされたファイルの 1Mbyte

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

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

他5件のコメントを見る
id:TransFreeBSD

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

2015/03/12 13:06:02
id:a-kuma3

mime multipartはこの辺っぽい?

長いっ!
BASE64 でエンコードされたまま、テンポラリファイルに書き出してるっぽいですね。

2015/03/12 13:45:50
id:yoneto164 No.2

回答回数813ベストアンサー獲得回数94

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

id:hotmilkcocoa No.3

回答回数1ベストアンサー獲得回数0

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

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

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

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

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

  • max_execution_time

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

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

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

  • post_max_size

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

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

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

  • upload_max_filesize

アップロードできる 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();

}
?>
アップロードページに戻る

コメントはまだありません

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません