まめーじぇんと@Tech

技術ネタに関して (Android, GAE, Angular). Twitter: @mame01122

angular-file-uploadとGAE/Jでファイルとパラメータを一緒にアップロード

そもそものきっかけ

今作っているウェブサービスで、AngularJSを使っているフロントエンドから、ファイルと一緒に、ユーザの入力値も一緒にアップする必要があったので、その方法をメモ。

angular-file-upload (https://github.com/danialfarid/ng-file-upload)は有名なファイルアップロードのライブラリかと思います。実際のウェブサービスでは、このライブラリを使ってユーザ名と一緒にアイコンとなる画像もアップし、ユーザ情報を登録する・・・ということも多いと思いますが、フロントエンド/バックエンドの両方をカバーした説明がなく結構ハマりました。

フロントエンド

angular-file-uploadの使い方は結構情報があるので特に問題ないかもしれませんが、念のため。
https://github.com/danialfarid/ng-file-uploadからライブラリをダウンロードし、適宜読み込んでください。このあたりは、他のブログやStackOverFlowにお任せします。パラメータは、JSON形式でアップすることになるらしく、下記の記述であれば、(少なくとも僕の環境では)うまく動きました。

Javascript
    $scope.uploadFile = function () {

        var file = $scope.selectedFile[0];
        $scope.upload = $upload.upload({
            url: '/upload',
            method: 'POST',
            data: {data: parameter},
            file: file,
        }).success(function (data) {
			//成功時の処理
        });
    };
    
	$scope.onFileSelect = function ($files) {
		$scope.selectedFile = $files;
    };

このonFileSelectを、HTML側から呼び出して、ファイルを変数に格納します。

HTML側
<input type="file" ng-file-select="onFileSelect($files)">

そして、その他の情報(テキスト情報など)を入力させた後、HTML側からuploadFileを呼びます。

で、ここでのキモは、data: {data: parameter}の部分かと思います。このparameterとは何ぞや?というと、
僕の場合は

var parameter = {
	"id": 1,
	"title": "title",
	"tag": "tag",
}

のような変数を用意していました。

そしてそれを受け取るバックエンド(Servlet)側の実装です。

バックエンド

@Override
public String execute(HttpServletRequest request,
		HttpServletResponse response) throws Exception {

	String params = null;
	Blob thumbnail = null;

	try {
		ServletFileUpload upload = new ServletFileUpload();
		response.setContentType("text/plain");

		FileItemIterator iterator = upload.getItemIterator(request);
		while (iterator.hasNext()) {
			FileItemStream item = iterator.next();
			InputStream stream = item.openStream();

			if (item.isFormField()) {

				String value = Streams.asString(stream, "iso-8859-1");
				value = new String(value.getBytes("iso-8859-1"), "utf-8");

				JSONObject object = new JSONObject(value);
				params = object.getString("data");
			} else {
								+ ", name = " + item.getName());
				thumbnail = DatastoreUtil
						.transcodeInputStreamToBlob(stream);
			}
		}
	} catch (Exception e) {

	}

1つずつ見ていきたいと思います。

FileItemIterator, FileItemStream, ServletFileUpload, Streamsは、org.apache.commons.fileupload.servlet.ServletFileUploadのものです。必要に応じてjarファイルをダウンロード、プロジェクトに追加してください。
if (item.isFormField()) {}は、FORMのパラメータなのか、Blobデータなのかによって場合分けをする必要があるようです。フォームに入力されたテキスト情報はifの方、そうでないものはelseの方ですね。
そして、

String value = Streams.asString(stream, "iso-8859-1");
value = new String(value.getBytes("iso-8859-1"), "utf-8");

は、2バイト文字が文字化けをしないための対策です。もし日本語はじめ、2バイト文字を使う場合は入れておいてください。

あとはJSONObjectにして、そこからフロントエンド側で指定した"data"でオブジェクトを取得します。その後は、煮るなり焼くなりお好きにどうぞ。

また、elseの方では、InputStreamをBlobデータに変換しています(僕の場合はこのあと、Datastoreに格納しています)
このDatastoreUtil#transcodeInputStreamToBlobの中身は、下記のような感じです(まあ、不要かもですが)

public static Blob transcodeInputStreamToBlob(InputStream stream) {
	if (stream == null) {
		throw new IllegalArgumentException("Inputstream is null");
	}
	ByteArrayOutputStream bytes = new ByteArrayOutputStream();
	try {
		Streams.copy(stream, bytes, true);
		return new Blob(bytes.toByteArray());
	} catch (IOException e) {

	}

	return null;

	}

これで問題なく動く・・・ハズ!