2013年4月19日金曜日

25.Google App Engine/Java 1.7.7でiPhoneにPush通知してみた


◯Google App Engineは1.7.7から外向きのSocke通信をサポート


Google App Engine 1.7.7から外向きのSocket通信がサポートされました。
それに伴い、
AppEngineから直接APNSサーバーと通信してiOSにPush通知を送信することもできるようになりました。


これまではAppEngineのアプリからAPNSサーバーにPush通知を送信するためには、
別の環境(EC2やElastic Beanstalkなど)に踏み台にするWebアプリを配置して、
以下のような構成にする必要がありました。

AppEngine → [httpsで通信] → 踏み台のWebアプリ →[Socket通信]→ APNSサーバー

これはAppEngineから外部サーバーに対する接続が制限されていたためで、
AppEngineから外部のサーバーに対して許可されているhttpやhttpsで通信して、
APNSサーバーとの通信はその先のWebアプリ(踏み台)から行なっていたわけです。


それが今回のアップデートにより、

AppEngine →[Socket通信]→ APNSサーバー

と、AppEngineから直接APNSサーバーと通信してPush通知を送信できるようになりました。



◯サンプルコード

public class SendPushController extends Controller {
    /**
     * Logger
     */
    private static Logger log = Logger.getLogger(SendPushController.class
        .getName());

    @Override
    public Navigation run() throws Exception {

        // 記録しておいたDeviceTokenを取得する
        String deviceToken = "**************************************************";

        // Appleのサーバーと通信するための証明書ファイルのPathを取得する
        URL url = this.getClass().getResource("/test-dev.p12");
        String certFilePath = url.getFile();
        // 証明書のパスワードを取得する
        String certPassword = "********";

        ApnsServiceBuilder serviceBuilder =
            APNS.newService().withCert(certFilePath, certPassword)
            // 注:↓を指定しないと内部でThreadを生成しようとしてコケる
                .withNoErrorDetection();

        // 接続先としてSandbox(開発用環境)を指定する場合
        serviceBuilder.withSandboxDestination();
        // 接続先として本番用環境を指定する場合
        // serviceBuilder.withProductionDestination();

        ApnsService apnsService = null;
        try {
            apnsService = serviceBuilder.build();
            PayloadBuilder payloadBuilder = APNS.newPayload();

            // alert文字列
            payloadBuilder
                .alertBody("Google App Engine/Java 1.7.7でiPhoneにPush通知してみる!");
            // 音の指定
            // payloadBuilder.sound("hoge.wav");
            // バッジの指定(アイコン右上の赤丸内の数字)
            // payloadBuilder.badge(4);

            log.info("start to send push.");

            // Push通知の送信(複数のデバイストークンをまとめて送信も可能)
            apnsService.push(deviceToken, payloadBuilder.build());

            response.getWriter().write("success to send push.");

        } catch (Exception e) {
            response.getWriter().write("failed to send push.");
            log.log(Level.SEVERE, "failed to send push.", e);

        } finally {
            // Connectionを解放
            if (apnsService != null) {
                apnsService.stop();
            }
        }
        return null;
    }
}


APNSサーバーに対してPush通知を送信するためのJava用ライブラリとして「java-apns」を使っています。

iPhoneアプリからサーバーにデバイストークンを登録する機能は別途必要ですが、
送信部分はこれだけです。

App Engine側のアプリと「踏み台のWebアプリ」とのやりとりの記述が無くなり、
「踏み台のWebアプリ」のサーバーを管理する必要もなくなるので、随分楽になるでしょう。

ローカルの開発環境からも特に問題なくPush通知を送信することができました。




◯Production環境では課金を有効にする必要がある


The Socket API will be enabled for this application once billing has been enabled in the admin console.

Production環境では課金を有効にしていない場合に上記のエラーが発生します。


Ver.1.7.7から「最低でも週$2.10課金される」という制度が廃止されたので、このタイミングなら課金設定が必須とされてもそんなに痛くはないかな?
もしかしてタイミング合わせたのでしょうか。




◯送信結果

左の画像が「アラート」の送信結果、
右の画像が「バッジ」の送信結果です。
バッジは数字の「4」をサーバーから送ったのが対象のアプリ(FortuneAstro)のアイコンの右上に表示されています。




