Push Notificationを使ってみる

■概要
LocalNotificationは時限式で単にメソッドを呼べば良いだけで、実装も簡単だったが、
Push Notificationてどうやるんだ?Pushって名が付くくらいだからサーバー側から何か来るんだよね。
という事で、いつも通り調べながら作りはじめてみた。

■認証
まずProvisioning PortalでPush NotificationをEnableにする必要がある。
今回は既に作成済みのAppIDsをEnableにした。Enable for Apple Push Notification serviceにチェック。
どうやら、開発用のDevelopment Push SSL Certificateと、配布用のProduction Push Certificateがあるようだ。
両方ともConfigureを押してEnableにする必要がある。
どちらも手順は同じだが、Configureを押すとダイアログみたいな画面が表示される。
一行に直訳すると、キーチェーンアクセスから認証アシスタントを使って証明書を要求してファイル保存せよ。
鍵から自分の英語名(公開鍵)を選択し、下記の通り選択して、メール送付ではなくファイル保存にする。
CertificateSigningRequest.certSigningRequestというファイルが出来上がるので、ダイアログに戻りContinueを押す。
上記で作成したファイルを選択してGenerateを押す。
provisioningファイルが出来上がるのでダウンロードして登録する。
開発用がaps_development.cer、リリース用がaps_production.cer。どちらもダブルクリックでキーチェインに登録出来る。
キーチェーンアクセスを確認してみると、Push Serviceが2つ登録されている。
最後に、Provisioning PortalのProvisioningから、DevelopmentとDistributionのProvisioningファイルも更新してインストールしておかないと、
NSCocoaErrorDomainという謎エラーになるので注意。
結構シビアなので、古いProvisioningは全て削除して、Provisioning PotalからModifyで更新し直して、日付が新しくなったものを入れ直した方が良い。

■iOSデバイス登録と、デバイストークン受信
Push Notificationを受け取る為に、iOSデバイスをAPNsに登録し、デバイストークンを受け取る必要がある。
アプリ起動時に登録すれば良いだろう。

AppDelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    // Remote Notificationを受信するためにデバイスを登録する

    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge

                                                                           | UIRemoteNotificationTypeSound

                                                                           | UIRemoteNotificationTypeAlert)];

で、デバイストークン受信時の実装。

AppDelegate.m

// デバイストークンを受信した際の処理

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {

    NSMutableString *tokenId = [[NSMutableString alloc] initWithString:[NSString stringWithFormat:@"%@",devToken]];

    [tokenId setString:[tokenId stringByReplacingOccurrencesOfString:@" " withString:@""]]; //余計な文字を消す

    [tokenId setString:[tokenId stringByReplacingOccurrencesOfString:@"<" withString:@""]];

    [tokenId setString:[tokenId stringByReplacingOccurrencesOfString:@">" withString:@""]];

    NSLog(@"deviceToken: %@", tokenId);

}

エラーも取っておくと良いだろう。3G回線がWiFiが繋がらないとエラーになる。また、WiFiの場合port5223が開いてないと繋がらない。

AppDelegate.m

- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)err{

NSLog(@"Errorinregistration:%@",err);

}


■鍵の作成
サーバーサイドをコーディングする前に、鍵(*.pem)を作成しておく必要がある。
まずはキーチェーンアクセスから、Apple Development iOS Push Servicesの下のプライベートキーを書き出す。
開発用なので、Developmentのほうを使う。ファイル名は「apns-dev-cert.p12」とする。
最初に出てくるパスワードは未入力で良いらしい。
次に出てくるパスワードは任意のパスワードを入れる必要がある。
作成されたapns-dev-cert.p12から、鍵(pem)を二つ作る。
ターミナルを起動して下記コマンドを実行する。

ターミナル

openssl pkcs12 -clcerts -nokeys -out apns-dev-cert.pem -in apns-dev-cert.p12

openssl pkcs12 -nocerts -out apns-dev-key.pem -in apns-dev-cert.p12

openssl rsa -in apns-dev-key.pem -out apns-dev-key-noenc.pem

cat apns-dev-cert.pem apns-dev-key-noenc.pem > apns-dev.pem
結果、apns-dev.pemと、apns-dev-cert.pemが出来上がる。
サーバーに置いておく必要がある。

■Notification送信と、iOSアプリでの受信

サーバー側プログラムは調査中。

