More than 3 years have passed since last update.

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

Blobstoreは昔からあるServiceで、GAEにくっついて存在するServiceです。

GCSはGoogle Cloud Platform の中でFileを扱うことに長けたServiceです。

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

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


GCSの設定

Productionで動かす場合、GCSの設定を行う必要があります。

GCSの設定については、こちらの記事を参照してください。

Localで試すだけなら、この設定はする必要はありません。


Google Cloud Storage Client Library Download

GAE/jからGCSのファイルを扱うためのLibraryが公式で提供されています。

まずはこれをDownloadします。

いくつか方法が用意されているので、お好きな方法でどうぞ。

https://developers.google.com/appengine/docs/java/googlecloudstorageclient/download

また、Sample もあります。

この記事の内容も、ほとんど公式のSampleどおりです。

Slim3用に少し修正を加えているのと、SampleはファイルをUploadするのではなく、入力した文字列をGCSに書き込んでいるので、それをファイルUploadに変えています。


備考

このSampleでは大きなファイルは扱えません。

大きなファイルを扱いたい場合は、Part2を参照してください。


File Upload

UploadされたFileをGCSに保存しているのが以下のsrcです。

そんなに特筆することもなく、見たまんまという感じです。

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制限に引っかかってしまいます。

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