あたらしものずきっ!

試してみたものとか、遊んでみたものを色々記してみます。

iPhoneにpushするApple Push Notification Serviceなコードかいてみた

とりあえず動くこと最優先。実務に使うにはやばい出来です。Moose使ってNet::APNSなモジュールに書き直してる最中。

phpのサンプルが文字コードに触れてなかったので、その部分手を抜いたら大やけど。pushで送られるpayloadはunicodeのようです。まぁ、当たり前なんだろうけどな。

JSON::XSでutf8のフラグを操作しないとiPhone側にalertが出ないのを確認済み。

#!/usr/local/bin/perl

use warnings;
use strict;
use Encode qw/encode decode/;
use Socket;
use Net::SSLeay qw/die_now die_if_ssl_error/;
use JSON::XS;
use YAML::Syck;

#YAML
$YAML::Syck::ImplicitTyping = 1;
$YAML::Syck::SingleQuote    = 1;

#Net::SSLeay initialize
$Net::SSLeay::trace = 4;
$Net::SSLeay::ssl_version = 10;

Net::SSLeay::load_error_strings();
Net::SSLeay::SSLeay_add_ssl_algorithms();
Net::SSLeay::randomize();

use constant ssl_error_want_read    => 2;
use constant ssl_error_want_write   => 3;
use constant ssl_error_want_connect => 4;
use constant ssl_error_want_accept  => 5;

my $app_config = LoadFile("APNS_CONF.yaml");
my $message = $ARGV[0] || die "Need message";
$message = decode('utf8', $message);
$message = encode('unicode', $message);
my $badge   = $ARGV[1] || die "Need numbers";

$badge =~ s/[^0-9]//g;
if($badge eq "") { die "Badge must set numbers."; }
$app_config->{device_token} =~ s/\s//g;
if( $app_config->{device_token} eq "" ) { die "device_token nothing!"; }

#Apple port initialize
my $apple_port = 2195;
#my $apple_port = 2196;
my $apple_host = 'gateway.sandbox.push.apple.com';
#my $apple_host = 'feedback.push.apple.com';
my $packed_apple_host = inet_aton($apple_host)
    or die "Cannot pack $apple_host: $!";
print "Packed $apple_host\n";
my $apple_serv_params = sockaddr_in($apple_port, $packed_apple_host)
    or die "Cannot pack $apple_host:$apple_port: $!";
print "Packed $apple_host:$apple_port\n";
my $payload = JSON::XS->new->utf8(1)->encode({
    aps => {
        alert => $message,
        badge => $badge,
    },
});
$payload =~ s/("badge":)"([^"]+)"/$1$2/;
#Connect to apple
my $sock;
socket  ($sock, PF_INET, SOCK_STREAM, getprotobyname('tcp'))  or die "socket: $!";
connect ($sock, $apple_serv_params) or die "connect: $!";

my $ctx = Net::SSLeay::CTX_new() or die_now("Failed to create SSL_CTX $!.");
print "Create SSL_CTX\n";

Net::SSLeay::CTX_set_options($ctx, &Net::SSLeay::OP_ALL)
    and die_if_ssl_error("ssl ctx set options");
print "Set options to ssl ctx\n";
Net::SSLeay::CTX_set_default_passwd_cb($ctx, sub { $app_config->{passwd} });
Net::SSLeay::CTX_use_RSAPrivateKey_file($ctx, $app_config->{key}, &Net::SSLeay::FILETYPE_PEM);
Net::SSLeay::die_if_ssl_error("private key");
print "Set RSA private key\n";
Net::SSLeay::CTX_use_certificate_file ($ctx, $app_config->{cert}, &Net::SSLeay::FILETYPE_PEM);
Net::SSLeay::die_if_ssl_error("certificate");

my $ssl = Net::SSLeay::new($ctx) or die_now("Failed to create SSL $!.");
print "Created SSL\n";
Net::SSLeay::set_fd($ssl, fileno($sock)) or die_now("Failed to set SSL Toolkit $!");
Net::SSLeay::connect($ssl) or die_now("Failed to connect SSL $!");

my $res = Net::SSLeay::write($ssl,
    chr(0).pack('n',32).pack('H*',$app_config->{device_token}).pack('n',length($payload)).$payload
) or die_if_ssl_error("ssl write");
if($res > 0) {
    print "Written $payload Size:$res\n";
}else{
    print "Failed to write $!\n";   
}
#my $got = Net::SSLeay::read($ssl) or die_if_ssl_error("ssl read");
#print $got;
 
CORE::shutdown($sock, 1);
Net::SSLeay::free ($ssl);
Net::SSLeay::CTX_free ($ctx);
close($sock);