2014年9月30日火曜日

WordPressで$_SESSIONが使えない

wordpress_errorページ遷移のログを簡易的に$_SESSIONに保存して使いまわそうとしたら動作しなかったので調査。

環境: CentOS 6.5, nginx 1.6.2, php-fpm 5.4.33

initにフックしてsession_startしても、各ページでsession_idするたびに新たに新しいIDになって引き継げない。

function hoge_init() {
    if (!session_id()) {
        session_start();
    }
}
add_action('init', 'hoge_init');

調べてみるとPHPの設定でsession.cookie_pathの設定が「/tmp」になってた。
# less /etc/php.ini

session.cookie_path = /tmp

cookie_pathはクッキーを保存するパス(URI)。一致したアクセスのみクッキーを使えるようになる。

cookie_pathを直したら動いた。

他のサーバーをみると「/」だったので、session.save_pathと勘違いして書き換えてたみたい。設定した覚えがないのでどっかのバージョンは/tmpに設定されるかも。

ちなみにsession.save_pathの設定は/etc/php-fpm.d/www.confに記載してある。

 

 

ちょっと深追いしてWordPressと_SESSIONの関係を調査。

WordPressはグローバル変数として_SESSIONを扱っていない。

var_dump($GLOBALS);
するとWordPressが生成したグローバル変数が出力される。

 

ということで$_SESSIONを使うのはやめて、update_user_metaを使うことにした。

 

< Related Posts >

2014年9月26日金曜日

WordPressのWP_Cronを使って非同期処理

wp-cronWordPressに投稿するとアプリにプッシュ通知する仕組みを作っているときの覚書。

環境: CentOS 6.5, WordPress 4.0.0

Linuxのcronとかpthreadに頼らずにやってみた。

WordPressにはwp_schedule_single_eventという登録したフックを一度だけ実行する関数がある。

ソースをみると10分の間に同じリクエストがくると無視されるが、$argsが違えばスケジュールに登録される。

同じ記事に対して何度もプッシュ通知が来てもウザいのでちょうどいい。

コードサンプルは上の公式サイトを参考に。

 

 

でも念のためLinuxのCronに10分ごとに/wp-cron.phpにリクエストを送る処理書いた。。。
# vi /etc/cron.d/wordpress

MAILTO=""

# Excute WordPress WP_Cron
*/10 * * * * root curl http://hoge.com/wp-cron.php > /dev/null 2>&1

参考サイト:Better wp-cron using linux's crontab

 

< Related Posts >

2014年9月25日木曜日

【Titanium】ビルドしたときにzipalignエラー

titanium_errorネットで拾った古いソースコードを古いSDKでビルドしたときに下記エラーに遭遇した。

環境: Mac OS X 10.9.4, Titanium SDK 3.3.0.GA, Android SDK Tools 23.0.2

[ERROR] Unable to find Android SDK tools: zipalign.
[ERROR] You have an incomplete or out-of-date installation.
[ERROR] Verify your Android SDK packages or reinstall the Android SDK by running 'titanium setup android' or manually downloading from http://appcelerator.com/android-sdk.

下記サイトを参考にAndroid SDKにあるzipalignをコピーする

$ cd /Applications/android-sdk-macosx/
$ cp build-tools/19.1.0/zipalign tools/

これでビルドは通った。

元に戻すときは削除するだけ。
$ rm -rf tools/zipalign

 

< Related Posts >

2014年9月24日水曜日

MariaDB(MySQL)設定のチューニング

mariadb_settings5年前にも書いたけど、調べ直したときの覚書。

環境: MariaDB 10.0.13

 

参考サイト

 

変数名 デフォルト 説明
max_connections 151 最大同時接続数。
Node.jsから非同期で処理する場合は一気に増加するので多めに設定。
thread_pool_size CPUの数 同時実行するスレッドグループの数。
デフォルトのままでいい。
参考:Thread pool in MariaDB 5.5 - MariaDB Knowledge Base
thread_cache_size 0 キャッシュするスレッドの数。
"pool-of-threads"の場合無視される。
table_open_cache 400 全てのスレッドでオープンするテーブルの数。
多すぎても少なすぎてもダメらしい。
参考:Optimizing table_open_cache - MariaDB Knowledge Base
参考:MySQL :: MySQL 5.1 リファレンスマニュアル :: 6.4.8 MySQL でのテーブルのオープンとクローズの方法
key_buffer_size 134217728 MyISAMのインデックスをキャッシュするメモリ容量。全てのスレッドで共通。
実メモリの25%が妥当らしい。
参考:Optimizing key_buffer_size - MariaDB Knowledge Base
sort_buffer_size 2M ソートするときにスレッドごとに使うメモリ容量。大きすぎるとメモリが足りなくなるので注意。
read_rnd_buffer_size 262144 ソート後にレコードを読み込むときに使用するメモリ容量。これもスレッドごと。
join_buffer_size 128KB インデックスを使わないテーブル結合に使われるメモリ容量。
read_buffer_size 128KB スレッドごとのシーケンシャルスキャンに使うメモリ容量。
参考:第2回 一番の基本,シーケンシャルスキャン:データベースと秋の空・RDBMS掌握術!<br />~PostgreSQLから学ぶDBMSとの付き合い方~|gihyo.jp … 技術評論社
query_cache_size 0 実行結果をキャッシュするメモリ容量。
query_cache_limit 1MB このサイズ以上の実行結果はキャッシュしない。

 