* テストに使ったiPhoneアプリの作成とPush通知の受信確認はもーりさんを通じてあゆたのSさんにお願いしました。
お二人ともありがとうございましたヽ(´▽`)ノ



◯参考リンク

iPhoneアプリにPush通知機能を実装する方法のまとめ - もとまか日記
iPhoneプッシュ通知まとめ - webネタ
notnoop/java-apns · GitHub

◯Pythonで試した方のブログ

Google App EngineからiOSアプリへPush通知が送れるようになりました - laiso



2013年4月12日金曜日

24.appengine ja night #24 Google Cloud Endpoints and BigQuery



Ryo Yamasaki(@vierjp)です。

4/10の「appengine ja night #24」で登壇させていただき、
「Google Cloud Endpoints」と「BigQueryの新機能」について発表したので、
補足も含めて記事にしておこうと思います。

こちらが発表資料です。
appengine ja night #24 Google Cloud Endpoints and BigQuery


◯Google Cloud Endpoints

・概要
Endpointsを使うことでできる事について簡単に説明しています。


・基本的な実装手順
これまでに書いたエントリーと重なるところも多いですが、
新しい内容としては「エラー処理の方法」、「JSでAngularJSと組み合わせて利用する方法」について書いています。


・OAuth2に関する説明と実装方法
これまでこのブログで触れて来なかった、
「Cloud EndpointsでOAuth2を利用する方法」について解説しています。


・デモ
簡単なあしあと帳のようなアプリで、
サーバー側のAPは「一覧取得「メッセージ投稿」の二種類のAPIを作成しています。
それらをJSとAndroidのクライアント側から実行します。
メッセージ投稿APIは要認証なので、クライアント側でOAuth2の認証もしています。


・質問:「JavaScript見た感じjQueryと比べてあまり工数変わらない感じがするけど?」
この質問、会場で上手に答えられなかったのでまとめておきます。

たしかにJS側の実装だけ見るとjQueryでAjaxした場合と記述量はあまり変わらない気もしますが、
佐藤さんや小川さんが答えてくれた事も含めて、以下のように考えます。
 ・クライアントがJSだけでもサーバー側の通信周りの処理が不要になる分「サーバー側の実装コスト」が減る
 ・クライアントとしてJS以外にAndroidやiOSも検討しているなら総合的な工数はさらに減る
 ・GoogleアカウントでのOAuth2認証を使うのが簡単(クライアント・サーバー側両方)
 ・JS側で他のGoogle APIも使うなら、使い方が同じなので相性が良い。
 ・API Explorerを利用可能 (自動で「Google APIs Discovery Service」に準拠する)



◯「BigQueryの新機能」

当初はCloud Endpointsだけの予定でしたが、
前回の記事「BigQueryの新機能 (2013/03/15)」の後に
Google佐藤さんより依頼があったので、追加でデモを行いました。

・デモ
JOIN EACH(Big JOIN)とGROUP EACH BYのデモを行いました。
デモでお見せした内容は「BigQueryの新機能 (2013/03/15)」と全く同じですので、
こちらのエントリーをご覧いただければと思います。


・JOIN EACH(Big JOIN)とGROUP EACH BYで何が変わるか
これまでBigQueryを使う上で、JOINのサイズ制限が使い勝手の上で結構苦労が多く、
この制限を使う回避するために割く時間はBigQueryを扱う中で結構な時間を占めていました。
また、長期的に見た場合にデータの増加や新しい集計パターンに対処できなくなる懸念もありました。
新しい機能によってその手間や懸念が無くなる、というのが良いところです。


・質問:JOINサイズの制限
「ドキュメント上は制限がなくなったとあるが、実際にはあまりに大きいテーブルをJOINした場合にエラーになる事がある」という話に対して
「では限界はどのくらいなのか」という質問をいただきましたが、
残念ながらこちらは把握できていません。

限界値を探るために数億件レベルのサイズのテーブルをJOINしてクエリを投げまくると、
さすがに課金が発生してしまうので個人ではちょっと痛いのです。。
(企業なら気にしないレベルの金額だと思いますが)


・質問:「常にEACHをつけてしまえばOK?」


1.「基本はEACHを付ける」
2.「EACHを付けた場合に速度に問題があるならSmall JOINを検討する」
という手順が良いと思います。


ドキュメントに「Small JOINの方が速い」と明記されており、
前回のエントリーでも実際にクエリを投げて検証したように、実際にSmall Joinの方が速いようです。
(前回の検証では、あるクエリについてBig JOINが14.0sでSmall JOINが4.9s)

そのため、「工数」と「求める速度」のトレードオフで決めるのが良いとおもいます。

「夜中にバッチ処理から実行する」等でクエリの実行速度をそこまで求めない場合には
BIG JOINにしてしまえばクエリ作成が楽ですし、
運用期間を重ねるに連れてテーブルサイズが増大しSmall JOINでは動かなくなる、
という心配も減ります。
よってこの場合は「常にEACHをつける」で良いと思います。

逆に「ユーザーに見せるためにWebアプリからAPI経由でBigQueryを実行して結果を表示する」ような、
速度が要求される場合かつJOINするテーブルについて今後のサイズ増大の懸念が無いなら
Small JOINを検討する、のが良いでしょう。



◯最後に

以上で"自身によるまとめ"とさせていただきたいと思います。

まともに登壇させていただくのは今回が初めてでしたが、良い経験になりました。
聞いていただいた皆様ありがとうございました。m(__)m

あと、発表の練習場所とアドバイスをくれたもーりさん、ありがとうございましたヽ(´▽`)ノ


他の方の発表については下記のブログでまとめられているので、こちらをご覧ください。
appengine ja night #24 #ajn24 に行ってきました - @thorikiriのてょりっき
(この方はいつも色々な勉強会を鬼のような速さでまとめていて驚きます)