ラベル PHP の投稿を表示しています。 すべての投稿を表示
ラベル PHP の投稿を表示しています。 すべての投稿を表示

2010-04-17

OAuth Request Body HashをつかうためにOAuthクライアントを実装してみた時のメモ

OAuth Request Body Hashとは、OAuthの拡張仕様の一つです。
OAuth Coreではapplication/x-www-form-urlencodedなリクエストに対してのみ正当性を保証します。そのためPOSTでXMLを送信したいときなどには別の手段が必要になります。OAuth Request Body Hashは、そのようなnon-form-encodedなリクエストボディに対する正当性を保証するための仕組みです。
詳細は下記をどうぞ。
http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
(oauth body hashでググるとドラフトがトップに出るようですが(2010-04-17現在)、Finalバージョンはすでにあがっているようです)

YahooのTech Blogも参考になります。
http://techblog.yahoo.co.jp/web/openid/oauth_1/


このoauth_body_hashなのですが、日本語の情報が少なく、今のところライブラリで対応しているものがあまり無いようなので自分で実装してみました。
せっかくなので2-legged OAuthでoauth_body_hashを使う場合のサンプルコードをさらしておきます。
<?php

$consumer_key = "consumer_key";
$consumer_secret = "consumer_secret";
$version = "1.0";
$timestamp = time();
$nonce = (string)rand();
$signature_method = "HMAC-SHA1";
$url = "http://example.com/";
$request_body = '<?xml version="1.0" encoding="utf-8"?><foo>bar</foo>';

$body_hash = urlencode(base64_encode(hash("SHA1", $request_body, true)));

$params = sprintf('oauth_body_hash=%s&oauth_consumer_key=%s&oauth_nonce=%s&oauth_signature_method=%s&oauth_timestamp=%d&oauth_version=%s',
                  $body_hash, $consumer_key, $nonce, $signature_method, $timestamp, $version);
$sbs = sprintf('POST&%s&%s', urlencode($url), urlencode($params));
$signature = urlencode(base64_encode(hash_hmac("SHA1", $sbs, $consumer_secret."&",  true)));
$auth_header = sprintf('OAuth oauth_body_hash="%s",oauth_consumer_key="%s",oauth_version="%s",oauth_timestamp="%d",oauth_nonce="%s",oauth_signature_method="%s",oauth_signature="%s',
                       $body_hash, $consumer_key, $version, $timestamp, $nonce, $signature_method, $signature);

$headers = array("Host: example.com",
                 "Accept: */*",
                 "Authorization: $auth_header",
                 "Content-Type: text/xml; charset=utf-8",
                 "Content-Length: ".strlen($request_body));

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $request_body);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_exec($ch);
curl_close($ch);


なお、PHPに限らずOAuthを自分で実装する時の注意点としては以下のような感じでしょうか

* oauth_body_hashの値はurlencodeした値です。signatureを計算する時はsignature base stringに組み込んだあともう一度urlencodeします。
* oauth_body_hashの計算に秘密鍵は必要ありません。つまりHMACではなく、単なるダイジェスト値です。
* base64 encodeの実装によっては末尾に改行を付加するものがありますが、改行は不要です。(PHPの場合は上記のサンプルと通りでOKですが、Javaのcom.sun.mail.util.BASE64EncoderStreamなどでは改行を付加するものがあるようです)
* timestampは秒までの整数値です。当然と言えば当然なのですが、なにも知らずに時間取得関数を使うとミリ秒まで返してくるものがあるので、はまると気づきにくいかもしれません。
* 2-legged OAuthの場合はaccess tokenが不要ですので、signatureを計算する時の秘密鍵はconsumer_secretに&をつけたものになります。
* signature base stringをつくるときのパラメータはアルファベット順にソートする必要があります。
* signature base string自体はurlencodeする必要はありません。urlencodeするのはURLとパラメータです。



参考:http://oauth.net/core/1.0/

2010-03-08

namespaceつきのPHP extensionをつくる

仕様についてはともかく、php 5.3 からnamespaceが使えるようになりました。
今のところPHPのソースに標準でついてるエクステンションでnamespaceを使っているものは無いようだったのでちょっと試してみました。


通常のnamespaceなしの場合との違いは一カ所だけです
zend_function_entry msgpack_functions[] = {
//    PHP_FE(hoge, NULL)  /* 通常の場合 */
    ZEND_NS_FE("myspace", hoge, NULL)
    {NULL, NULL, NULL}  /* Must be the last line in msgpack_functions[] */
};

PHP_FEの代わりにZEND_NS_FEを使うだけです。
他はそのままでOKです。

ソースファイル
PHP_FUNCTION(hoge)
{
    RETVAL_STRINGL("hoge", 5, 1);
}

ヘッダファイル
PHP_FUNCTION(hoge);


サンプル
<?php
print myspace\hoge();PHP_FUNCTION(hoge);

実行結果
$ php sample.php
hoge

