AWS S3を使ってUnity WebGLの開発で起こったこと。

まえふり

UnityでWebGL向けに開発し、ネットに公開するには何らかの方法でサーバーが必要になります。
本番をどのように対応するかはさておき、簡単かつ無料でWebGLを公開したいのであればAWSの1年無料枠でS3を使って動作確認しました。

そのときの内容とトラブったことのまとめになります。
個人的に、Unity使ってWebGL向けの開発をしている人で、WebGLのデータを置くサーバーを検討している人やどうするのか分からない人などにこの記事を参考にしていただけると幸いです。

環境

  • Unity 2017.2.0f3

    • WebGL
  • AWS(無料枠)
    • EC2, S3

Unity WebGLでの開発について

クロスドメインとCORS

Unity内でサードパーティなどの外部連携の際、APIをWWWなどで処理させると思います。

WWWの処理は、Unityがビルドするプラットフォームに合わせて対応しているため、iOS/Androidだったらネイティブの通信処理に合わせた対応が行われており、WebGLはJavascriptのXMLHttpRequestで対応している模様。
WebGLでサードパーティなどの外部連携を行うと必ず、クロスドメイン問題が発生します。

今回のクロスドメイン問題の対応は、開発しているゲーム専用のAPIも開発していたので、そこを経由して外部連携することにしました。

 
   

 

検証: S3でCORSの設定について

S3には、外部からのアクセスを考慮するためCORSの設定があります。これは外部向けに設定するケースっぽいから、今回はその逆なので関係ナッシング。

PHPで説明するところの

header('Content-type: application/json');
header("Access-Control-Allow-Origin: *");

を対応しないと解決しないので、実は記事書いている時に気づいたものの、、ふぅ笑

だいたいの人がサーバーを用意することが多いよねー。

関連記事:
* WebGL WWW security (Cross-Origin Resource Sharing) help please
* Unity 5 の WebGL で外部からテクスチャを与える方法について調べてみた

HTTPS(SSL)

今回、プライベートでの開発でなるべくお金をかけて開発したくなかったため、AWSの1年間無料枠を前提に開発を進めていました。EC2にはパブリックDNS、S3にはリンクが用意されるので、ドメインを用意することなく動作確認までできちゃいます。

スクリーンショット 2018-01-21 13.45.08.png

スクリーンショット 2018-01-21 13.49.37.png

ただし、S3はhttpsでEC2はhttpなので、S3はAWSが提供しているSDKを使って、データのダウンロードをすることがありますが、直リンクで扱うことも全然あるので、セキュリティ関連で問題になったりならなかったり。

今回、EC2のパブリックDNSによって、「Mixed Content: The page at …」というエラーに遭遇しました。

Mixed Content: The page at …

実際には↓こんなエラーです。

スクリーンショット 2018-01-21 14.22.05.png

Wordpressでよく遭遇する問題としてよく見かけますが、今回の場合だと、サードパーティで扱われるAPI群が全てHttpsで用意されていたため、Httpで統一する方法が取れず、EC2のパブリックDNSでは限界がきた感じ。

対応方法

Route53でドメインを購入し、ACMでSSL証明書を発行してRoute53とロードバランサーと連携する方法になるかと思います。

まとめ

ここまでで大きなトピック2つをご紹介しました。
全て把握すると開発中でなくても情報さえ把握しておけば、今回のようなことは設計段階で解決する内容だと思います。
知っておけば設計段階で問題解決することは、どんな分野でも一緒ってことですねー。

特に経験者には敵いませんねぇ、、ホント笑
 

続きを読む

Ubuntu on AWSに軽量デスクトップLXDEとxrdpを使ってリモート接続してみた | Developers.IO

Ubuntu on AWSに軽量デスクトップLXDEとxrdpを使ってリモート接続してみた | Developers.IO. 18 users テクノロジー 記事元: クラスメソッド発のAWS/iOS/Android技術者必読メディア | Developers.IO … 続きを読む

