Ajaxによるmultipart/postでの画像ファイルアップロード

モバイルアプリでは、サーバーと連動して動作するものが多くみられます。
ハイブリッドアプリでこうしたアプリを作る場合は、Ajaxで実現するのが一般的だと思いますので、そのやり方について説明します。

追記:Android 2.3系およびそれ以前のAndroidは、FormDataオブジェクトが未定義のため、この記事の方法は利用出来ません。ご注意下さい。

追記2:Android 4.4では、Formからのファイル選択自体ができないため、この記事の方法は利用出来ません。(4.4.2で確認。今後のバージョンアップ出来るようになるかは不明です)
formタグを使わない手段については、こちら

サーバー側の実装

まずは、サーバー側の機能の実装です。
ここでは、普通にブラウザからもアップロード出来るような作りを考えます。実装はphpですが、他の言語でも基本的に同じように作れると思います。

/uploader.php


<?php
  $textUpload = "";
  if ($_FILES['userfile']):
      $uploadfile = __DIR__ . '/uploads/image.jpg';
    if (move_uploaded_file($_FILES['userfile']['tmp_name'],$uploadfile)) {
      $textUpload = "File is uploaded";
    } else {
      $textUplaod = "Upload fail";
    }
  endif;
  if (preg_match('/^text\/html/', $_SERVER['HTTP_ACCEPT'])) : ?>
<pre>
<?php echo $textUpload; ?>
</pre>
<form enctype="multipart/form-data" action="/uploader.php" method="POST">
  <input name="userfile" type="file" />
  <input type="submit" value="Send" />
</form>
Current Image:
<a href="https://www.asial.co.jp/uploads/image.jpg?<?php echo time(); ?> &mode=1" class="popupimg"><img src="https://www.asial.co.jp/uploads/image.jpg?<?php echo time(); ?>"></a>
<a href="/uploader.php"> REFRESH </a>
<?php else: 
  header( 'Content-Type: application/json; charset=utf-8', true ); 
  echo json_encode( array("message" => "Upload is OK")  );
  endif; ?>

写真をおくためにuploadsディレクトリを作成して、書き込み権限を付与しておきます。
この状態で、ブラウザからアクセスすると、投稿フォームがあるので、そこから画像を
アップロードしてみて下さい。

コードの中で$_SERVER['HTTP_ACCEPT'])) を見ているのは、ブラウザフォームからの
アクセス(html/textを要求)なのか、後に行うAjaxからのアクセス(jsonを要求)なのか
を判定するためです。
なので、もしAjaxからのアクセスしか行わないのであれば、$_SERVER['HTTP_ACCEPT']))
に対するif判定でtrueの部分の処理はいりません。

アプリ側の実装

次に、Monacaから「最小限のテンプレート」でプロジェクトを作成します。
利用するプラグインとして、「jQuery」を設定します。

index.htmlのheader部のjavascriptを以下のようにします。


        function upload(form) {
            $form = $('#upload-form');
            fd = new FormData($form[0]);
            $.ajax(
                'http://[サーバーのURL]/uploader.php',
                {
                type: 'post',
                processData: false,
                contentType: false,
                data: fd,
                dataType: "json",
                success: function(data) {
                    alert( data.message );
                    console.log(data);
                },
                error: function(XMLHttpRequest, textStatus, errorThrown) {
                    alert( "ERROR" );
                    alert( textStatus );
                    alert( errorThrown );
                }
            });
            return false;
        }

また、body部のhtmlは


 <form id="upload-form" method="post" enctype="multipart/form-data" onSubmit="return upload(this);">
 <input id="upload-form-file" name="userfile" size="27" type="file" accept="image/*;capture=camera"/>
 
 
 <input type="submit" name="submit" value="OK" />
 </form>

とします。

これで、アプリを起動して、写真を選択、OKボタンを押すと、サーバーに接続して、写真ファイルをアップロードが行えます。アップロード後、サーバー側の「REFRESH」リンクをクリックすると、投稿した画像に更新されていることが確認出来ます。

コードの内容は、フォーム内容をFormDataクラスとして取得し、それをAjaxによりサーバーに送っているだけなので、難しいところはあまりないと思います。。dataTypeを「json」としているのは、サーバからのレスポンスを受けるにはjsonが便利だからなので、htmlやtextとして取得しても問題ありません。(success処理の実装は少し変わります)

まとめ

簡単ではありますが、Ajaxを利用して画像ファイルをアップロードするサンプルコードを紹介しました。Ajaxを利用するとページの再読み込みが必要ないので、Onsen UIなどのフレームワークからも使えます。

便利なのは、通常のフォームからデータを取得しているので、画像以外のデータも合わせて送ることが出来ること、既存のフォームにもデータを送ることが出来ることなどです。

1つ注意点としては、上記の中でフォームからアップロードする写真を選ぶ部分は、ユーザーが手動でやらなくてはならず、勝手に写真をアップロードすることは出来ません。これはセキュリティ的な制限のためです。アプリの内部に保存した画像ファイルをアップロードする場合は、Monaca (Cordova)のFileTransferを使うことになります。

サーバーと連動するアプリの開発に役立ててみて下さい。