iOS + PHPでPush Notificationを実装する
Morning Relayという目覚ましアプリで、iOS + PHPでPush Notificationを実装してみた。公式ドキュメントを読むと複雑で難しそうだが、じっくりやれば大丈夫。サーバー側の実装は公式ドキュメントには実例が載っていないのだが、「apns-php」というPHPのライブラリを使うことでラクにできた。
環境
概要
- 準備
- iOS側での実装
- サーバー側での実装
準備
1. App IDを作成する
※ すでにApp IDがあるときは、これらの操作は不要
(1) iOS Dev Center > iOS Provisioning Portalを開く。
(2) ウインドウの左メニューの[App IDs]をクリック
(3) [New App ID] をクリック
(4) Descriptionにアプリ名(App IDを説明する名前)を入力、Bundle Identifierにドメイン名を逆にするなどの一意の値を入力して、[Submit]をクリック。App IDが新規に作成される。
2. プロビジョニングファイルの作成とローカルへのコピー
(1) iOS Provisioning Portalにアクセスして[New Profile]からプロビジョニングファイルを作成する。App IDは、1で作成したものを選択する。
(2) 作成したプロビジョニングファイルをダウンロードして、~/Library/MobileDevice/Provisioning Profilesにコピーする。ファイル名は、MorningRelayDev.mobileprovisionなど、開発用か製品用か区別できるようにしておく。
3. ローカルでCSR(証明書署名要求: Certificate Signing Request)ファイルを作成、それをAppleのサーバーにアップロードして証明書と鍵を受け取る
(1) ローカルでキーチェーンアクセスアプリケーションを起動して、キーチェーンアクセス > 証明書アシスタント > 認証局に証明書を要求…をクリック、ユーザのメールアドレスはAppleに登録したメールアドレスを入力、通称は開発者の名前や会社名などを入力、要求の処理でディスクに保存をチェック、続けるをクリックして、適当な名前を付けてファイルを保存する
(2) iOS Provisioning Portal > App IDsから先ほど作成したアプリのConfigureリンクをクリック、Configure App ID ページで「Enable for Push Notification Services」ボックスにチェックを入れて、まずは開発版のDevelopment Push SSL Certificateの[Configure]をクリック、[Continue]クリックで次に進む。
(3) (1)で作成したキーファイルを設定して、[Generate]をクリックする。なぜかGoogle Chromeでは反応しなかった。Safariを使えば数秒で認証が完了する。
(4) 完了すると「Download & Install Your Apple Push Notification service SSL Certificate」の画面が表示されるので、[Download]ボタンで証明書をダウンロードする。ここで、App IDsの画面で、アプリの「Push Notification」が「Configurable」から「Enabled」になったことを確認する。
(5) ダウンロードした証明書(aps_development.cer)は開発用なので、aps_development_dev.cerなどにリネーム後、ローカルでダブルクリックしてMacのキーチェーンに登録する。
※ 「Development Push SSL Certificate」に続き「Production Push SSL Certificate」も同様に上記の手順を実行する。
MEMO:
> 公式ドキュメントにはもう少し詳しく書いてある:CSRの生成時に、キーチェーンアクセスは秘密暗号鍵と公開暗号鍵のペアを生成します。秘密鍵 はデフォルトでログインキーチェーンに組み込まれます。公開鍵は、CA(認証局)に送信され るCSRに含められます。CAから証明書が戻ってくると、その証明書の項目の1つが公開鍵になっています。
4. 自分のサーバーに配置するためのSSL証明書と鍵を作成する(2種類のpemファイルを作成)
(1) キーチェーンアクセスからSSL証明書と鍵を取得する。左側のペインの自分の証明書 > Apple Development IOS Push Services: *** をクリックして展開して、証明書と秘密鍵の両方が表示させる。
(2) 証明書と鍵の両方を選択して、ファイル > 書き出す」を選択、それらを「個人情報交換(.p12)」ファイルとして書き出す。
(3) サーバーで使えるように、以下のコマンドで.p12形式を.pem形式に変換する。AppNameDev.pemファイルが作られる。(「AppNameDev」部分を書き換える。)
$ openssl pkcs12 -in AppNameDev.p12 -out AppNameDev.pem -nodes
(4) Entrust Root Certification Authorityを作成する。キーチェーンアクセス > 左下ペインの「証明書」 > Entrust Root Certification Authority > 右クリック > 「"Entrust Root Certification Authority"を書き出す...」 > ファイル名を「entrust_root_certification_authority.pem」にして保存する。これで、entrust_root_certification_authority.pemファイルが作られる。
iOS側での実装
1. RemoteNotificationを登録する
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... // アプリケーションが起動するたびに、デバイストークンを要求してそれをプロバイダに渡すことで、 // プロバイダが最新のデバイストークンを持つことを保証 [application registerForRemoteNotificationTypes: (UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeSound|UIRemoteNotificationTypeAlert)]; return YES; }
これで、アプリ初回起動時にRemoteNotificationを許可するかどうかのダイアログが表示されるようになる。「didFinishLaunching」内だと「didFinishLaunchingWithOptions」メソッドが存在すれば呼ばれないので注意。
2. デバイストークン(Device Token)を取得する
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *) devToken { NSLog(@"deviceToken: %@", devToken); [self sendProviderDeviceToken:devToken]; // 自分のサーバーに送信する } - (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *) err { NSLog(@"Errorinregistration.Error:%@", err); }
3. デバイストークンを自分のサーバーに送る
- (void)sendProviderDeviceToken:(NSData *)token { NSMutableData *data = [NSMutableData data]; [data appendData:[@"device=" dataUsingEncoding:NSUTF8StringEncoding]]; [data appendData:token]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://morning-relay.com/push"]]; [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; [request setHTTPMethod:@"POST"]; [request setHTTPBody:data]; [NSURLConnection connectionWithRequest:request delegate:self]; }
この例ではNSURLConnectionクラスを使っているが、自分のサーバーにデータをポストするだけなので、どんな方法でもOK。
サーバー側での実装
iOSから送られてきたデバイストークンを受け取る処理は省略。以下は、Apple Push Notificationサービス(APNs)のサーバーにPUSHする処理。
1. apns-phpをダウンロードする
(1) apns-phpというライブラリ(ApnsPHP-r100.zip)をダウンロードする。
(2) ZIPファイルをサーバー(ローカルホストでもOK)に展開して、ブラウザからアクセスできる場所に配置する。
(3) サーバープログラムのルートディレクトリ(CakePHPの場合はapp/webroot)にcertificatesというフォルダを作り、その中に上記で作成した「AppNameDev.pem」と「entrust_root_certification_authority.pem」ファイルを配置する。※ 本番では適切なアクセス権を設定する
2. apns-phpのファイルを修正する
(4) sample_push.phpを以下のように修正する。
... // Instanciate a new ApnsPHP_Push object $push = new ApnsPHP_Push( ApnsPHP_Abstract::ENVIRONMENT_SANDBOX, // 修正前 // 'server_certificates_bundle_sandbox.pem', // 修正後 'certificates/AppNameDev.pem' ); ... // Set the Root Certificate Autority to verify the Apple remote peer // 修正前 // $push->setRootCertificationAuthority('entrust_root_certification_authority.pem'); // 修正後 $push->setRootCertificationAuthority('certificates/entrust_root_certification_authority.pem'); ... // Instantiate a new Message with a single recipient // 修正前 // $message = new ApnsPHP_Message('9e1791742ce6658f0d04128ec05ae4ed9858e534ff6ae6e8e18c68ded547ba0b'); // 修正後 $message = new ApnsPHP_Message('iOSで取得したDeviceTokenを記入。NSLogで出力したものから半角スペースを除くだけでOK。');
3. Apple Push Notification サービス(APNs)にプッシュ
(1) ブラウザでsample_push.phpにアクセスする
これでOKと思いきや、ブラウザでsample_push.phpにアクセスすると、以下のエラーが表示される。
Mon, 27 Aug 2012 08:22:13 +0200 ApnsPHP[4102]: INFO: Trying ssl://gateway.sandbox.push.apple.com:2195... Mon, 27 Aug 2012 08:22:14 +0200 ApnsPHP[4102]: ERROR: Unable to connect to 'ssl://gateway.sandbox.push.apple.com:2195': (0) Mon, 27 Aug 2012 08:22:14 +0200 ApnsPHP[4102]: INFO: Retry to connect (1/3)... Mon, 27 Aug 2012 08:22:15 +0200 ApnsPHP[4102]: INFO: Trying ssl://gateway.sandbox.push.apple.com:2195... Mon, 27 Aug 2012 08:22:15 +0200 ApnsPHP[4102]: ERROR: Unable to connect to 'ssl://gateway.sandbox.push.apple.com:2195': (0) Mon, 27 Aug 2012 08:22:15 +0200 ApnsPHP[4102]: INFO: Retry to connect (2/3)... Mon, 27 Aug 2012 08:22:16 +0200 ApnsPHP[4102]: INFO: Trying ssl://gateway.sandbox.push.apple.com:2195... Mon, 27 Aug 2012 08:22:17 +0200 ApnsPHP[4102]: ERROR: Unable to connect to 'ssl://gateway.sandbox.push.apple.com:2195': (0) Mon, 27 Aug 2012 08:22:17 +0200 ApnsPHP[4102]: INFO: Retry to connect (3/3)... Mon, 27 Aug 2012 08:22:18 +0200 ApnsPHP[4102]: INFO: Trying ssl://gateway.sandbox.push.apple.com:2195... Unable to connect to 'ssl://gateway.sandbox.push.apple.com:2195': (0)
回避方法は、Stack Over Flowのページにあった。
// Abstract.php $streamContext = stream_context_create(array('ssl' => array( // この行をコメントアウト。なぜこうすれば動くかは不明。 // 'verify_peer' => isset($this->_sRootCertificationAuthorityFile), 'cafile' => $this->_sRootCertificationAuthorityFile, 'local_cert' => $this->_sProviderCertificateFile )));
(2) リトライ!
これで、再度ブラウザからアクセスすると、以下のようなデバッグメッセージが表示されて...
Mon, 27 Aug 2012 08:59:35 +0200 ApnsPHP[6999]: INFO: Trying ssl://gateway.sandbox.push.apple.com:2195... Mon, 27 Aug 2012 08:59:35 +0200 ApnsPHP[6999]: INFO: Connected to ssl://gateway.sandbox.push.apple.com:2195. Mon, 27 Aug 2012 08:59:35 +0200 ApnsPHP[6999]: INFO: Sending messages queue, run #1: 1 message(s) left in queue. Mon, 27 Aug 2012 08:59:35 +0200 ApnsPHP[6999]: STATUS: Sending message ID 1 [custom identifier: Message-Badge-3] (1/3): 177 bytes. Mon, 27 Aug 2012 08:59:36 +0200 ApnsPHP[6999]: INFO: Disconnected.