【2018年】AWS全サービスまとめ その1(コンピューティング、ストレージ、データベース、移行 …

【2018年】AWS全サービスまとめ その1(コンピューティング、ストレージ、データベース、移行、ネットワーキング & コンテンツ配信)|Developers.IO. 10 users テクノロジー 記事元: クラスメソッド発のAWS/iOS/Android技術者必読メディア | Developers.IO … 続きを読む

MonacaからPhoneGap Builderへ移行

なぜ切り替えるに至ったか

Monacaを使っていたものの、公開版を作成するためには有料プログラムにしなくてはならない。
使い始めてた時からわかっていたもの、便利だったのでずーっと問題を放置してきた。
しかし、いよいよ自分が作成したアプリが完成に近づいてきてビルド回数に制約があることも踏まえ、切り替えるのも自分のスキルアップにつながるかなと思ってやってみた。

まとめると

・Monacaありがとう
・riot,AWS,phinaのコンビネーションでもMonacaもPhoneGap Buildもできました
・でも、はまるところもあります

以下、自分の備忘録に近いところもあるけど、吐き出しておく。

Monacaはアプリ開発を加速します

そもそも切り替えるかどうか悩んだのは、やっぱり便利だから。
開発で悩みを減らす、そのことをビジネスにしているのだからそれはそれでいいのだと思う。
ホントありがたいサービスです。
これがなかったら、私はここまでこれませんでした。
費用面がクリアできるなら、Monacaで開発したほうがいいです。
私のように3連休まるまるこの問題解決に使うことなく開発に専念できます。

では切り替えますか…何に?

Monacaはcordovaをベースにしてることは知ってます。
でも、みなさんphonegapに行きつくわけですね。
そこからして無知な私は調べるしかなくて。
調べてた途中でtelerik platformなるサービスも見つけたが、2018年5月でサービスやめるっていうことで、マイグレーションガイドがあってこれが非常にありがたかった。これみれば、イマドキこういうサービスがあるんだと理解できた。やっぱりcordovaかphonegapの2択なんだとわかったので。

クリップボード01.jpg

その2択を調べてみた1ところ…やっぱりよくわからないけど、PhoneGap Buildを使わないと最終的なビルドが面倒そうに見えたので、phonegapにしてみることにした。

Phonegap Builderに切り替えようとしたけど

というわけで、また調べると過去にアプローチした方のを参考にすることにした2

参考にしたいものの、ギャップが大きすぎた。
ここで、今回のアプリと自分がこれまでどうやって開発してきたかを簡単に説明すると…
・riot.js+phina.js+aws(DynamoDB,Coginto Userpool)で作られたゲーム
 ・一部をのぞきcdn参照。
・開発は自分の自宅PC(windows10)
・IDE?そんなもん知らんなぁ(テキストエディタで編集してIISサーバ経由で動作確認)
 ※IISサーバ経由なのはriotが求めるから(XSSが…というのでそれはそれで仕方ない)。
 ※ほんとはpercel出たあたりに期待したのだけど、phinaとの相性3で断念。

そもそもMonacaで動くのか?

つまり、そもそもMonacaですらまともに動くかどうかわからない状態。
というわけで、まずはMonacaに移植することにした。

案の定動かないわけです。
エラーログもはかずに、背景色のみ。
CDNなところはすべて取っ払い、必要なコンポーネントを全部追加して、パスも通しても動かず。

理屈的にはコンポーネントを追加すれば動くはずなんだけど…
今回は必要なライブラリはダウンロードして取り込むことにしました。

回り道したようにも思えましたが、結果として「Androidアプリ設定」などその後の作業がイメージしやすかったです。

動いちゃうと案外満足しちゃうもので、やっぱこのまま…とも一瞬思いました。

いよいよMonacaから旅立つ

でも、ビルドに制約があるので…改めて手順にしたがってphonegapから。
あらかじめnode.js,npmは使える環境にはしていたので、そこは割愛。

1.Phonegapのインストール

npm install -g phonegap

まぁ普通。npm install -g phonegapしたかったので、管理者権限でやったことぐらい?

2.スマホにデバッグツールをインストール

インストール自体は悩まないのだけど…後述しますが、ネットワーク連係するアプリの場合あまり使えないのです…

3.プロジェクトを作成

phonegap create <AppName> --template blank

ここも特にひねりはない。参考例もblankにしているが、下手にhelloworld入っているよりわかりやすいんじゃないかなと思いました。

4.開発

今回はあらかじめ開発してあるので、www以下に開発した成果物を放り込む。
その際に以下を残せというから残した。

<script type="text/javascript" src="cordova.js"></script>

5.モバイルでの確認

$ phonegap serve android

できるんだ!と思ってやってみたところ…できた!
ここまできたら、2.で導入したデバッカーで動作させようとしたところ…

ちーっとも接続しない。
そもそもPC→スマホって接続できてないのでは?
と思って、昔でいうLANボード2枚刺しみたいなことをしようと調べたら…
いまさら SoftAP を使用してWindowsノートマシンを無線親機にする

これこれ。ありました。この手順にしたがって、PCを無線親機化して、スマホ側で認証することでようやく接続できました。
で、画面が表示されてボタンも反応して一安心、と思ってログオン機能を試したところ動かない。
サーバ側のログをみても無反応。

何がまずいのかわからなかったので、またネットでググると
Cordova、IonicでHTTP通信できない・画像とかが読み込めないときの対処法3選
これのホワイトリストの設定ミスの項をみて、そもそもwhitelistのプラグイン入れてない!と気がづき、入れました。

今度は…と思ったら「認証できません」というあらかじめアプリで仕込んだメッセージが。
なんでなん?
と思ってサーバ側のエラーログを見たら

NetworkingError: Network Failure

このキーワードで調べるとCORSConfigurationにぶち当たるのだが、これが曲者。
S3の話はそれで解決するのかもしれないが、実際のところはサーバ側(ここでいえばphonegap serve)の問題とのこと。
(この裏どりだけが再検索しても出てこない…英語のページでnot client sideというようなキーワードがあったはず。)

つまりは

$ cordova serve android

した後、自PCで

http://localhost:3000/

して確認したら、あとは信じるしかない…と割り切って先に進んだ。

6.プラグイン

ここもまぁ普通。あまり悩むことはない。

7.設定の修正

ここはMonacaを経由したおかげで何をすべきかが分かった。
むしろここを見たほうがいい。

8.アイコンの変更

Favicon Generator
である程度作ってくれるのだが、最新のiosに対応してないので自力で作らないところがあるのがつらい。

9.PhoneGap Buildでアプリを作成する

ようやくここまできました。
PhoneGap Build

まずはadobeのIDがなかったので作りました。まぁ悩むところはないが、パスワードの制約が面倒。
そして、やっぱりgithubが必要だというので、これまで避けてきたけどgithubもアカウント作成。

githubを使うのも初めてだったのでプログラムソースのアップロードに四苦八苦。
結局webから登録可能だと知ったのが、調べ始めてから3時間後。
無題.jpg

ネットを見ると、gitから登録する手順はたくさん載っているが、こんなシンプルな方法がなかなか出てない。当たり前だからか?

そこに至るまでにgithubのデスクトップ版入れたりしたが、どうにも使いづらいうえに案外ネットにも情報がない。
結局git for windowsを入れたほうがシンプルで分かりやすい。

10.PhoneGap Buildに鍵を登録する

これも書いてあるとおりなのだが、javaも必要なのか…てかkeytoolだけが必要なんだけど

keytool -genkey -v -keystore [keystore_name].keystore -alias [alias_name] -keyalg RSA -keysize 2048 -validity 10000

このときのエイリアス名、のちに使うので慎重に。

無題.jpg

「アカウントを編集」-「署名キー」-「キーを追加」
このときにタイトルはどうでもいいけど、エイリアスはkeytoolで作成したときに -alias [alias_name]で指定した値と一致しないとエラーになる。(←エラーとなったからわかる。)

果たして動くのか?

そうなんです、ここまで作業したものの最終的に実機確認ができてないので不安でしたが…
動きましたー
・aws cognito userpoolを使った認証
・aws DynamoDBを使ったデータ取得、更新
・riot.js+routerを使ったフレームワーク
・phina.jsの動作
すべて問題なし。

ただ反応が鈍いかな…PC(corei7)とスマホ(arrowsM02)を比較しちゃダメなのかな。

続きを読む

AWS Mobile Hub による react-native アプリ開発(Cloud API REST呼び出し)

AWS mobile-hub が react-native に対応したとのことなので、マニュアル (Developer Guide) を元にサンプルアプリを作成・試してみる。

今回は Dynamodb にてテーブルを作成し、CRUDアプリケーションをサンプルに沿って作成してみた。

はじめに

前回の記事から Dynamodb に登録・参照する簡単なサンプルプログラムを実行してみる。

Backend (Mobile Hub) の設定

テーブルの作成

コマンドから下記を実行

awsmobile database enable --prompt

マニュアル通りの Notes テーブルを作成

Welcome to NoSQL database wizard
You will be asked a series of questions to help determine how to best construct your NoSQL database table.

? Should the data of this table be open or restricted by user? Open
? Table name Notes

You can now add columns to the table.

? What would you like to name this column NoteId
? Choose the data type string
? Would you like to add another column Yes
? What would you like to name this column NoteTitle
? Choose the data type string
? Would you like to add another column Yes
? What would you like to name this column NoteContent
? Choose the data type string
? Would you like to add another column No


Before you create the database, you must specify how items in your table are uniquely organized. This is done by specifying a Primary key. The primary key uniquely identifies each item in the table, so that no two items can have the same key.
This could be and individual column or a combination that has "primary key" and a "sort key".
To learn more about primary key:
http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.PrimaryKey


? Select primary key NoteId
? Select sort key (No Sort Key)

You can optionally add global secondary indexes for this table. These are useful when running queries defined by a different column than the primary key.
To learn more about indexes:
http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.SecondaryIndexes

? Add index No
Table Notes saved

CRUD API の作成

下記コマンドにて cloud-api を作成

awsmobile cloud-api enable --prompt

先ほど作成した Notes テーブルから生成を選択。ログイン必須を選択した。

This feature will create an API using Amazon API Gateway and AWS Lambda. You can optionally have the lambda function perform CRUD operations against your Amazon DynamoDB table.


? Select from one of the choices below. Create CRUD API for an existing Amazon DynamoDB table
? Select Amazon DynamoDB table to connect to a CRUD API Notes
? Restrict API access to signed-in users Yes
Adding lambda function code on: 
/Users/masanao/dev/awsmobile/awsmobilejs/backend/cloud-api/Notes/
...
Path to be used on API for get and remove an object should be like:
/Notes/object/:NoteId

Path to be used on API for list objects on get method should be like:
/Notes/:NoteId

JSON to be used as data on put request should be like:
{
  "NoteTitle": "INSERT VALUE HERE",
  "NoteContent": "INSERT VALUE HERE",
  "NoteId": "INSERT VALUE HERE"
}
To test the api from the command line (after awsmobile push) use this commands
awsmobile cloud-api invoke NotesCRUD <method> <path> [init]
Api NotesCRUD saved

設定の反映

下記コマンドにて AWS のモジュールを作成

awsmobile push

作成に少し時間がかかるので、完了するのを待つ。

building backend
   building cloud-api
      zipping Notes
   generating backend project content
   backend project content generation successful
done, build artifacts are saved at: 
/Users/masanao/dev/awsmobile/awsmobilejs/.awsmobile/backend-build

preparing for backend project update: awsmobile
   uploading Notes-20180108140947.zip
   upload Successful  Notes-20180108140947.zip
done

updating backend project: awsmobile
awsmobile update api call returned with no error
waiting for the formation of cloud-api to complete
cloud-api update finished with status code: CREATE_COMPLETE

Successfully updated the backend awsmobile project: awsmobile

retrieving the latest backend awsmobile project information
awsmobile project's details logged at: awsmobilejs/#current-backend-info/backend-details.json
awsmobile project's access information logged at: awsmobilejs/#current-backend-info/aws-exports.js
awsmobile project's access information copied to: aws-exports.js
awsmobile project's specifications logged at: awsmobilejs/#current-backend-info/mobile-hub-project.yml
contents in #current-backend-info/ is synchronized with the latest in the aws cloud

フロントエンドの構築

App.js を編集・CRUDのコードを書く。

モジュール import

import { API } from 'aws-amplify-react-native';

CRUDコードの記述

サンプルコードをそのまま貼り付けすることとした。

state の追加

state = {
  apiResponse: null,
  noteId: ''
     };

  handleChangeNoteId = (event) => {
        this.setState({noteId: event});
}

saveNote 関数の追加

  // Create a new Note according to the columns we defined earlier
    async saveNote() {
      let newNote = {
        body: {
          "NoteTitle": "My first note!",
          "NoteContent": "This is so cool!",
          "NoteId": this.state.noteId
        }
      }
      const path = "/Notes";

      // Use the API module to save the note to the database
      try {
        const apiResponse = await API.put("NotesCRUD", path, newNote)
        console.log("response from saving note: " + apiResponse);
        this.setState({apiResponse});
      } catch (e) {
        console.log(e);
      }
    }

getNote 関数の追加

  // noteId is the primary key of the particular record you want to fetch
      async getNote() {
        const path = "/Notes/object/" + this.state.noteId;
        try {
          const apiResponse = await API.get("NotesCRUD", path);
          console.log("response from getting note: " + apiResponse);
          this.setState({apiResponse});
        } catch (e) {
          console.log(e);
        }
      }

deleteNote 関数の追加

  // noteId is the NoteId of the particular record you want to delete
      async deleteNote() {
        const path = "/Notes/object/" + this.state.noteId;
        try {
          const apiResponse = await API.del("NotesCRUD", path);
          console.log("response from deleteing note: " + apiResponse);
          this.setState({apiResponse});
        } catch (e) {
          console.log(e);
        }
      }

rennder 関数の変更(Viewの変更)

<View style={styles.container}>
        <Text>Response: {this.state.apiResponse && JSON.stringify(this.state.apiResponse)}</Text>
        <Button title="Save Note" onPress={this.saveNote.bind(this)} />
        <Button title="Get Note" onPress={this.getNote.bind(this)} />
        <Button title="Delete Note" onPress={this.deleteNote.bind(this)} />
        <TextInput style={styles.textInput} autoCapitalize='none' onChangeText={this.handleChangeNoteId}/>
</View>

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  textInput: {
      margin: 15,
      height: 30,
      width: 200,
      borderWidth: 1,
      color: 'green',
      fontSize: 20,
      backgroundColor: 'black'
   }
});

