iOSでPush通知機能の実装に必要な2つの証明書を作成する方法まとめました

2014年5月23日

iOSでPush通知を実装する方法をまとめました。おそらくここを読んでいる方は実機テストをした方だと思いますので実機テストの方法は省きます。

Pushに必要な証明書の作り方を中心に取り上げます。

PushはPHPのライブラリであるapns-phpを使って行います。

最終的にPushに必要な証明書は2つ

最終的に必要になる証明書は2つのみです。

1. ルート証明書
entrust_root_certification_authority.pem
2. サーバ用証明書
server_certificates_sandbox.pem

これを準備するのが大変です。

作り方は同じで cerからpemに変換 という処理が必要になります。これが大変。また、ファイルの名前がはじめどれがどれか分からなくなります。本記事ではデフォルトのファイル名を使って説明しています。最終段階でリネームしますのでご安心を。

CertificateSigningRequest.certSigningRequest を作成

- Macのアプリケーション → ユーティリティ → キーチェインアクセスを起動
- メニューのキーチェインアクセス→証明書アシスタント→認証局に証明書を要求…

ユーザのメールアドレスと名称を入力します。メールアドレスは一応Apple Developperと同じメールアドレスを入力しました。

aps_development.cerをCertificateSigningRequest.certSigningRequestから作成

作成した CertificateSigningRequest.certSigningRequest を Apple Developerで aps_development.cer に変換します。

Apple Developerにアクセス。APP IDsから制作中のAPPを選択。

1で作成したCertificateSigningRequest.certSigningRequestをアップロード。

aps_development.cerをDownloadします。

ここまでで作成したファイルは2つです。

ついてこれていますか?

aps_development.cer から server_certificates_sandbox.pem を作成

aps_development.cer をダブルクリックするとキーチェインアクセスが起動します。

キーチェインアクセスの左側のメニューで「ログイン」「証明書」を選択します。そうすると

Apple Development IOS Push Service: ______

があると思います。これが追加された aps_development.cer になります(何度も挑戦するとどれがどれかわからなくなるので注意を)。

これを書き出しします。

証明書.p12 としてまず書き出しします。

パスワードが求められますが、最初はとりあえずパスワード無しでいいかと思います。

キーの持ち出し大丈夫ですか?ということで管理者のパスワードも求められます。証明書のパスワードとは違うので注意が必要です。

最後にopensslコマンドで 証明書.p12 から server_certificates_sandbox.pem へ変換します。

openssl pkcs12 -in 証明書.p12 -out server_certificates_sandbox.pem -nodes -clcerts

ついてこれてますか?

ルート証明書作成 entrust_root_certification_authority.pem を作成

https://www.entrust.net/downloads/root_index.cfm

こちらより Personal Use and Secure Server Installation を選択

entrust_2048_ca.cer のリンクを探してDownload

entrust_2048_ca.cer をダブルクリックすると キーチェインアクセス が起動します。

キーチェインアクセスの左のメニュー「ログイン」「すべての証明書」を選び右上の検索窓でキーワード「entrust」を入力。

Entrust.net Certification Authority (2048) を選択・書き出します。

この時重要なのは pem ファイルで書き出しすることです。

書き出しすると Entrust.net Certification Authority (2048).pem になると思いますが、このファイルを entrust_root_certification_authority.pem としてリネームします。

XCode、apns-phpでコード実装

このへんは調べるとすぐにわかり易いサイトが出てくるかと思いますがとりあえず参考に。

ApnsPHPでのsample_push.php ライブラリに添付されていますがちょっとException処理を追加しています。

<?php
require_once 'ApnsPHP/Autoload.php';
$push = new ApnsPHP_Push(
	ApnsPHP_Abstract::ENVIRONMENT_SANDBOX,
	'server_certificates_sandbox.pem'
);
$push->setRootCertificationAuthority('entrust_root_certification_authority.pem'); // 本記事で作成したもの(ルート証明書)

$push->connect();

try{

    $message = new ApnsPHP_Message('デバイスID'); // XCodeで取得したもの <>とスペースを取り除きます
    $message->setCustomIdentifier("Message-Badge-3"); // それぞれのメッセージID
    $message->setBadge(3); // バッジを付けます
    $message->setText('テストメッセージ'); // メッセージを送信
    $message->setSound();
    $message->setCustomProperty('acme2', array('bang', 'whiz')); // 独自に送りたいプロパティ
    $message->setExpiry(30);
    $push->add($message);
    $push->send();

} catch(ApnsPHP_Message_Exception $e){
    print_r( $e->getMessage() );
}
$push->disconnect();