今の設定値を確認したい場合はSQLを実行する。
# mysql -p -u root
> show variables like '%size';

 

設定した値が妥当かどうか判断してくれるツールがある。

ダウンロードして実行
# cd /opt/software/
# wget http://mysqltuner.pl/ -O mysqltuner.pl
# perl mysqltuner.pl

 

 

< Related Posts >

2014年9月23日火曜日

ロードバランサとしてHAProxyかNginxか

haproxy_or_nginxロードバランサにHAProxyとNginxで迷ったときの覚書。

ベンチマークはHAProxyの方が速いみたい。

HAProxyは1.5からHTTPSもサポート。YUMのリポジトリにあるのは1.4が主流。

http, httpsをロードバランスするだけならNginxの方がいいかな?

Nginxでロードバランサするときは下記記事が参考になりそう。

 

< 2014/09/23 Modified >
Nginxの死活監視に不安があったけど、ちゃんと指定ポートに応答がなかったら別サーバーにリクエストを投げてくれた。

 

< Related Posts >

2014年9月22日月曜日

MariaDB Galera Clusterをインストールしてデータベースをクラスター化

新サービスのサーバー構成を考えているときの覚書。

環境: CentOS 6.5 x86_64, MariaDB Galera Server 10.0.13

前の記事も参考に。

サービス全体のサーバー構成。

server_structure

ロードバランサー02やWeb02がいればさらに安心できる。

ちなみにMariaDB Galera Clusterの最小ノード数は「3」。今回はデータベースをクラスター化するまで。

 

参考サイト

 

目次

  1. YUM経由でインストール
  2. 起動と設定
  3. 2台目以降のノード設定
  4. クラスターに参加

 


1.YUM経由でインストール

公式サイトを参考にYUMのリポジトリを追加する。

[web01]# vi /etc/yum.repos.d/MariaDB.repo