作成したApp.js ソース

react-native のサンプルソースを元にしたため、一部不要なソースコードがありますがそのままにしています。

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import Amplify from 'aws-amplify-react-native';
import { API } from 'aws-amplify-react-native';
import { withAuthenticator } from 'aws-amplify-react-native';
import aws_exports from './aws-exports';

Amplify.configure(aws_exports);

import React, { Component } from 'react';
import {
  Platform,
  StyleSheet,
  Text,
  Button,
  TextInput,
  View
} from 'react-native';

const instructions = Platform.select({
  ios: 'Press Cmd+R to reload,n' +
    'Cmd+D or shake for dev menu',
  android: 'Double tap R on your keyboard to reload,n' +
    'Shake or press menu button for dev menu',
});

class App extends Component<{}> {
  constructor(props) {
        super(props);
    this.state = {
      apiResponse: null,
      noteId: ''
    };
  }

  handleChangeNoteId = (event) => {
        this.setState({noteId: event});
  }

  // Create a new Note according to the columns we defined earlier
    async saveNote() {
      let newNote = {
        body: {
          "NoteTitle": "My first note!",
          "NoteContent": "This is so cool!",
          "NoteId": this.state.noteId
        }
      }
      const path = "/Notes";

      // Use the API module to save the note to the database
      try {
        const apiResponse = await API.put("NotesCRUD", path, newNote)
        console.log("response from saving note: " + apiResponse);
        this.setState({apiResponse});
      } catch (e) {
        console.log(e);
      }
    }
     // noteId is the primary key of the particular record you want to fetch
      async getNote() {
        const path = "/Notes/object/" + this.state.noteId;
        try {
          const apiResponse = await API.get("NotesCRUD", path);
          console.log("response from getting note: " + apiResponse);
          this.setState({apiResponse});
        } catch (e) {
          console.log(e);
        }
      }