$aErrorQueue = $push->getErrors();
if (!empty($aErrorQueue)) {
	var_dump($aErrorQueue);
}

ポイントはtry, catchすることです。公式のapns-phpライブラリに付いているサンプルではtry, cacheがなくなぜエラーなのかわかりにくいのです。

XCode側

AppDelegate.mで

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [application registerForRemoteNotificationTypes: (UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeSound|UIRemoteNotificationTypeAlert)];
    return YES;
}
- (void)application:(UIApplication*)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)devToken{
    // デバイストークン送信
    [self sendProviderDeviceToken:devToken];
}
- (void)sendProviderDeviceToken:(NSData *)token
{
    NSString *device_id = [[[token description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] stringByReplacingOccurrencesOfString:@" " withString:@""];

    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString: [NSString stringWithFormat:@"http://_________/%@", device_id] ]];

    [NSURLConnection
     sendAsynchronousRequest:request
     queue:[NSOperationQueue mainQueue]
     completionHandler:^(NSURLResponse *response, NSData *jsonData, NSError *error) {
         NSLog(@"Sent Device ID : [%@]", device_id);
     }
     ];

}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    // フォアグラウンド
    if (application.applicationState == UIApplicationStateActive)
    {
        NSLog(@"%@", [userInfo description]);
    }
    // バックグラウンド
    if (application.applicationState == UIApplicationStateInactive)
    {
        NSLog(@"%@", [userInfo description]);
    }
}

PostfixなどのMTAからメールを送る際に自ホスト→自ホストにだけ届かない時の対処策

2014年3月31日

とても限定的なお話なのですが、PostfixなどのMTAから自ホスト→自ホストにメールを送っているのになぜか自ホストにだけ届かない時の対処策ということについてまとめたので参考になればと。

自ホストにのみMTAからメールが送れないとい現象の原因はだいたいの場合、Google Appsなどでメールを利用している場合、MTA自身がGoogleではなく自ホスト自身にメールを送っている場合がほとんどです。mailコマンドを叩くと不本意にも見つからなかったメールが見つかったりします。

要するに、hosts か postfix main.cf の mydestination の設定に問題があるわけです。

ここまで理解できれば簡単で

自分のホスト名を example.com としたらメールサーバを s1.example.com などと分けてしまいましょう。結構当たり前なのですが、小規模だと最近では同一ということが多いのではないかなと。

# vim /etc/hostname

s1.hostname.com

逆引きしている場合は逆引き設定も忘れずに。特にMXレコードなど注意です。

あとは hostsの書き換え

# vim /etc/hosts
127.0.0.1	localhost
___.___.___.___	s1.example.com

最後に postfixの設定を。ポイントはmydestinationをs1.example.comにすることです。

# vim /etc/postfix/main.cf
myhostname = example.com # 送信元アドレス
mydomain = example.com # 送信元アドレス
myorigin = /etc/mailname # example.comを記述しておく
mydestination = s1.example.com, localhost

これで自ホストにのみ届かないということはなくなるかと思います。

Symfony2: FormTypeにサービスコンテナなどをインジェクションする方法

2014年3月29日

DoctrineなどのQueryBuilderで取得した項目をフォームで利用するなど当たり前の事なのですが、これがSymfony2ではインジェクションをしないことには実装できません。意外とややこしいのです。

例えば

class ExampleType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $em = $this->getDoctrine()->getManager();
        $entities = $em->getRepository('AcmeUserBundle:user')->findAll();

        $choices = array();
        foreach($entities as $entity)
        {
            $choices[$entity->getId()] = $entity->getName();
        }
        $builder
            ->add('column_name', 'choice', array( 'choices' => $choices )))
        ;
    }

なんて事ができればいいのですが、インジェクションしないとこれは動きません。で!どのようにするかというと、service.yml に インジェクションするコンストラクタを設定定義してます。この定義した内容を formTypeをnewする際にコントローラー側から getします。コード見ないときっとわかりにくいかと思います。

まずは service.yml ですが、各bundle内でも構いませんし、appの中でも構いません。bundleの中に記述したほうが分かりやすいと思ってます。

service.yml の内容

services:
    acme_user.user.form.type:
        class: Acme\UserBundle\Form\UserType
        arguments: ["@doctrine.orm.entity_manager", "@service_container"]
        tags:
            - { name: form.type, alias: app_user_user }

