dip Engineer Blog

Engineer Blog
ディップ株式会社のエンジニアによる技術ブログです。
弊社はバイトル・はたらこねっとなど様々なサービスを運営しています。

ajax(jQuery)+PHP(Laravel)で突然ファイルアップロードができなくなった話

はじめに

こんにちは、PHPで求人系サービスの開発や社内向けツールの開発を行なっている @taku-0728 です。 今回は弊社のある社内向けツールで突然ファイルアップロードができなくなった問題が発生したため、 同じような事象に悩んでる方々の参考になればと思い事象と解決策をまとめます。 いろいろと思考錯誤した結果を記載していますが、筆者は最終的に「その3 php.iniの設定見直し」で解決しました!

対象コード

<?php
    <form class="form-horizontal" id="upload-form">
        @csrf
        <input type="file" class="upload">
        <button type="submit">送信</button>
    </form>
?>
<script>
    var formData;
    $('#upload-form').on('submit', function(event) {
        formData = new FormData($('#upload-form').get(0));
        $.post({
            url: "{{ route('upload.store', compact('environment', 'project')) }}",
            data: formData,
            processData: false,
            contentType: false,
            success: function() {
                location.href = "{{ route('reserves.index', compact('environment')) }}";
            },
        });
    });
</script>

関係あるところだけ切り取ったのでフェイクも入ってますが、対象のコードは大体こんな感じだったはずです。

事象

ある日、突然「ファイルアップロードに失敗する」と連絡を受けたのでいろいろ調べてみると、 ajaxでサーバ側に送られるリクエスト内で、アップロードしたいファイルに関するmimetypeとsizeが期待値と異なっていることがわかりました。

期待値

Illuminate\Http\UploadedFile::__set_state(
    array(
        'test' => false,
        'originalName' => 'image.jpeg',
        'mimeType' => 'image/jpeg',
        'size' => 719449,
        'error' => 0,
        'hashName' => NULL,
    )
),

実際の値

Illuminate\Http\UploadedFile::__set_state(
    array(
        'test' => false,
        'originalName' => 'image.jpg',
        'mimeType' => 'application/octet-stream',
        'size' => 0,
        'error' => 6,
        'hashName' => NULL,
    )
),

フレームワークはLaravelを使用しているので、リクエストはIlluminate\Http\UploadedFileクラス形式で送られます。 その中身を見てみるとファイル名は正常に取得できるにもかかわらず、mimetypeがapplication/octet-streamになっており、 sizeも0になっていることがわかります。どうやら原因はここにありそうです。

対処法

その1 ディスク容量を確認してみる

ファイルがアップロードできないとのことなので、まず下記コマンドでディスク容量を確認してみます。
# df -h

詳細は割愛しますが、この時ディスク容量の使用状況が98%とほぼ限界に近い値でした。 古いログファイルなどが残っていたので削除してディスク容量を開放しましたが、解決しなかったので対処法2へ

その2 enctype="multipart/form-data" を追加してみる

コードは書き変えていないのにファイルアップロードができなくなったので、ブラウザなどの仕様変更の可能性を疑いました。 mimetypeが想定と違っていることが原因でないかと考え、今まで指定していなかったmimetypeをmultipart/form-dataに指定してみることにしました。 multipart/form-dataとはmimetypeの1種であり複合データ型のことを指します。
1回のHTTP通信で、複数の種類(テキストやファイルなど)を一度に扱うことができ、ファイルアップロードでよく使われる形式です。
multipart/form-data形式でデータをPOSTするにはformタグの中に下記のように追記します。

<form class="form-horizontal" id="upload-form" enctype="multipart/form-data">

これを追記してアップロード処理を実施してみると、エラーが変わって下記のようになりましたが、問題は解決しなかったので対処法3へ
PHP Warning: Missing boundary in multipart/form-data POST data in Unknown on line 0

その3 php.iniの設定見直し

コードは書き換えておらず、ブラウザなどの仕様変更の可能性も疑いましたが他のブラウザでも同様の事象が起きたため、今度は環境周りの設定値を疑いました。 php.iniのファイルアップロード周りの設定を確認してみます。 今回は以下の設定値を確認しました。

post_max_size (POSTデータの最大サイズ ※1回のアップロードファイルすべての合計サイズ)
upload_max_filesize (1ファイルあたりの最大アップロードサイズ)
upload_tmp_dir (formからファイルを受け取る時の一時保存場所)

当初、ファイルのアップロードサイズが小さすぎるのではないかと思い確認しましたが、 post_max_sizeupload_max_filesizeともに十分な容量でした。
次にupload_tmp_dirを確認すると、こちらはコメントアウトされており、 値は未設定となっていました。

そこで
upload_tmp_dir => no value => no value
これを
upload_tmp_dir => /tmp => /tmp

こうすると正常にアップロードできるようになりました!

考察

正直言ってなぜupload_tmp_dirに値を設定すると正常にファイルがアップロードできたのか完全には理解できていないのですが、 ディスク容量が不足していたために一時保存領域として使っていたディレクトリに書き込みができなかったのかと推測しています。 また原因が判明したら追記しようと思います。

筆者

https://dippeople.dip-net.jp/6818/dippeople.dip-net.jp (写真右)