ざっと見たところ、今のところエクステンションでクラスをnamespaceに入れるインターフェースは用意されてないようです。(自分でマクロを書けばできなくはなさそう)
また、一つのエクステンション内でnamespaceは複数つかえますが、namespaceが別でも同名の関数を定義するのは普通の方法では難しそうです。

2009-12-07

MessagePack for PHP のベンチマーク測定

php_msgpackのベンチマーク測定を行いました。
比較対象は、PHP serializeとJSONです。

ベンチマークには以下のようなコードを用いました。

class MyBench
{
  private $packer;
  private $unpacker;
  private $name;
  private $time = 0;
  private $size = 0;
  private $debug = 0;

  function __construct($name, $packer, $unpacker)
  {
    $this->name = $name;
    $this->packer = $packer;
    $this->unpacker = $unpacker;
  }

  public function init()
  {
    $this->size = $this->time = 0;
  }

  public function exec($var)
  {
    $packed = $this->exec_pack($var);
    $unpacked = $this->exec_unpack($packed);
    if ($this->debug) {
      var_dump(bin2hex($packed));
      var_dump($unpacked);
    }
  }

  public function exec_pack($var)
  {
    $func = $this->packer;
    $t = microtime(true);
    $packed = $func($var);
    $this->time += microtime(true) - $t;
    $this->size += strlen($packed);
    return $packed;
  }

  public function exec_unpack($var)
  {
    $func = $this->unpacker;
    $t = microtime(true);
    $unpacked = $func($var);
    $this->time += microtime(true) - $t;
    return $unpacked;
  }

  public function dump_result($msg = "")
  {
    printf("% 25s ", $msg);
    print("$this->time sec, $this->size byte\n");
  }

  public function bench_int($num = 10000)
  {
    $this->init();
    for ($i = 0; $i < $num; $i++) {
      $this->exec($i);
    }
    $this->dump_result("$this->name (int):");
  }

  public function bench_float($num = 10000)
  {
    $this->init();
    for ($i = 0; $i < $num; $i++) {
      $this->exec((float)($i / 23));
    }
    $this->dump_result("$this->name (float):");
  }

  public function bench_string($num = 10000)
  {
    $this->init();
    $str = "";
    for ($i = 0; $i < $num; $i++) {
      $this->exec($str);
      $str .= "$i";
    }
    $this->dump_result("$this->name (string):");
  }

  public function bench_array($num = 1000)
  {
    $this->init();
    $arr = array();
    for ($i = 0; $i < $num; $i++) {
      $this->exec($arr);
      $arr []= $i;
    }
    $this->dump_result("$this->name (array):");
  }

  public function bench_map($num = 1000)
  {
    $this->init();
    $arr = array();
    for ($i = 0; $i < $num; $i++) {
      $this->exec($arr);
      $arr["k$i"] = "v$i";
    }
    $this->dump_result("$this->name (map):");
  }

  public function bench_all()
  {
    $this->bench_int();
    $this->bench_float();
    $this->bench_string();
    $this->bench_array();
    $this->bench_map();
  }
}

$packers = array(array("MessagePack", "msgpack_pack", "msgpack_unpack"),
                 array("PHP serialize", "serialize", "unserialize"),
                 array("JSON", "json_encode", "json_decode")
                 );


foreach ($packers as $p) {
  $obj = new MyBench($p[0], $p[1], $p[2]);
  $obj->bench_all();
}

実行結果は若干見づらかったので、結果を表にまとめると以下のようになりました。


 (注1.数字の単位は秒です。シリアライズ後のサイズは省略しました。
注2.便宜上、キーが文字列の配列のことをmapと書いてます。)

全体的に見るとMessagePackが優秀ですが、型によってはJSONやPHP Serializeの方が速いものもありました。
特に、文字列型ではJSONが異常に遅い一方で、PHP Serializeは健闘しています。


ちなみにMessagePackのシリアライズだけの時間を見てみると、 以下のようになりました。
       MessagePack (int): 0.0383882522583 sec, 29616 byte
     MessagePack (float): 0.0395138263702 sec, 90000 byte
    MessagePack (string): 0.187726020813 sec, 189415563 byte
     MessagePack (array): 0.0688591003418 sec, 2314272 byte
       MessagePack (map): 0.0919380187988 sec, 5287678 byte

int, float, stringに関しては大体半分くらいの時間ですが、どうやらarray のデシリアライズに異常に時間がかかっていることがわかりました。
時間があれば詳しく調べてみようと思いますが、たぶん、ひとつの配列要素を読むごとにzendオブジェクトをallocしてしまっているのが原因だと思われます。

また、takei-hさんのバージョンでも同じようにしてシリアライズの時間を測定してみました。
        MessagePack (int): 0.0359139442444 sec, 29616 byte
     MessagePack (float): 0.0392725467682 sec, 90000 byte
    MessagePack (string): 0.229011297226 sec, 189415563 byte
     MessagePack (array): 0.0804646015167 sec, 1158620 byte
       MessagePack (map): 0.0922095775604 sec, 4788178 byte