アプリ側では、NSDictionaryで受信する。
必要に応じて、アラート、音声、バッジを設定する。

AppDelegate.m

#import <AudioToolbox/AudioServices.h>

// プッシュ通知を受信した際の処理
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
#if !TARGET_IPHONE_SIMULATOR
    NSLog(@"remote notification: %@",[userInfo description]);
    NSDictionary *apsInfo = [userInfo objectForKey:@"aps"];
 
    NSString *alert = [apsInfo objectForKey:@"alert"];
    NSLog(@"Received Push Alert: %@", alert);
 
    NSString *sound = [apsInfo objectForKey:@"sound"];
    NSLog(@"Received Push Sound: %@", sound);
    AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
 
    NSString *badge = [apsInfo objectForKey:@"badge"];
    NSLog(@"Received Push Badge: %@", badge);
    application.applicationIconBadgeNumber = [[apsInfo objectForKey:@"badge"] integerValue];
#endif
}

■CPANで、Net::APNSを導入
サーバ側でCPANを使ってNet::APNSを導入する必要がある。
これで、Apple Push Notification Serviceが使えるようになる。

■サーバサイドコーディング
perlを使って通知を送るプログラムを作成する。

apns.cgi

#! /usr/bin/perl
use strict;
use warnings;
use Jcode;
use Net::APNS;
{

  print"Content-type: text/html;¥r¥n¥r¥n";
  print  <<HTML;
<html><head>
   <meta http-equiv="Content-Type" content="text/html; charset=utf8">
   <meta http-equiv="Content-Style-Type" content="text/css">
</head>
<body>
HTML

my $val = "新しく村が出来ました。";
&Jcode::convert(¥$val,'utf-8');

  my $APNS = Net::APNS->new;
  my $Notifier = $APNS->notify({
      cert   => "apns-dev-cert.pem",
      key    => "apns-dev.pem",
      passwd => ""
  });
  $Notifier->devicetoken("4a1542abcdef.....ea9");
  $Notifier->sandbox(1);
  $Notifier->message($val);
  $Notifier->badge(1);
  $Notifier->sound('default');
  $Notifier->custom({ custom_key => 'custom_value' });
  $Notifier->write;

  print "処理終了<br>¥n";

  print <<HTML;
</body></html>
HTML

  exit;
}
今回はdevice_tokenは固定値を埋め込んだが、ユーザリストあたりから持ってきてループすれば良いだろう。
実行すると、すぐに下記の通知が来た。
開発用の汝人狼也を入れていれば出るようだ。
リリース用は鍵(*.pem)をProductionから作りなおしておく必要が有る。
ところで、拡張子が*.pemではURLが分かった場合に鍵が外部に漏れてしまうので以下のような対策をとる必要がある。
 ・拡張子を*.cgiにして、実行権限を与えず、意図的にInternal Server Errorにする
 ・そもそもファイルを直接見れない場所に配置する
後者が一般的。

■リリースに向けて
開発時はなんでも証明出来れば良かった(?)のだがproductionは証明書を書き出す必要がある。
キーチェインのApple Production iOS Push Serviceの証明書から*.p12を書き出し、ターミナルから*.pemを作成する。

ファイル名は、devと見分けがつくように、proとした。

ターミナル

openssl pkcs12 -clcerts -nokeys -out apns-pro-cert.pem -in apns-pro-cert.p12

openssl pkcs12 -nocerts -out apns-pro-key.pem -in apns-pro-cert.p12

openssl rsa -in apns-pro-key.pem -out apns-pro-key-noenc.pem

cat apns-pro-cert.pem apns-pro-key-noenc.pem > apns-pro.pem
リリースされたアプリ所持者に通知するならproを使うようにcgiで指定すれば良いだろう。
それとGatewayが違うので、sandboxには0を指定する。

■リリースしたが通知がこない!?
sandboxとproductionでAPNS番号(個体識別番号?)が違うので、apns.cgiで固定で入れた4a1542...のままだと通知が来ない。
改めてproduction用に取得しなおす必要がある。
調査の過程で、productionが有効の場合、development(sandbox)が無効という記事を見かけたが、そんな事は無く両方とも有効。


■その他
永井氏(@x68060)、水野氏(@cz500)にはお知恵を拝借+ご協力頂いたのでこの場を借りて御礼申し上げます。