PHPでXML-RPCサーバを作るには、IXRのライブラリが簡単です。
PEAR::XML_RPC2も良いですが、こっちの方がCakePHPで使うには良さそうですね。
以下のページで、CakePHPでの使い方もまとまっています。
http://bakery.cakephp.org/articles/view/how-to-create-an-xml-rpc-server-with-cakephp
基本的に、インデックスアクションでレシーバを登録するだけで、後は各アクションでPHPのオブジェクトをreturnできます。
あまりに簡単すぎて、基本的なことを調べるのがめんどくさくなります。
たとえば、base64やdateTimeで囲んだ値を返したいときはどうしたら良いでしょうか?
このようにやればOKです。
function _hogeAction($data) {
return new IXR_Base64($bytedata);
}
function _piyoAction($data) {
return new IXR_Date(time());
}
Base64は、内部でbase64_encodeが呼ばれるので、バイトデータを直接渡すようにします。
dateは、タイムスタンプ値かISO形式を渡します。
dateのパースは、あまり柔軟性は無いので気をつけてください。
function parseIso($iso) {
$this->year = substr($iso, 0, 4);
$this->month = substr($iso, 4, 2);
$this->day = substr($iso, 6, 2);
$this->hour = substr($iso, 9, 2);
$this->minute = substr($iso, 12, 2);
$this->second = substr($iso, 15, 2);
}
WordPressはXML-RPC対応で、地味に色々な対応プログラムが出ていますね。
XML-RPCのライブラリを使ってAPIをたたけば、たいていの操作はできますが、
お手軽に使うならライブラリを使いたいです。
PHPのライブラリでは、↓のものがシンプルにまとまっていて使いやすかったです。
http://sourceforge.jp/projects/wpapiclient1/
ただし、WordPress 2.9.2 + PHP5.3 で試したところ、
日付がおかしくなる(1999年11月になってしまう)問題が発生したため、以下のように修正しました。
wordpress/core/Util.class.php
static function createDateTime($timestamp)
{
$timestamp = date('Ym\TH:i:s', $timestamp); //'c' を 'Ymd\TH:i:s' に置換する
xmlrpc_set_type($timestamp, 'datetime');
return $timestamp;
}
date()の結果の違いが原因です。
‘c’ → 20100510T10:00:00+09:00
‘Ymd\TH:i:s’ → 20100510T10:00:00
このように、「c」だとタイムゾーンを表す値が入ってしまいます。
date関数
しかし、XML-RPC で dateTime.iso8601 形式を使う場合、タイムゾーンの指定はできない仕様のため、不正な日時と見なされてしまうようです。
Ymd\TH:i:s のように手動で指定することで、正しいフォーマットに指定できますね。
XML-RPCで、サーバ側にhttpsを使用する場合、クライアント側でcURLがSSLを認証できないとエラーになることがあります。
これは、cURLにSSLの証明書が入っていないのが原因です。
(Curl returned non-null errno 60:SSL certificate problem, verify that the CA cert is OK.
Details:error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed)
そこで、証明書のチェックを無効化します。
生PHPでcurlをたたく場合、以下のようにします。
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
PEAR::XML_RPC2を使う場合、以下のように、’sslverify’をfalseに指定します。
$options = array('debug' => false, 'sslverify' => false);
$url = "http://example.com/xmlrpc.php";
$this->client = XML_RPC2_Client::create($api, $options);
Ethnaは標準でXML-RPCに対応しているので、
# ethna add-action-xmlrpc hoge
とやるだけで app/action-xmlrpc にActionのテンプレートが作れますし、クライアントからHogeメソッドをコールすると、自動的にHogeアクションが実行されます。
(エントリポイントはindex.phpではなくxmlrpc.phpになります)
しかし、デフォルトでは引数がActionFormのform定義と順番に1対1で対応するため、引数が多くなると使いづらくなります。
ここは、PHPらしく引数名をキーにした連想配列にしたいところ。
(プロジェクト名)_Controller に以下のメソッドを定義(オーバーライド)することで、XMLRPCの第一引数を連想配列として扱い、対応する名前のformに値がセットされるようになります。
function trigger_XMLRPC($method, $param)
{
// アクション定義の取得
$action_obj =& $this->_getAction($method);
if (is_null($action_obj)) {
return Ethna::raiseError("undefined xmlrpc method [%s]", E_APP_UNDEFINED_ACTION, $method);
}
// オブジェクト生成
$backend =& $this->getBackend();
$form_name = $this->getActionFormName($method);
$this->action_form =& new $form_name($this);
$def = $this->action_form->getDef();
/***** ここから変更 *****/
if (isset($param[0]) && is_array($param[0]))
{
foreach ($param[0] as $key => $value)
{
if (isset($def[$key]))
{
$this->action_form->set($key, $value);
}
}
}
/***** ここまで変更 *****/
// バックエンド処理実行
$backend->setActionForm($this->action_form);
$session =& $this->getSession();
$session->restore();
$r = $backend->perform($method);
return $r;
}
これで、クライアント側では引数の順番を気にせずに済みます。
PHPでXML-RPCを使った記録です。
PEAR::XML_RPC2を使うことにしました。PHP5専用で使いやすそうです。
が必須です。
クライアント側では、
require_once './XML/RPC2/Client.php';
$options = array('debug' => true);
$client = XML_RPC2_Client::create('http://www.bpsinc.jp/xmlrpc.php', $options); //URLはダミー
try {
$result = $client->Version(); //関数名はサーバ側で定義したもの
print_r($result);
} catch (XML_RPC2_FaultException $e) {
var_dump($e);
} catch (Exception $e) {
var_dump($e);
}
のように使えます。
しかし、これだとなぜか結果が全部NULLになることがあります。
debugメッセージを見ると、Server Responseは正常なのに、Decoded ResultがNULLになっているように見えます。
この問題、Server側かHttpRequestの実装の問題だと思うのですが、Server ResponseのBodyの最初に改行が入っているのが原因でした。
"\n<?xml version="1.0"?><methodResponse> …"
PEAR/XML/RPC2/Backend/Xmlrpcext/Client.php の112行目付近で、
$result = xmlrpc_decode($body, $this->encoding);
となっているところを、
$result = xmlrpc_decode(trim($body), $this->encoding);
とすれば直ります。(マルチバイト対応のtrimを書いた方が良いかもしれません)
※XMLRPCEXT拡張モジュールが入っていない場合は、PEAR/XML/RPC2/Backend/PHP/Client.phpになります。
ライブラリを書き換えるのは強引ですが、xmlrpc_decode関数自体がEXPERIMENTALのままですし、あとはHttpRequestやサーバ側を変更しなければ行けないので、スマートな解決方法は思いつきません。
とりあえずresultがnullになる問題は回避できました。