結果としては微妙に遅くなっていますがあまり大きな差はありませんでした。
配列をあらかじめ調べてmapかarrayか判断する、という処理は意外と軽いということがわかりました。
配列を長くすれば少し遅くなるのですが、すべてmap型でシリアライズしてしまった場合でもデータサイズが大きくなってその分オーバーヘッドが生じるので、デシリアライズも含めて考えると結果的にはあらかじめ調べておいた方が得になりそうです。

2009-12-01

MessagePack for PHP のarrayの扱い方について

php-msgpackでのarrayの扱い方については昨日はさらっと流してしまいましたが、arrayを実装するにしてもいろいろ方法があって悩ましいところなので、とりあえず保留しておきたかった、というのが正直なところです。

PHPでのarrayの扱い方として考えられるのは大きく分けて以下の3つくらいだと思います
  1. 不正なデータとして捨てる
  2. キーを調べてarrayかmapを判断する
  3. ユーザが指定できるようにする。

PHPだけでの使用を前提に考えると1が一番効率よいのですが、Cで書かれたサーバなどと通信するとなると不都合です。

2の方法はtake-hさんが採用されているようです。
http://tinyurl.com/yh6wgg4
この方法も考えては見たのですが、どうしてもパフォーマンスが犠牲になってしまうのであんまり実装する気は起きませんでした。

というわけで3の方法が使えるようにしたバージョンを作りました。
http://sui2.is-a-geek.net/trac/raw-attachment/wiki/php_msgpack/php5-msgpack-0.1.1.tar.gz

msgpackオブジェクトにオプションを指定することで、arrayを使うかどうかを選択できます。
$obj = new Msgpack;
$data = array("A", "B", "C");

$obj->setopt(MSGPACK_ENABLE_ARRAY, true);
var_dump(bin2hex($obj->packer($data)));

$obj->setopt(MSGPACK_ENABLE_ARRAY, false);
var_dump(bin2hex($obj->packer($data)));

 これを実行すると以下のようになります
string(14) "93a141a142a143"
string(20) "8300a14101a14202a143"

 unpackするときも同様です。
$data = pack("c*", 0x93, 0xa1, 0x41, 0xa1, 0x42, 0xa1, 0x43);

$obj->setopt(MSGPACK_ENABLE_ARRAY, true);
$obj->feed($data);
while ($obj->remain()) {
  var_dump($obj->unpacker(NULL));
}
$obj->setopt(MSGPACK_ENABLE_ARRAY, false);
$obj->feed($data);
while ($obj->remain()) {
  var_dump($obj->unpacker(NULL));
}
 実行結果
array(3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "B"
  [2]=>
  string(1) "C"
}
NULL

このようにユーザに適宜オプションを設定してもらうことで、2の場合程パフォーマンスを犠牲にせずにarrayをつかえますが、arraytとmapが入れ子になった場合には対応できません。


結局はオプションをいくつか用意してユーザが1~3を選べるようにするのが無難かなと思います。

2009-11-30

MessagePackのPHP Extensionを作りました

MessagePack は、id:viverさんが開発されたバイナリシリアライズ形式です。
http://d.hatena.ne.jp/viver/
http://msgpack.sourceforge.jp/

すでに、perl, ruby, pythonでは使えるようになっていますが、PHPだけはまだ無いようだったので作ってみました。

ソースはとりあえず以下からダウンロードできます。
http://sui2.is-a-geek.net/trac/raw-attachment/wiki/php_msgpack/php5-msgpack-0.1.0.tar.gz

python版のソースをベースにPHPに移植した感じになってます。


ビルドにはphpの開発環境が必要です。
Debian系のディストリビューションならphp5-devパッケージを入れれば必要なものは入ると思います。そのほかのUnix系OSでも、PHPのソースをダウンロードすれば必要なスクリプトは付属しています。

ソースをダウンロードして展開したら、以下のコマンドでインストールできます。
$ phpize
$ ./configure
$ make
$ sudo make install

make できたらPHPの.iniファイルに以下の行を追加します。
extension=msgpack.so
Debianなら/etc/php5/apache2/conf.d/や/etc/php5/cli/conf.d/以下にmsgpack.iniを配置してもOKです。


使い方はpython版と大体同じです。
$data = array(1, -1, true, false, null, "key" => "value");

// simple procedural usage
$serialized = msgpack_pack($data);
var_dump(msgpack_unpack($serialized));

オブジェクト指向型の呼び出し方法もサポートしています。
// OO usage
$obj = new Msgpack;
$serialized = $obj->packer($data);
var_dump($obj->unpacker($serialized));

Msgpackオブジェクトを使えば複数のオブジェクトを連続してデシリアライズできます。
// stream deserialize
$buf = pack("c*", 0x00, 0x40, 0x7f, 0xe0, 0xf0, 0xff);
$obj->feed($buf);
while ($obj->remain()) {
  var_dump($obj->unpacker(NULL));
}

他の言語のMessagePackと少し違うのは、array型を使用していない点です。
PHPは配列は実際には順番付けされたマップなので、 すべてmap型でシリアライズするように実装してます。
その分少しデータ量が増えてしまいますが、そこはあきらめました。

ベンチマークは後日調べてみる予定です。

ZenBackWidget