simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

PlayFab:golangサーバーからCDNのFileContentにファイルをアップロード

概要

やりたいことは半年前から変わらず、キューブの世界のデータをバイナリキャッシュサーバーに置き
PlayFab ログインユーザーが Unity クライアントから CDN 経由で最新の世界データを引いてくることです。
できれば圧縮したファイルをユーザーに届けたい(いや必須で)

現在の進捗は次の記事の通り
golang 自作バイナリキャッシュサーバーが S3 に定期バックアップを保存している状況

simplestar-tech.hatenablog.com

調査結果とアイディア

公式ブログによれば?
blog.playfab.com

次の GetContentUploadUrl api をオブジェクトキーとシークレット情報で実行すると url が得られるのでこれにファイルアップロードリクエストを投げれば良いらしい
api.playfab.com

アイディアはここから
であるならば CloudScript 側でシークレットキーを使って(いらないかもだけど)アップロード url を作りこれを golang サーバーにリクエストボディで伝える
その url に golang から圧縮したファイルアップロードをしてもらう感じ いけそうですよね?
やってみます。

アップロード url を CloudScript から作ってバイナリキャッシュサーバーに渡す

CloudScript の説明を読むと、次のサーバーAPIフルアクセスになってて、クライアントから切り離されているからゲームコード書いていいよとある
api.playfab.com

残念ながら GetContentUploadUrl はこの Server リストに載っていなくて、次のように CloudScript からも http リクエストを作らなければならなかった

CloudScript の実装(動作確認済み)

handlers.updateContentFileCubedata = function (args, context) {
    // get upload url
    var headers = {
        "X-SecretKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    };
    var body = {
        Key: "hoge.zip",
        ContentType: "application/zip"
    };
    var url = "https://XXXXX.playfabapi.com/Admin/GetContentUploadUrl";
    var content = JSON.stringify(body);
    var httpMethod = "post";
    var contentType = "application/json";
    var response = http.request(url, httpMethod, content, contentType, headers);
    response = JSON.parse( response );

    // request zip upload
    headers = {
        "Authorization": "Basic dXNlcjpwYXNzd29yZA=="
    };
    body = {
        dataUrl: response.data.URL
    };
    url = "https://xxx.your.domain.net/upload";
    content = JSON.stringify(body);
    httpMethod = "post";
    contentType = "application/json";
    response = http.request(url, httpMethod, content, contentType, headers);
    return { responseContent: response };
};

※Authorization の値は user:password の値なので、本番では使われていません

golang で zip アーカイブ

これでいけました。(イケてません)

// data をアーカイブ
func compress(data []byte) (resData *bytes.Buffer, err error) {
	b := new(bytes.Buffer)
	zipWriter := zip.NewWriter(b)
	defer zipWriter.Close()

	writer, err := zipWriter.Create("filename")
	if err != nil {
		log.Fatal("zip input file create error.", err)
		return nil, err
	}
	writer.Write(data)

	return b, nil
}

golang で 圧縮と解凍

byte 配列を圧縮して byte 配列にしたいなら gZip ですよ。
zip はアーカイバなのでファイル名が中に必要 でこれは不要と

実装例はこちらを見つけました。
gist.github.com

テストのため byte 配列をファイル保存して 内容をチェックしてみます。

参考は昔の自分の記事
simplestar-tech.hatenablog.com

関数化しておくとこんな感じ

// data書き出し
func writeFileData(data []byte, filepath string) (err error) {
	file, err := os.Create(filepath)
	if err != nil {
		return
	}
	defer file.Close()
	file.Write(data)
	return
}

次のテストコードで圧縮前と圧縮後、解凍後で全部問題ないデータになっていることを確認できました。

		resData, err := gZipData(cubedata)
		if err != nil {
			log.Fatal("gZipData error.", err)
		}

		resdata2, err := gUnzipData(resData)
		if err != nil {
			log.Fatal("gUnzipData error.", err)
		}

		writeFileData(cubedata, "cubedata.bin")
		writeFileData(resdata2, "resData.gz")
		writeFileData(resdata2, "unzip.bin")
		

バイト配列を url にファイルとしてアップロード

ローカルにファイルを作らずにアップロードできるんじゃないかなと思っていますが、どうでしょう?

できました。
実装は次の通り(はじめてのことなのでいろいろ間違えて疎通確認取るまで大変でした!)

		// make gz data
		compressedData, err := gZipData(cubedata)
		if err != nil {
			log.Fatal("gZipData error.", err)
		}
		body := new(bytes.Buffer)
		body.Write(compressedData)

		// create request
		request, err := http.NewRequest("PUT", requestJson.DataUrl, body)
		if err != nil {
			log.Fatal(err)
		}
		request.Header.Set("Content-Type", "application/gzip")

		// send request
		client := &http.Client{}
		response, err := client.Do(request)
		if err != nil {
			log.Fatal(err)
		}
		defer response.Body.Close()

		// check result
		result, err := ioutil.ReadAll(response.Body)
		if err != nil {
			log.Fatal("ioutil.ReadAll(response.Body)", err)
		}
		resultMessage := string(result)
		if resultMessage != "" {
			log.Fatal("PlayFab upload error.", resultMessage)
		}

これで全世界何万人のユーザーが来ても、問題なくファイルを配信する機能が確認できました。

f:id:simplestar_tech:20191110182604p:plain
CDN にファイルアップロードされた

Unity からファイルをダウンロードする方法は Qiita に昔記事を書いていたので
こちら
qiita.com
クライアント実装時に参照しようと思います。

まとめ

半年間調査を続けた大目標が達成されました!
キューブの世界のデータを考え得る限り実装が楽で高速なバイナリキャッシュサーバーに置くことができ
PlayFab ログインユーザーだけが Unity クライアントから CDN 経由で定期的に最新のキャッシュサーバーのデータで更新される世界データを引いてこれること
圧縮したファイルをユーザーに届けられている

ここから Unity クライアントに作業を戻していきます。