     // noteId is the NoteId of the particular record you want to delete
      async deleteNote() {
        const path = "/Notes/object/" + this.state.noteId;
        try {
          const apiResponse = await API.del("NotesCRUD", path);
          console.log("response from deleteing note: " + apiResponse);
          this.setState({apiResponse});
        } catch (e) {
          console.log(e);
        }
      }

  render() {
    return (
<View style={styles.container}>
        <Text>Response: {this.state.apiResponse && JSON.stringify(this.state.apiResponse)}</Text>
        <Button title="Save Note" onPress={this.saveNote.bind(this)} />
        <Button title="Get Note" onPress={this.getNote.bind(this)} />
        <Button title="Delete Note" onPress={this.deleteNote.bind(this)} />
        <TextInput style={styles.textInput} autoCapitalize='none' onChangeText={this.handleChangeNoteId}/>
</View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
  textInput: {
      margin: 15,
      height: 30,
      width: 200,
      borderWidth: 1,
      color: 'green',
      fontSize: 20,
      backgroundColor: 'black'
   }
});
export default withAuthenticator(App);

実行

react-native run-ios

スクリーンショット 2018-01-08 18.51.18.png

テキストボックス内に NoteID を入力して “Save Note” を押すと、保存されます。

内容はソースコードに書いてあるように固定文字が登録されます。
あくまでサンプルソースということで・・・。

AWS 確認

Mobile Hub の Cloud Logic には下記のように登録されています。

スクリーンショット 2018-01-08 19.01.38.png

Lambda も確認すると、NotesCRUD-xxxx という名前で登録されているのが確認できます。

スクリーンショット 2018-01-08 19.02.59.png

DynamoDB も同様にテーブルが作成され、データも登録されているのがわかります。

スクリーンショット 2018-01-08 19.24.53.png

通常のCRUD であればほぼノンプログラミングでバックエンドロジックが生成できますね。

続きを読む