GAE/jでファイルを扱う Part1


GAEでユーザからのファイルのUploadなどを受け取る場合、選択肢は2つあります。

* [Blobstore](https://developers.google.com/appengine/docs/java/blobstore/#Java_Writing_files_to_the_Blobstore)
* [Google Cloud Storage (以下GCS)](https://developers.google.com/storage/index)

Blobstoreは昔からあるServiceで、GAEにくっついて存在するServiceです。
GCSはGoogle [Cloud Platform](https://cloud.google.com/?hl=ja) の中でFileを扱うことに長けたServiceです。

将来的にはGCSに集約したいという話もあるので、これからGAEでアプリを作る場合は、GCSを選択するのが無難でしょう。

この記事ではGAE/j (Slim3)からGCSのファイルを扱う方法について書きます。

# GCSの設定
Productionで動かす場合、GCSの設定を行う必要があります。
GCSの設定については、[こちらの記事](http://qiita.com/sinmetal/items/20fc9ae601161291b16e)を参照してください。
Localで試すだけなら、この設定はする必要はありません。

# Google Cloud Storage Client Library Download
GAE/jからGCSのファイルを扱うための[Library](https://developers.google.com/appengine/docs/java/googlecloudstorageclient/)が公式で提供されています。
まずはこれをDownloadします。
いくつか方法が用意されているので、お好きな方法でどうぞ。
https://developers.google.com/appengine/docs/java/googlecloudstorageclient/download

また、[Sample](https://developers.google.com/appengine/docs/java/googlecloudstorageclient/getstarted) もあります。
この記事の内容も、ほとんど公式のSampleどおりです。
[Slim3](https://sites.google.com/site/slim3documentja/)用に少し修正を加えているのと、SampleはファイルをUploadするのではなく、入力した文字列をGCSに書き込んでいるので、それをファイルUploadに変えています。

# 備考
このSampleでは大きなファイルは扱えません。
大きなファイルを扱いたい場合は、[Part2](http://qiita.com/sinmetal/items/7d2617001494cd204255)を参照してください。

# File Upload
UploadされたFileをGCSに保存しているのが以下のsrcです。
そんなに特筆することもなく、見たまんまという感じです。

```Java
public class FileController extends Controller {

	private final GcsService gcsService = GcsServiceFactory
		.createGcsService(new RetryParams.Builder().initialRetryDelayMillis(10)
			.retryMaxAttempts(10).totalRetryPeriodMillis(15000).build());

	...

	private void doPost() throws IOException {
		FileItem fileItem = requestScope("uploadFile");

		GcsOutputChannel outputChannel =
				gcsService.createOrReplace(getFileName(),
						new GcsFileOptions.Builder().mimeType(fileItem.getContentType()).build());
		try (OutputStream outputStream = Channels.newOutputStream(outputChannel)) {
			outputStream.write(fileItem.getData());
		}
	}

	private GcsFilename getFileName() {
		// your gcs bucket name and file name
		return new GcsFilename("sample-bucket", "samplefile");
	}
}
```

UploadされたFileは、Google Cloud Console から見えることができます。
Localで試した場合は、Development Console Datastore Viewerの__GsFileInfo__というKindで保存されている情報を見ることができます。

# File Download
GCSのFileをDownloadして返しているのが以下のsrcです。
Uploadと比べて長くなっているのは、Blobstore APIを使う場合と使わない場合があるからです。
Blobstore APIを利用してGCSのFileを操作することもできます。
今のところ、どっち使っても良いような気がしますが、ファイルが大きいとGcsServiceを使うやり方だと、GAEのメモリ上でcopyしているので、60sec制限に引っかかってしまいます。

```Java
public class FileController extends Controller {

	private final GcsService gcsService = GcsServiceFactory
		.createGcsService(new RetryParams.Builder().initialRetryDelayMillis(10)
			.retryMaxAttempts(10).totalRetryPeriodMillis(15000).build());

	private static final int BUFFER_SIZE = 2 * 1024 * 1024;

	/** Blobstore APIを使うか */
	public static final boolean SERVE_USING_BLOBSTORE_API = false;


	...

	private void doGet() throws IOException {
		GcsFilename fileName = getFileName();
		if (SERVE_USING_BLOBSTORE_API) {
			BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
			BlobKey blobKey =
					blobstoreService.createGsBlobKey("/gs/" + fileName.getBucketName() + "/"
							+ fileName.getObjectName());
			blobstoreService.serve(blobKey, response);
		} else {
			GcsService gcsService = GcsServiceFactory.createGcsService();
			GcsFileMetadata metadata = gcsService.getMetadata(fileName);
			response.setContentType(metadata.getOptions().getMimeType());
			GcsInputChannel readChannel =
					gcsService.openPrefetchingReadChannel(fileName, 0, BUFFER_SIZE);

			copy(Channels.newInputStream(readChannel), response.getOutputStream());
		}
	}

	private GcsFilename getFileName() {
		// your gcs bucket name and file name
		return new GcsFilename("sample-bucket", "samplefile");
	}

	private void copy(InputStream input, OutputStream output) throws IOException {
		try {
			byte[] buffer = new byte[BUFFER_SIZE];
			int bytesRead = input.read(buffer);
			while (bytesRead != -1) {
				output.write(buffer, 0, bytesRead);
				bytesRead = input.read(buffer);
			}
		} finally {
			input.close();
			output.close();
		}
	}
}

```

# Github
srcはGithubに置いてあります。

## Controller
https://github.com/sinmetal/gcs-client-library-sample/blob/master/src/main/java/org/sinmetal/gcsexample/controller/FileController.java

## 試したhtml
https://github.com/sinmetal/gcs-client-library-sample/blob/master/src/main/webapp/fileupload.html