SiNBLOG

140文字に入らないことを、極稀に書くBlog

Slim3 BlobstoreAPIを使ってみた!

先日、Slim3 Source Code Reading でFileUploadを読みました。
その時に、横山殿がBlobstoreのサンプルも作ってきてくださいました。
Blobstoreに関しては、Slim3というよりはGAEの話になりますが、FileUploadと同じような用途で使うということで試してみたのです。
その時は、軽く試してみただけだったのですが、その後もう少し使ってみました。
また、@shin1ogawa 殿からいくつかのキーワードをいただきました。

  • FileService
  • AppEngineFile
  • Google Cloud Storage

という訳でやってみました。

IndexController


public class IndexController extends Controller {

private UploadFileService uploadFileService = new UploadFileService();

private BlobstoreService blobstoreService =
BlobstoreServiceFactory.getBlobstoreService();

@Override
public Navigation run() throws Exception {
final long MEGA_BYTE = 1024 * 1024 * 1024;
UploadOptions options =
UploadOptions.Builder.withMaxUploadSizeBytes(100 * MEGA_BYTE);
String uploadUrl =
blobstoreService.createUploadUrl("/blobstore/upload", options);
requestScope("uploadUrl", uploadUrl);

List uploadedFiles =
uploadFileService.getUploadedFileList();
requestScope("uploadedFiles", uploadedFiles);
return forward("index.jsp");
}
}

まずは、BlobstoreにUploadするためのURLを生成します。
ついでに、Uploadできるファイルの容量に制限を付けてみました。
また、BlobstoreへUpload後はCallbackURLにredirectされます。
そのCallbackURLを指定しているのが、以下の部分です。

String uploadUrl =
blobstoreService.createUploadUrl("/blobstore/upload", options);


index.jsp


form action="${f:url(uploadUrl)}" method="post" enctype="multipart/form-data">
input type="file" name="uploadFile">
input type="submit" value="Submit">
/form>
ちょっと、はてなさんにHTMLが認識されてしまったので、先頭の"<"を消しています・・・。
jsp側ではBlobstoreへ投げるためのformを作っています。
今のところどんなファイルでも投げれるのですが、jsとかで制御できるんじゃないかな?と思ってます。


UploadController


public class UploadController extends Controller {

private UploadFileService uploadFileService = new UploadFileService();

private BlobstoreService blobstoreService =
BlobstoreServiceFactory.getBlobstoreService();

@Override
public Navigation run() throws Exception {
Map blobs = blobstoreService.getUploadedBlobs(request);
BlobKey blobKey = blobs.get("uploadFile");
uploadFileService.entry(blobKey);
return redirect(basePath);
}
}

BlobstoreからのCallbackを受け取らせているControllerです。
今回は単一のファイルしかUpしていませんが、複数ファイル上げることもできるはずです。
BlobKeyを元にUploadしたファイルにアクセスします。
そのため、ここで取得したBlobKeyをDatastoreに保存してやる必要があります。
その辺りの処理はUploadFileServiceに実装しました。


UploadFileService


public class UploadFileService {

private UploadedFileMeta meta = UploadedFileMeta.get();

public Key entry(BlobKey blobKey) {
UploadedFile uploadedFile = UploadedFile.getInstance(blobKey);
uploadedFile.setKey(UploadedFile.createKey(blobKey));
return Datastore.put(uploadedFile);
}

public List getUploadedFileList() {
return Datastore.query(meta).asList();
}
}

Datastoreへ保存していますが、然程大したことはしてないです。
UploadedFileというのはModelです。
このModelにBlobKeyやUploadされたFileの情報を保存しています。


UploadedFile


@Model(schemaVersion = 1)
public class UploadedFile implements Serializable {

private static final long serialVersionUID = 1L;

@Attribute(primaryKey = true)
private Key key;

@Attribute(version = true)
private Long version;

private String blobKey;

private String filename;

private String contentType;

private Long size;

private Date creation;

private String md5Hash;

....

public String getServeURL() {
return String.format("/blobstore/serve?blobKey=%s", blobKey);
}

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

public static UploadedFile getInstance(BlobKey blobKey) {
BlobInfoFactory factory = new BlobInfoFactory();

UploadedFile model = new UploadedFile();
model.setBlobKey(blobKey.getKeyString());
BlobInfo blobInfo = factory.loadBlobInfo(blobKey);
BeanUtil.copy(blobInfo, model, new CopyOptions().exclude("blobKey"));
return model;
}

....
}

getter/setterなどはデフォルトのままなので、割愛しています。
BlobInfoにUploadしたファイルの情報が入っています。
BlobKeyさえあれば呼び出せるので、Datastoreにコピーしたものを保存する必要はないかもしれませんが、検索などをするためには必要でしょう。
取得できる情報は、名前のままです。creationは作成日付ですね。
getServeURL()はこの後出てくるBlobstoreからUploadされたファイルを読み込むためのURLを返しています。


ServeController


public class ServeController extends Controller {

private BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();

@Override
public Navigation run() throws Exception {
BlobKey blobKey = new BlobKey(requestScope("blobKey").toString());
blobstoreService.serve(blobKey, response);
return null;
}
}

BlobKeyに対応するファイルをBlobstoreから取得しています。
BlobstoreService.serve()でreponseに直接書き込んでくれます。
この時、ファイル名などを指定できず、zipなんかはserve.zipになってしまいました。
まぁ、Webアプリが扱うファイルを保存する場所ですので、そんなものなのかな?と思い、それ以上突っ込みませんでした。
また、読み込んだ時に、Outgoing Bandwidthが課金されます
これは1GB/dayが無料枠としてありますが、アクセスが多いとごりごり消費されていきそうです。
Blobstoreから読み出すので、Memcacheなどもききません。
100MBぐらい上げれるファイルアップローダなんか作ろう日には、クラウド破産へまっしぐらになりそうです!w


以上で実装は終わりです。
読み出し時にOutgoing Bandwidthがかかるので、ちょっと使いどころが難しいかなぁと感じた方も多いのではないかと思います。
Blobstore Stored Dataは5GB/1dayなので、結構いけますが、ユーザに画像を上げて貰って表示した場合、Outgoing Bandwidthが重くのしかかります。
また、Upload時にBlobstoreからredirectされるため、redirectの回数が増えがちです。
これらを解決するのが、FileServiceAppEngineFileImageServiceです。
今回のエントリーで、その辺りも書こうと思っていたのですが、長くなってしまったので、分けようと思います。

今回のソースコード
IndexController
Google Code Archive - Long-term storage for Google Code Project Hosting.
UploadController
Google Code Archive - Long-term storage for Google Code Project Hosting.
ServeController
Google Code Archive - Long-term storage for Google Code Project Hosting.
UploadedFileModel
Google Code Archive - Long-term storage for Google Code Project Hosting.
UploadFileService
Google Code Archive - Long-term storage for Google Code Project Hosting.
index.jsp
Google Code Archive - Long-term storage for Google Code Project Hosting.