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


前置き部分は[Part1](http://qiita.com/sinmetal/items/6ed7ec012af030f3e8fa)を参照してください。

# 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になります。

```Java
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に保存しています。

```Java
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

```Java: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
```

```Java
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