[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.0/centos6-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1

 

インストール実行
[web01]# yum search MariaDB
[web01]# yum install MariaDB-Galera-server

epelリポジトリを使ったので、前の記事を参考に登録した方がいいかも。

クラスター構成なので常に稼働する前提。メンテナンスで停止するときは手動で起動するため自動起動はしないようにしておく。
[web01]# chkconfig mysql off

 

 


2.起動と設定

公式ドキュメントに従う。

設定ファイルを編集
[web01]# vi /etc/my.cnf.d/server.cnf

[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci

thread_handling = pool-of-threads
max_connections = 500

# Tuning
sort_buffer_size = 1MB
read_rnd_buffer_size = 1MB
join_buffer_size = 1MB
query_cache_size=16M

#
# * Galera-related settings
#
[galera]
# Mandatory settings
wsrep_provider=/usr/lib64/galera/libgalera_smm.so
wsrep_cluster_name=hoge-cluster
wsrep_cluster_address=gcomm://
wsrep_node_name=WEB01
wsrep_node_address=192.168.0.2
wsrep_slave_threads=8
binlog_format=row
default_storage_engine=InnoDB
innodb_autoinc_lock_mode=2

「wsrep_provider」「wsrep_cluster_address」はなくても良さそうだけど、コメントアウトしたら動かなかった。

最初のノードとして起動する。起動スクリプトを読むと「bootstrap」を指定すると「--wsrep-new-cluster」として実行してくれるみたい。
[web01]# /etc/rc.d/init.d/mysql bootstrap

セキュリティの初期設定
[web01]# mysql_secure_installation

起動状態を確認
[web01]# mysql -u root -p

> SHOW GLOBAL VARIABLES LIKE 'wsrep_%';

MySQLコンソール終了
> quit

 

各ノードとの通信に使うポートを開ける。
参考:Galera Cluster Address - MariaDB Knowledge Base
参考:習うより慣れろ! iptablesテンプレート集(1):ステートフルパケットフィルタを使ったサービスの公開 (2/6) - @IT
[web01]# vi /etc/sysconfig/iptables

-A INPUT -m state --state NEW -m tcp -p tcp --dport 3306 -s 192.168.0.0/24 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 4567 -s 192.168.0.0/24 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 4444 -s 192.168.0.0/24 -j ACCEPT

ちなみに4444はrsyncが使う。

反映。確認。
[web01]# /etc/rc.d/init.d/iptables reload
[web01]# /etc/rc.d/init.d/iptables status

 


3.2番目以降のノード設定

インストールまでは一緒。

自動起動しないようにしておく。
[app01]# chkconfig mysql off

設定ファイルを編集
[app01]# vi /etc/my.cnf.d/server.cnf

[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
thread_handling = pool-of-threads
max_connections = 500

# Tuning
sort_buffer_size = 1MB
read_rnd_buffer_size = 1MB
join_buffer_size = 1MB
query_cache_size=16M

#
# * Galera-related settings
#
[galera]
# Mandatory settings
wsrep_provider=/usr/lib64/galera/libgalera_smm.so
wsrep_cluster_name=hoge-cluster
wsrep_cluster_address=gcomm://
wsrep_node_name=APP01
wsrep_node_address=192.168.0.3
wsrep_slave_threads=8
binlog_format=row
default_storage_engine=InnoDB
innodb_autoinc_lock_mode=2

赤文字以外は一緒。

 

ーーー ここから。後で気付いたクラスターに参加するので不要な設定

クラスターに参加せず起動
[app01]# /etc/rc.d/init.d/mysql start

セキュリティの初期設定
[app01]# mysql_secure_installation

Web01上のphpMyAdminからログインできるユーザーを作成する。
参考:MySQL :: MySQL 4.1 リファレンスマニュアル :: 4.4.5 MySQL への新規ユーザの追加
[app01]# mysql -p -u root mysql

> GRANT ALL PRIVILEGES ON *.* TO admin@localhost IDENTIFIED BY 'admin#1sec' WITH GRANT OPTION;
> GRANT ALL PRIVILEGES ON *.* TO admin@'192.168.0.%' IDENTIFIED BY 'some_pass' WITH GRANT OPTION;

> select host, user from user;

> quit

。。。とやったけど、同期後(クラスターに参加後)はWEB01で上書きされるのでWEB01で設定しておけば大丈夫だった。

ーーー ここまで。後で気付いたクラスターに参加するので不要な設定

 

次はファイヤーウォール(iptables)を設定。WEB01と同じ。ローカルIP以外はアクセス不可にする。
[app01]# vi /etc/sysconfig/iptables

-A INPUT -m state --state NEW -m tcp -p tcp --dport 3306 -s 192.168.0.0/24 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 4567 -s 192.168.0.0/24 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 4444 -s 192.168.0.0/24 -j ACCEPT

iptablesを反映して確認。
[app01]# /etc/rc.d/init.d/iptables reload
[app01]# /etc/rc.d/init.d/iptables status

 

Web01上のphpMyAdminからApp01のデータベースを確認できるようにしておく。
[web01]# cd /home/httpd/httpdocs/phpMyAdmin/
[web01]# cp config.sample.inc.php config.inc.php
[web01]# vi config.inc.php

/*
* First server(WEB01)
*/
$i++;
/* Authentication type */
$cfg['Servers'][$i]['auth_type'] = 'cookie';
/* Server parameters */
$cfg['Servers'][$i]['host'] = 'localhost';
$cfg['Servers'][$i]['connect_type'] = 'tcp';
$cfg['Servers'][$i]['compress'] = false;
$cfg['Servers'][$i]['AllowNoPassword'] = false;

/*
* Second server(APP01)
*/
$i++;
/* Authentication type */
$cfg['Servers'][$i]['auth_type'] = 'cookie';
/* Server parameters */
$cfg['Servers'][$i]['host'] = '192.168.0.3';
$cfg['Servers'][$i]['connect_type'] = 'tcp';
$cfg['Servers'][$i]['compress'] = false;
$cfg['Servers'][$i]['AllowNoPassword'] = false;

下記にアクセスするとCookie用のランダム文字列を生成してくれる。

phpMyAdminから各サーバーに接続できることを確認。

 


4.クラスターに参加

APP01上で作業。まずは一旦停止。
[app01]# /etc/rc.d/init.d/mysql stop

クラスターのノードとして起動。gcommに指定するノードはクラスターに参加しているノードであればどれでもいいらしい。自動的に他のノードにも伝搬される。
[app01]# /etc/rc.d/init.d/mysql start --wsrep_cluster_address=gcomm://192.168.0.2

エラーになった場合はログを確認
[app01]# less /var/lib/mysql/app01.hoge.com.err

成功した場合は状態を確認
[app01]# mysql -p -u root

> SHOW STATUS LIKE 'wsrep_%';

| wsrep_local_state_comment    | Synced
| wsrep_incoming_addresses     | 192.168.0.2:3306,192.168.0.3:3306

phpMyAdminから、WEB01上でテーブルを作ってからAPP01上で削除したりして同期されるか確認。

この設定だと起動するときに「wsrep_cluster_address」オプションを付けないとクラスターに参加しないので注意。

一度クラスターを構成してしまえば、全てがプライマリなので最初にbootstrapで起動したノード(WEB01)も「wsrep_cluster_address」を付けて再起動する必要がある。

全てがプライマリという感覚がマスター・スレイブに慣れていると最初戸惑う。「どれがマスター?」と考えてしまう。

 

< 2014/09/25 Modified >
注意事項。詳しくは公式ドキュメントを参考に。MariaDB Galera Cluster - Known Limitations - MariaDB Knowledge Base

  • InnoDBしか同期しない。MyISAMはInsertしたあと他のNodeに反映されない。
  • 全てのテーブルはPrimary Keyも持つ必要がある。

 

< Related Posts >

2014年9月21日日曜日

Nginxを設定してWordPressにリバースプロキシする

フロントエンドにNginxのサーバーを立てて、バックエンドのWordPressにリバースプロキシする設定しているときの覚書。

環境: CentOS 6.5, Nginx 1.2.6, WordPress 4.0

システム構成は下図。ポイントは外部と通信するときはHTTPSで暗号化して、内部ではHTTPでやりとりする。

server_structure_https

 

参考サイト

 

以下「https://hoge.com/demo/」アクセスしたときにweb01のWordPressを表示する設定。

SSLのバージョンに注意。

lv01のnginx設定

server {
    listen 80;
    server_name hoge.com www.hoge.com;
    return 301 https://hoge.com$request_uri;
}

server {
    listen       443 ssl;
    server_name  hoge.com;

    root /home/httpd/httpdocs/;

    #
    # SSL
    #
    ssl on;
    ssl_certificate      /etc/nginx/ssl.d/2014_hoge_cert.pem;
    ssl_certificate_key  /etc/nginx/ssl.d/2014_hoge_nopass_key.pem;
    ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers  ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS;
    ssl_prefer_server_ciphers   on;


    #
    # Header
    #
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Host $http_host;

    location /demo {
        proxy_pass http://web01.hoge.com;
    }
}

「web01.hoge.com」はhostsファイルで名前解決する。

 

web01のnginx設定

server {
    listen       80;
    server_name  .hoge.com;

    root    /home/httpd/hoge/web;
    index   index.php index.html;
    charset utf-8;


    #
    # Include other config files
    #
    include /etc/nginx/conf.d/common/location_default.conf;
    include /etc/nginx/conf.d/hoge/*.conf;
}

 

hoge/01_demo.conf

#
# Demo
#
location /demo {
    alias  /home/httpd/hoge/web-demo;

    access_log /var/log/nginx/hoge_demo.access.log;
    error_log /var/log/nginx/hoge_demo.error.log;

    # PHP-FPM
    location ~* \.php$ {
        fastcgi_split_path_info ^/demo(.+\.php)(.*)$;
        include /etc/nginx/conf.d/common/fastcgi_php.conf;
    }

    # Rewrite for WordPress
    if (!-e $request_filename) {
        rewrite ^/demo/(.*)$ /demo/index.php?q=$1 last;
    }
}

 

common/fastcgi_php.conf

#
# fastcgi for PHP-FPM
#
fastcgi_pass   127.0.0.1:9000;
fastcgi_index  index.php;
fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
fastcgi_intercept_errors on;
include        fastcgi_params;
fastcgi_buffer_size 1024k;
fastcgi_buffers 4 1024k;
client_max_body_size 5M;

 

common/location_default.conf

#
# Log Off
#
location ~ /robots.txt  { access_log off; log_not_found off; }
location ~ /favicon.ico { access_log off; log_not_found off; }

location ~ /\. {
    deny  all;
    access_log off;
    log_not_found off;
}

 

最後にweb01のWordPressのwp-config.phpに追記する。

/**
* If WordPress is behind reverse proxy which proxies https to http.
*/
if ( !empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' ) {
        $_SERVER['HTTPS'] = 'on';
}

これをしないとweb01から見たらhttpアクセスなのでhttpsにリダイレクトし続ける。

LV01からWeb01のPHP-FPMへ直接処理投げてもいいかもしれない。

 

< Related Posts >

2014年9月19日金曜日

Nginxの設定(Config)変数の値を確認する方法

nginx_debugNginxでリバースプロキシして、バックエンドにあるWordPressを表示するように設定をゴニョゴニョしているときの覚書。

環境: Nginx 1.6.2

php-fpmとの連携を記述するときに「$document_root」の値を確認したい。

add_headerする方法が一番簡単。
# vi /etc/nginx/conf.d/01.wordpress.conf

#
# Demo
#
location /demo {
    root  /home/httpd/hoge/web-demo;
    try_files $uri $uri/ =404;

add_header debug_nginx1 "$document_root";
add_header debug_nginx2 "$fastcgi_script_name";
}

これでブラウザでアクセス。Google Chromeの開発ツールでは「Response Headers」に表示される。

その設定が反映されたか確認するのに便利。

 

< Related Posts >

2014年9月17日水曜日

【Titanium, Android】Notificationからアプリにデータを渡す

notification_tap_android新着プッシュ通知(Notification)をアプリ側に表示して、それを押したら新着記事を表示する方法を調査したときの覚書。

Androidで嵌った。

環境: Titanium SDK 3.3.0.GA

プッシュ通知を受け取るまでは前の記事を参考に。

 

Notificationをタップして発行するIntentにpushExtraでセットした値が取れない。試したこと。

  • Windowの「focus」イベントで渡されたActivityを参照
    Ti.Android.currentActivity.intent.getStringExtra(Ti.Android.EXTRA_TEXT)
  • Windowの「focus」イベントで渡されたActivityを参照。引数のイベントオブジェクトから参照。
    e.source.activity.intent.getStringExtra(Ti.Android.EXTRA_TEXT)
  • 起動後にrequireされた別ファイルでゴニョゴニョしているときに参照。
    Ti.Android.currentActivity.intent.getStringExtra(Ti.Android.EXTRA_TEXT)

どれもダメ。

 

どうやらIntentが渡されるActivityを勘違いしているみたい。

そもそも今起動中のActivityは何?

このサイトを参考に今起動中のActivityを表示。
$ cd /Applications/android-sdk-macosx/platform-tools/
$ ./adb shell dumpsys activity | grep -i run

  Running activities (most recent first):
      Run #3: ActivityRecord{4216fae0 com.hoge.appdemo/org.appcelerator.titanium.TiActivity}
      Run #2: ActivityRecord{426a7da0 com.hoge.appdemo/.HogeAppActivity}
      Run #1: ActivityRecord{41c99bf0 com.sec.android.app.twlauncher/.Launcher}
      Run #0: ActivityRecord{41bb6d98 com.sec.android.app.controlpanel/.activity.JobManagerActivity}
ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)

なぜか2つある。

このアプリはWindow1つだけなので、たぶんアプリとWindowかな?

と思いつつ、(Windowではなく)直接app.jsに書いてみると。。。

// Start the APP
require('/lib/core').init();

// Notification Test
console.log(Ti.Android.currentActivity.getStringExtra(Ti.Android.EXTRA_TEXT));

取れた!

 

どうやらAndroidのActivityに関して理解する必要がありそう。

 

ソースを見たわけではないけれど、Titaniumは起動後にアクティビティを作って、その後win.openすると「org.appcelerator.titanium.TiActivity」を生成するみたい。

まだ曖昧な部分があるけれど、動いたサンプルコードを一部抜粋。

app.js

// win.openする前でもTi.Android.currentActivityを取得可能

// Start the APP
// init関数の中ではNotificationからの値を取得できなかった。
require('/lib/core').init();

// Notification
if (Ti.Platform.osname == 'android') {
    var act = Ti.Android.currentActivity;
    var intent = act.intent;
    console.log(intent.getStringExtra(Ti.Android.EXTRA_TEXT));
    // ここでフラグ設定しておいて、windowのopenイベントで処理する
}

 

notification.js

/**
* This called from require('com.activate.gcm').registerC2dm
*/
function handlePushReceiveAndroid(e) {
    var intent, pending, notification;

    // Show Notification
    intent = Ti.Android.createIntent({
        className: Ti.App.getID() + '.HogeAppActivity',
        flags: Ti.Android.FLAG_ACTIVITY_NEW_TASK | Ti.Android.FLAG_ACTIVITY_CLEAR_TOP
    });
    intent.putExtra(Ti.Android.EXTRA_TEXT, 'test desu');

    // Intent for Click Notification
    pending = Ti.Android.createPendingIntent({
        intent: intent,
        flags: Ti.Android.FLAG_UPDATE_CURRENT
    });

    // Create Notification
    notification = Ti.Android.createNotification({
        contentIntent: pending,
        contentTitle: e.data.message,
        //flags: Ti.Android.FLAG_ONGOING_EVENT | Ti.Android.FLAG_NO_CLEAR
    });

    // Show
    Ti.Android.NotificationManager.notify(1, notification);
}

 

Notificationをタップしたときは常にアプリを起動し直す。本当は起動しているときはintentが更新されたイベントだけキャッチして処理したかった。今度再調査。

サーバーからのデータはputExtraするときにJSON形式で格納しておけばいいと思う。

最終的にはapp.js内のinitの引数にTi.Android.currentActivityを渡して処理することにした。

 

< Related Posts >

2014年9月16日火曜日

TitaniumアプリでPush Notification【Android版】

push-notification_android前回はiOSについて調査。今回はAndroid端末にプッシュ通知する方法を調査したときの覚書。

開発環境: Mac OS X 10.9.4, Titanium SDK 3.3.0.GA, Android 4.0.3
サーバー:CentOS 6.5

 

参考サイト

 

目次

  1. Google Cloud Messaging(GCM)を有効に
  2. GCMとの通信用モジュールを探す
  3. Titaniumモジュールをビルドする環境を整える
  4. Titaniumモジュールのビルド実行
  5. アプリからモジュールを利用
  6. サーバーからプッシュ通知テスト
  7. デバッグと検証してみた

 


1.Google Cloud Messaging(GCM)を有効に

iOSはApple Push Notification service(APNs)経由でプッシュ通知を送る。Androidの場合はGoogle Cloud Messaging(GCM)経由。

GCMのAPI Keyを取得

  1. Google API Consoleにアクセス
  2. プロジェクトを作成。
  3. プロジェクトの「Overview」を開くと左上に「Project Number」が表示されているのでこれをメモ。あとで「sender_id」としてアプリ側に記述する。
  4. APIs & auth → APIsから「Google Cloud Messaging for Android」を有効に
  5. APIs & auth → Credentialsから「Server Key」を作成。

 


2.GCMとの通信用モジュールを探す

iOSみたいにネイティブにサポートしてないので、公開されているモジュールを利用する。

「dist/com.activate.gcm-android-0.6.zip」をそのまま利用しても問題なかった。一応自分でカスタマイズできるようにビルドしてみた。

 

< 2014/12/15 Modified >
こちらの記事で紹介されている方がいろいろ修正されているみたい。

 


3.Titaniumモジュールをビルドする環境を整える

長くなったので別記事にした。

 


4.Titaniumモジュールのビルド実行

GitHubからcloneする
$ git clone https://github.com/liccowee/Google-Cloud-Messaging--Titanium-
$ cd Google-Cloud-Messaging--Titanium-/

環境変数を出力したいので、空のsampleモジュールを作る。
$ titanium create -p android -t module -d ./ -n sample --id com.sample

環境変数が記述されたbuild.propertiesをコピーする。
$ cp sample/android/build.properties ./

ちなみにbuild.propertiesはこんな感じだった。

titanium.platform=/Users/daiki/Library/Application Support/Titanium/mobilesdk/osx/3.3.0.GA/android
android.platform=/Applications/android-sdk-macosx/platforms/android-14
google.apis=/Applications/android-sdk-macosx/add-ons/addon-google_apis-google-14

 

manifestを編集
$ vi ./manifest

version: 0.6
minsdk: 3.3.0.GA

 

< 2015/04/24 Modified >
SDK 3.5.1.GAにしたらarchitecturesを加えないとビルドに失敗して「There is discrepancy between the architectures specified in manifest and compiled binary.」といわれた。

$ vi ./manifest

name: gcmjs
moduleid: net.iamyellow.gcmjs
guid:
platform: android
minsdk: 3.5.1.GA
architectures: armeabi armeabi-v7a x86

 

 

timodule.xmlを編集
$ vi ./timodule.xml

<uses-sdk android:minSdkVersion="10"/>

 

< 2015/04/24 Modified >
SDKをアップデートした場合はbuild.propertiesの値をチェック
$ vi ./build.properties

titanium.platform=/Users/daiki/Library/Application Support/Titanium/mobilesdk/osx/3.5.1.GA/android
android.platform=/Applications/android-sdk-macosx/platforms/android-21
google.apis=/Applications/android-sdk-macosx/add-ons/addon-google_apis-google-21

 

distディレクトリは空にしておく。
$ rm dist/*
$ ant clean

ビルド実行
$ ant

libディレクトリがないって怒られた。
$ mkdir lib

再ビルド
$ ant

distディレクトリに出力される。

 


5.アプリからモジュールを利用

Titaniumプロジェクトの直下に「com.activate.gcm-android-0.6.zip」を設置するだけで、次回ビルド時にインストールしてくれる。

自分で解凍しても同じ。
Titanium_Studio_Workspace/test_app/modules/android/com.activate.gcm

tiapp.xmlを編集。sender_idはGoogle API Consoleの「Project Number」

<modules>
    <module platform="android" version="0.6">com.activate.gcm</module>
</modules>
<property name="com.activate.gcm.sender_id" type="string">123427670900</property>
<property name="com.activate.gcm.component" type="string">com.hoge.app/org.appcelerator.titanium.TiActivity</property>

 

アプリ起動後に下記コードを実行して動作するか確認。

var gcm = require('com.activate.gcm');

gcm.registerC2dm({
    success: function(e) {
        console.log(e);
        alert('Registration ID:' + gcm.getRegistrationId());
    },
    error: function(e) {
        console.log(e);
    },
    callback: function(e) {
        alert('JS message event: ' + JSON.stringify(e.data));
        console.log(e);
    }
});

GCMにデバイスの登録が成功すると「success」プロパティに設定した関数が実行されて、registrationIdが返ってくる。

 


6.サーバーからプッシュ通知テスト

さっき取得したregistrationIdをターゲットにプッシュ通知をサーバーから送信してみる。

こちらを参考にしたPHPを設置。「$registration_id」にはデバイスのregistrationId。「$header」のkeyにはAPI Keyを記述する。

パラメーターの詳細は公式サイトを参考に。

<?php
$url = 'https://android.googleapis.com/gcm/send';

$registration_id = 'APAxxx';

$message = 'Hello, GCM!!';

$header = array(
  'Content-Type: application/json',
  'Authorization: key=xxx',
);
$post_list = array(
  'registration_ids' => array($registration_id),
  'collapse_key'     => 'demo',
  'data'             => array( "message" => $message )
);

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_list));

$ret = curl_exec($ch);
 
var_dump($ret);
?>

これを実行するとアプリ側で「callback」に設定した関数が実行されるはず。

 


7.デバッグと検証してみた

ddmsを立ち上げてログ出力を監視してみる。「C2dmModule」「GCM」というTagで絞り込むと見やすい。

image

どんな仕組みで動いているかまだよく分かっていない。

 

< 2015/04/24 Modified >
アプリのti.xmlにandroid:debuggable="true"を追記するとddmsでそのアプリの出力だけに絞り込むことが出来るので便利。

またtimodule.xmlに
<property name="ti.android.debug" type="bool">true</property>
を追記しないとモジュール内の
org.appcelerator.kroll.common.TiConfig.LOGD
がtrueにならないので、デバッグログを出力しない。

 

 

その他検証して分かったことなど

  • アプリをタスクマネージャーからKILLした時はプッシュ通知を受け取れない。GCMBaseIntentServiceがプッシュ通知に反応しない。
    これはLINEも同じ。これを解決するにはMessengerのように常時稼働するサービスを作る必要がある。
    メモリが圧迫されてアクティビティが破棄されるのはAndroid端末の「開発者向けオプション」→「アクティビティを破棄」をチェックすると再現できる。
  • 画面をロックしている状態だとcom.activate.gcm.componentを正しく設定してないと動かない(たぶん)。下記コマンドで現在稼働中のActivityを表示できる。
    $ ./adb shell dumpsys activity | grep -i run
  • 再起動直後はうまく動いてくれない。ddmsで見るとサービスは動いてプッシュ通知を検知している。要調査。

 

< 2014/12/19 Modified >
アプリがバックグラウンドのときにうまく動かなかったのはgcm.jsとgcm_activity.jsをResourcesの直下に設置してなかったから。

Forkされている方が断然使いやすかった。

 

< Related Posts >

2014年9月12日金曜日

Titaniumモジュールをビルドする環境を整える

titanium-module_buildこのTitaniumモジュールをビルドする環境を作ったときの覚書。

環境: Mac OS X 10.9.4, Titanium SDK 3.3.0.GA

 

参考

 

目次

  1. Android NDKをインストール
  2. Android SDK 2.3.3 (API 10)をインストール
  3. ビルドツールApache Antをインストール

 


1.Android NDKをインストール

TitaniumはNDK Revision 9をサポートしているらしいのでr9dをダウンロードする。
$ cd /Applications/
$ curl -O http://dl.google.com/android/ndk/android-ndk-r9d-darwin-x86_64.tar.bz2
$ tar jxvf android-ndk-r9d-darwin-x86_64.tar.bz2
$ rm android-ndk-r9d-darwin-x86_64.tar.bz2

環境変数を定義
$ vi ~/.profile

# Android NDK
export ANDROID_NDK=/Applications/android-ndk-r9d

確認
$ source ~/.profile
$ echo $ANDROID_NDK;

 


2.Android SDK 2.3.3 (API 10)をインストール

必須らしいので、android-sdk-macos/tools/androidを実行してインストール。

環境変数を定義してあるか確認。
vi ~/.profile

# Android SDK
export ANDROID_SDK=/Applications/android-sdk-macosx

確認
$ source ~/.profile
$ echo $ANDROID_SDK;

環境が整ったか確認
$ titanium setup

Where do you want to go? k

 


3.ビルドツールApache Antをインストール

公式サイトからダウンロード。
$ curl -O http://ftp.riken.jp/net/apache//ant/binaries/apache-ant-1.9.4-bin.tar.gz
$ tar -zxvf apache-ant-1.9.4-bin.tar.gz
$ rm apache-ant-1.9.4-bin.tar.gz

パスを通す
$ vi ~/.profile

# Apache Ant
export PATH=$PATH:/Applications/apache-ant-1.9.4/bin

確認
$ source ~/.profile
$ echo $PATH;

ちなみにgperfはMacの場合Xcodeを付随してインストールされる。

 

< Related Posts >

2014年9月3日水曜日

Titaniumアプリでプッシュ通知(Push Notification)【iOS版】

push-notification_iosアプリ側にプッシュ通知しようと調査したときの覚書。まずはiOSから。

開発環境: Mac OS X 10.9.4, Titanium SDK 3.3.0.GA
サーバー:CentOS 6.5, PHP 5.4.32

 

参考サイト

 

目次

  1. APNsとの通信下準備。証明書署名要求(CSR)を作成
  2. iOS Dev CenterでPush Notification可能なApp IDを作成
  3. App IDと証明書署名要求を提出して証明書を取得
  4. Linuxサーバーで使いやすいようにPEMフォーマットに変換
  5. プッシュ通知を受け取るデバイス用にProvisioning Profile作成
  6. アプリにプッシュ通知がきたときの動作を記述
  7. サーバーから通知テスト

 


1.APNsとの通信下準備。証明書署名要求(CSR)を作成

iOSの場合はApple Push Notification service(APNs) へプッシュ通知したい情報を送ると各端末に自動でプッシュしてくれる。

APNsから信頼してもらうために署名要求を提出して、署名(サイン)してもらった証明書を取得する必要がある。

まずはMac上で証明書署名要求(CSR)を作る。

  1. Keychain Accessを起動
  2. Keychan Access → Certificate Assistant → Request a Certificate From a Certificate Authority...を選択
  3. 「User Email Address」「Common Name」を入力して「Saved to disk」を選択してContinue.
  4. パスフレーズを入力

実際はLinuxサーバーからAPNsにアクセスするので、証明書署名要求(CSR)を作成する過程で作られる秘密鍵をエクスポートしておく。

Keychain Accessの「Keys」を見ると、さっき作ったCommon Nameで秘密鍵(private key)があるはず。

  1. 右クリックしてExportを選択
  2. 「.p12」が選択されていることを確認してSave.
  3. パスワードを入力

このp12ファイルはサーバーに置いておく。

 


2.iOS Dev CenterでPush Notification可能なApp IDを作成

Xcodeが自動生成する「Xcode iOS Wildcard App ID」はPush NotificationできないのでApp IDを新規作成する。

iOS Dev CenterからProvisioning Profile → Certificates, Identifiers & Profiles → Identifiersを選択。

  1. 「+」をクリック
  2. 「Push Notifications」をチェックしてContinue.

 


3.App IDと証明書署名要求を提出して証明書を取得

iOS Dev CenterのCertificatesを選択。

  1. 「+」で新規作成。
  2. 開発用の場合は「Apple Push Notification service SSL (Sandbox)」を選択
  3. App IDはさっき作ったのを選択
  4. CSRも作ったのを選択
  5. Continueするとすぐに証明書が発行される。

発行された証明書をダウンロードして保存。

 


4.Linuxサーバーで使用するためにPEMフォーマットに変換

プッシュ通知するサーバー(CentOS 6.5)にファイルを転送してPEMファイルを作る。

# cd apns/

証明書をPEMに変換
# openssl x509 -in aps_development.cer -inform der -out 2014hoge_cer.pem

秘密鍵をPEMに変換
# openssl pkcs12 -nocerts -in hoge.p12 -out hoge_key.pem

p12を作成したときのパスワードと新しくパスフレーズを入力

パスフレーズを解除
# openssl rsa -in hoge_key.pem -out hoge_key-nopass.pem

2つのファイルを結合
# cat 2014hoge_cer.pem hoge_key-nopass.pem > apns.pem

 


5.プッシュ通知を受け取るデバイス用にProvisioning Profile作成

iOS Dev CenterのProvisioning Profilesから

  1. 「+」で新規作成
  2. 開発用なら「iOS App Development」
  3. さっき作ったApp IDを選択
  4. アプリ用証明書を選択
    ※さっき作ったのはAPNs用証明書なのでここでは表示されない。
  5. デバイス選択
  6. 分かりやすい名前で登録

ダウンロードしなくても、Xcodeを開いて
Xcode → Preferences... → Accounts → View Details...
で更新すると同期される。

 


6.アプリにプッシュ通知がきたときの動作を記述

Titaniumの公式サイトを参考にアプリを起動したときに下記コードを実行する。

Ti.Network.registerForPushNotifications({
    // Specifies which notifications to receive
    types: [
        Ti.Network.NOTIFICATION_TYPE_BADGE,
        Ti.Network.NOTIFICATION_TYPE_ALERT,
        Ti.Network.NOTIFICATION_TYPE_SOUND
        ],
    success: function(e) {
        alert('Success: ' + JSON.stringify(e));
        console.log(e);
    },
    error: function(e) {
        console.log(e);
    },
    callback: function(e) {
        alert('Received push: ' + JSON.stringify(e));
        console.log(e);
    }
});

実機デバイスでアプリを実行すると、「プッシュ通知を許可しますか?」と聞いてくる。許可すると「success」プロパティにセットした関数が実行されてデバイストークンを取得できる。

プッシュ通知するにはこのデバイストークンを指定する必要があるらしい。

つまり全ての端末にプッシュ通知するには、全てのデバイストークンをループする必要がある。

 


7.サーバーから通知テスト

さっき取得したデバイストークン向けにプッシュ通知してみる。

PHPでは、ApnsPHPが簡単で便利。

ダウンロードしたsample_push.phpを参考に。

require_once './ApnsPHP/Autoload.php';
$push = new ApnsPHP_Push(
    ApnsPHP_Abstract::ENVIRONMENT_SANDBOX,
    './apns.pem'
);

$push->connect();
$message = new ApnsPHP_Message('デバイストークン');
$message->setText('Hello APNs-enabled device!');
$message->setSound();
$message->setExpiry(30);
$push->add($message);
$push->send();
$push->disconnect();

これをサーバー上で実行するとさっき書いた「callback」プロパティの関数が実行されるはず。

 

次回はAndroid端末向けにプッシュ通知する方法を調査する。

 

< Related Posts >

Related Posts Plugin for WordPress, Blogger...

Blog Archives