Java
GoogleAppEngine
Slim3
appengine-gcs-client
GoogleCloudStorage
More than 3 years have passed since last update.

前置き部分はPart1を参照してください。


Part1と違う点

Part1のやり方だと、GAE経由でGCSとFileをやりとりしているので、1requestの60sec制限に縛られ、大きなファイルをやりとりすることができません。

Part2では、そこを改善します。

Part1とPart2で以下のように流れが変わります。

特に変わるのはFileUploadです。


Part1のUploadの流れ

以下の順番でFileが直接渡されている。

1. Client

2. GAE

3. GCS


Part2のUploadの流れ


  1. GAEがFileUpload用のURLを発行する。

  2. ClientはFileUpload用のURLに対して、FileをUploadする。このURLはGAE Frontendを経由しないので、60secにもかからない。

  3. FileUploadが完了すると、1で発行されたURLで動いているServerが、指定されたPathに対して自動で、UploadされたFile情報Parameterに含めてRequestを送ってくる。

  4. 3のRequestをGAEで受けて、UploadされたFileの情報をDatastoreなどに保存する。


File Upload用のURLを発行

Clientに対して、FileをUploadするための専用のURLを発行します。

URLの発行はBlobstore APIを利用して行います。

想像ですが、BlobstoreのためのServer (Picasaと同じServer?)がFileを受け取って、GCSに保存しているんじゃないかなと思います。

Bucket名しか指定できず、UploadされたファイルはBucketの直下に置かれます。

ファイル名はUUIDのような値になります。

"/uploadFile"がUploadが完了した時に、叩かれるPathになります。

public class UploadFileController extends Controller {

private static final String BUCKET = "sample-bucket";

private BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();

private void respondUploadUrl() throws Exception {
final long MEGA_BYTE = 1024 * 1024 * 1024;

final UploadOptions options =
UploadOptions.Builder.withGoogleStorageBucketName(BUCKET).maxUploadSizeBytes(
100 * MEGA_BYTE);
final String url =
BlobstoreServiceFactory.getBlobstoreService().createUploadUrl("/uploadFile",
options);
response.setCharacterEncoding("utf-8");
response.setContentType("application/json");
response.getWriter().println("{\"url\":\"" + new URL(url).getPath() + "\"}");
response.flushBuffer();
}
}

発行されるPathは以下の様な感じです。

表からは見えないけど、GAEの中にこのPathを受け取る誰かがいるのでしょう。

/_ah/upload/aglzaW5wa21ubXNyHAsSFV9fQmxvYlVwbG9hZFNlc3Npb25fXxj5Agw


File Upload後に呼ばれる処理

Upload用のURLを発行した時に指定した"/uploadFile"を受け取るのが以下のsrcです。

request parameterにUploadされたFileの情報を入れて送ってくるので、それを受け取って、Datastoreに保存しています。

public class UploadFileController extends Controller {

private static final String BUCKET = "sample-bucket";

private BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();

...

private void receiveBlobServiceRedirect() throws Exception {
final Map<String, List<BlobInfo>> infoListMap = blobstoreService.getBlobInfos(request);

for (Entry<String, List<BlobInfo>> entry : infoListMap.entrySet()) {
System.out.println(entry.getKey());

for (BlobInfo info : entry.getValue()) {
BlobContent content = new BlobContent(info);
Datastore.put(content);
}
}
}
}

保存されたFileの情報を管理するために、Datastoreに保存する。

そのためのModel


BlobContent.java

@Model

public class BlobContent {

/** BlobKey */
@Attribute(primaryKey = true)
Key key;

String filename;

String contentType;

Long size;

Date creation;

String md5Hash;

/**
* {@link Key} 生成
* @param blobKey
* @return {@link Key}
* @author sinmetal
*/

public static Key createKey(BlobKey blobKey) {
return Datastore.createKey(BlobContent.class, blobKey.getKeyString());
}

/**
* the constructor.
* @category constructor
*/

public BlobContent() {
}

/**
* the constructor.
* @param info
* @category constructor
*/

public BlobContent(BlobInfo info) {
this.key = createKey(info.getBlobKey());
this.filename = info.getFilename();
this.contentType = info.getContentType();
this.md5Hash = info.getMd5Hash();
this.creation = info.getCreation();
this.size = info.getSize();
}

...

}


UploadされたFileは、Google Cloud Console から見えることができます。

また、GAE ConsoleのBlob Viewerからも見ることできます。

ただ、Blobstoreには実際のファイルは保存されておらず、Blob Viwerで情報を見ることができるだけのようです。

GCS上のファイルを消すと、Blob ViwerからのDownloadが失敗するようになります。

逆にBlob Viwerからファイルを削除すると、GCS上のファイルも消えます。

実際に利用する場合は、Datastoreにも情報を保存しているでしょうから、データの整合性を保つために、削除などはGAEのAppを通して行うのが、真っ当でしょう。

Localで試した場合は、Development Console Datastore ViewerのGsFileInfoというKindで保存されている情報を見ることができます。


File Download

FileのDownloadはBlobstore APIを利用して行います。

UploadしたFileのBlobKey.getKeyString()をparameterとして受け取り、ResponseにそのFileを返しています。

因みにBlobKeyの中身は以下のようになっており、GCSに関係あるということが書いてあります。

encoded_gs_key:c2lucGttbm1zLXByby9xdDBLWFNQZDNJSnVlSUxPRW5tSXhn

public class UploadFileController extends Controller {

private static final String BUCKET = "sample-bucket";

private BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();

@Override
protected Navigation run() throws Exception {
switch (request.getMethod()) {
case "GET":
String key = request.getParameter("key");
if (StringUtil.isEmpty(key)) {
respondUploadUrl();
} else {
// BlobKey.getKeyString()をparameterとして受け取る。
downloadGcsFile(new BlobKey(key));
}
break;
...
}
return null;
}

...

private void downloadGcsFile(BlobKey blobKey) throws Exception {
BlobInfo blobInfo = new BlobInfoFactory().loadBlobInfo(blobKey);
if (StringUtil.isEmpty(blobInfo.getContentType()) == false) {
response.setContentType(blobInfo.getContentType());
} else {
response.setContentType("application/octet-stream");
}
response.setContentLength((int) blobInfo.getSize());
blobstoreService.serve(blobKey, response);
}
}


Github

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


Controller

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


Model

https://github.com/sinmetal/gcs-client-library-sample/blob/master/src/main/java/org/sinmetal/gcsexample/model/BlobContent.java


直接関係ないけど、BlobContent一覧を返すController

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


試したhtml

https://github.com/sinmetal/gcs-client-library-sample/blob/master/src/main/webapp/fileupload-direct.html