@doctrine.orm.entity_manager
@service_container

は php app/console container:debug で一覧表示できますが、細かい動作の内容についてはsensioのソースの中を調べることになります。自分で書いたservice.ymlも同じようにコンストラクタとしてインジェクション出来ます。便利ですね。

続いてformTypeクラスのコンストラクタを記述します。先の動作しないformTypeを書き換えます。__construct を実装している所がポイントです。

class ExampleType extends AbstractType
{
    private $doctrine, $service_container;
    // private $user;

    public function __construct($doctrine, $service_container)
    {
        $this->doctrine = $doctrine;
        $this->service_container = $service_container;
        /* 下記のように予め書いておくことでUserにもアクセスできます。 */
        // $this->user = $service_container->get('security.context')->getToken()->getUser();
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $entities = $this->doctrine->getRepository('AcmeUserBundle:user')->findAll();

        $choices = array();
        foreach($entities as $entity)
        {
            $choices[$entity->getId()] = $entity->getName();
        }
        $builder
            ->add('column_name', 'choice', array( 'choices' => $choices )))
        ;
    }
    public function getName()
    {
        return 'app_user_user'; /* service.ymlのalias名を入力 */
    }

このformTypeをコントローラーからアクセスします。

$form = $this->createForm($this->get('acme_user.user.form.type'), $entity, array(
    'action' => $this->generateUrl('user_create'), // you should fix
    'method' => 'POST',
));

という感じで $this->get(‘service.ymlで定義した名前’); という風に クラスを new します。すると service.yml 経由で目的のクラスをインジェクションすることが出来ます。

多用するの結構面倒なんですよね。service.ymlの記述とコンストラクタをつくるところや、formtypeをnewするところなどポイントは多々ありますが、テンプレを作って動くサンプルを作ることがポイントね。

Symfony2のSwiftmailer便利ライブラリ公開

2014年3月23日

Symfony2でメールを送る際にSwiftmailを利用しますが、テンプレート読み込みなどまとめてライブラリ化すると非常に便利に、素早くメールを送ることが出来ます。

ライブラリにまとめたので是非活用してみてください。

1.parameters.yml記述

parameters:
    swiftmailer.from: "noreply@example.com"
    swiftmailer.admin: "noreply@example.com"
    swiftmailer.name: "Sender Name"

2.config.yml記述

services:
    swiftmailer:
        class: App\CommonBundle\Classes\Swiftmailer
        arguments: ["@service_container", "@mailer"]

3.クラスを作成

App/CommonBundle/Classes/Swiftmailer に下記ファイル、2つのファイルを作成します。

App/CommonBundle/Classes/Swiftmailer/Swiftmailer.php
App/CommonBundle/Classes/Swiftmailer/SwiftmailerConfigure.php

App/CommonBundle/Classes/Swiftmailer/Swiftmailer.php の内容

<?php

namespace App\CommonBundle\Classes;
use App\CommonBundle\Classes\SwiftmailerConfigure;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class Swiftmailer
{

    private $serviceContainer;
    private $mailer;

    private $template_text;
    private $template_html;
    private $options;

    private $subject;
    private $admin;

    public $from;
    public $to;

    public function __construct($serviceContainer, $mailer)
    {
        new SwiftmailerConfigure();
        $this->data_pack = array();
        $this->serviceContainer = $serviceContainer;
        $this->mailer = $mailer;
        $this->from = array(
            $this->serviceContainer->getParameter('swiftmailer.from') => $this->serviceContainer->getParameter('swiftmailer.name')
        );
        $this->admin = array(
            $this->serviceContainer->getParameter('swiftmailer.admin') => $this->serviceContainer->getParameter('swiftmailer.name')
        );
    }
    public function setTemplate($template_name)
    {
        $this->template_text = 'AppCommonBundle:Swiftmailer:'.$template_name.'.text.twig';
        $this->template_html = 'AppCommonBundle:Swiftmailer:'.$template_name.'.html.twig';
    }
    public function debug()
    {
       return $this->prepare();
    }
    private function prepare()
    {

       $template = $this->serviceContainer->get('templating');
       if(!isset($this->data_pack['options'])) $this->data_pack['options'] = array();

       $html_body = $template->render($this->template_html, $this->data_pack['options']);
       $text_body = $template->render($this->template_text, $this->data_pack['options']);

       $message = \Swift_Message::newInstance()
            ->setCharset('iso-2022-jp')
            ->setEncoder(new \Swift_Mime_ContentEncoder_PlainContentEncoder('7bit'))
            ->setSubject( $this->subject )
            ->setFrom( $this->from )
            ->setBody($text_body, 'text/plain', 'iso-2022-jp')
            ->addPart($html_body, 'text/html', 'iso-2022-jp');

        $message->setTo($this->to);

        return $message;

    }
    public function send()
    {

        $message = $this->prepare();
        $this->mailer->send( $this->prepare() );

    }
    public function setTo($to, $to_name)
    {
        $this->to = array($to => $to_name);
    }
    public function setFrom($from_mail, $from_name)
    {
        $this->from = array($from_mail => $from_name);
    }
    public function setSubject($subject)
    {
        $this->subject = $subject;
    }
    public function setEnclosure($file_path)
    {
        $this->data_pack['enclosure'] = $file_path;
    }
    public function setOptions($options)
    {
        $this->data_pack['options'] = $options;
    }
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'App\CommonBundle\Classes\Swiftmailer'
        ));
    }
    public function getAdmin(){
        return $this->admin;
    }
    public function getName()
    {
        return 'swiftmailer';
    }
    public function __toString(){
        return $this->prepare()->toString();
    }
}

