前置き部分はPart1を参照してください。
Part1と違う点
Part1のやり方だと、GAE経由でGCSとFileをやりとりしているので、1requestの60sec制限に縛られ、大きなファイルをやりとりすることができません。
Part2では、そこを改善します。
Part1とPart2で以下のように流れが変わります。
特に変わるのはFileUploadです。
Part1のUploadの流れ
以下の順番でFileが直接渡されている。
- Client
- GAE
- GCS
Part2のUploadの流れ
- GAEがFileUpload用のURLを発行する。
- ClientはFileUpload用のURLに対して、FileをUploadする。このURLはGAE Frontendを経由しないので、60secにもかからない。
- FileUploadが完了すると、1で発行されたURLで動いているServerが、指定されたPathに対して自動で、UploadされたFile情報Parameterに含めてRequestを送ってくる。
- 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
@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
Model
直接関係ないけど、BlobContent一覧を返すController
試したhtml