App/CommonBundle/Classes/Swiftmailer/SwiftmailerConfigure.php の内容

<?php

namespace App\CommonBundle\Classes;

class SwiftmailerConfigure
{
    public function __construct()
    {
        $this->configureSwiftMailer();
    }
    protected function configureSwiftMailer(){
        \Swift::init(function () {
            \Swift_DependencyContainer::getInstance()
                ->register('mime.qpheaderencoder')
                ->asAliasOf('mime.base64headerencoder');
            \Swift_Preferences::getInstance()->setCharset('iso-2022-jp');
        });
    }

}

4.SwiftmailerConfigure()をBundleのgetParent()やコンストラクタに実装

App/UserBundle/UserBundle.phpなどのBundleクラスの親にSwiftmailerConfigureを実装します。もっといい方法があるかもしれませんが。これはここで実装するSwiftmailerのコンテナとは別にFOSUserBundleなどでメールが送信される際に日本語がしっかりと文字化けせずに送信するようにエンコードなどの初期化を行っています。

<?php

namespace App\UserBundle;

use App\CommonBundle\Classes\SwiftmailerConfigure;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class AppUploadBundle extends Bundle
{
    public function getParent()
    {
        new SwiftmailerConfigure();
    }
    // ....
}

5.Swiftmailerのメールテンプレートを準備

App/CommonBundle/Resources/views/Swiftmailer/mail.text.twig
App/CommonBundle/Resources/views/Swiftmailer/mail.html.twig

の2つを準備します。mail という名前でテンプレートを作成すると、メール送信時にこのmailというキーワードを利用することになります。text, htmlの2つを準備してください。テンプレートはTwigテンプレートで記述します。{{ entity.title }} や {{ entity.email }} という感じです。

6.Swiftmailerをから送信する

$swiftmailer = $this->get('swiftmailer');
$swiftmailer->setTo('to@example.com', 'Your Name');
$swiftmailer->setTemplate('mail'); // template name "mail"
$swiftmailer->setSubject('TEST SUBJECT');
$swiftmailer->setOptions(array('entity' => $entity)); // i.g.

これでメールが送信されるかと思います。

メールが送信されない場合はparametersなどを確認して下さい。例えば

parameters:
    mailer_transport: sendmail
    mailer_host: 127.0.0.1
    mailer_user: null
    mailer_password: null

この辺りに問題がある気がしますので確認して下さい。あとは問題があるとしたらキャッシュをクリアしたりしてみてください。

curl -I を wgetでも行う方法 (すぐ忘れる)

2014年3月11日

$ curl -I http://blog.kmusiclife.com/
HTTP/1.1 200 OK
Server: nginx
Date: Tue, 11 Mar 2014 02:46:18 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/5.3.3-7+squeeze15
X-Pingback: http://blog.kmusiclife.com/_____

これをwgetで行いたい場合は下記のとおりになります。

$ wget -Sq --spider http://blog.kmusiclife.com/
HTTP/1.1 200 OK
Server: nginx
Date: Tue, 11 Mar 2014 02:47:19 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/5.3.3-7+squeeze15
X-Pingback: http://blog.kmusiclife.com/_____

curl -I と wget -Qs –spider です。

次のページ

検索

 

最新記事

タグ一覧

中の人について

カテゴリ一覧

年間アーカイブ