PUSHERでWebhookを行ってみる

PUSHERWebHookができるという記事がブログで紹介されてますよ!と会社で教えてもらったので、試してみました。
先日試してみたwebsocket版の通知とは異なってWebHookなのでユーザが設定する任意のURLに対してPUSHリクエストがあったことをHTTPのPOSTで通知する仕組みになります。
(下の図の赤字で書いたWeb Hookの部分)
f:id:hideack:20120205220755p:image
ですので、PUSH通知できる対象はブラウザ等では無く、Webサーバ上で稼働する任意のアプリケーションが通知対象となります。

利用方法はとても簡単でPUSHERのコントロールパネルにWebHooksという項目があるので選択をするとWebHookを有効にするためのチェックボックスとフックする先のURLを指定する欄があるのでURLを指定します。
f:id:hideack:20120205221444p:image

そうすると、PUSHERのAPIを通してWebアプリケーションからPUSH通知が走った場合に、設定したURLへ対してPOSTリクエストが行われます。(同時にWebsocketで接続中のWebブラウザへも通知が行われる)
こうすることによって、フック先のWebアプリケーション側でもPUSH通知が走ったことを検出することができます。
PUSH通知をされた側のWebアプリケーションは、このままでは本当に真のPUSH通知元(=PUSHER)からのPOSTリクエストかどうかを判定することができませんが、PUSHERから通知されたリクエストには、

  • X-Pusher-Key
  • X-Pusher-Signature

という二つのHTTPヘッダが付いていて、PUSH通知内容(POSTリクエストの内容)に対するHMAC SHA256が付いているので、この内容がPUSHER APIの秘密鍵から作られるSHA256のメッセージダイジェストの内容と一致するかを確認することでPUSHERから通知されたことを確認することができます。

以下、ドキュメントに掲載されているサンプルのままですが、読めばシンプルで分かりやすいと思います。

<?php
$app_key = $_SERVER['X-Pusher-Key'];
$webhook_signature = $_SERVER['X-Pusher-Signature'];
$body = http_get_request_body();
$expected_signature = hash_hmac( 'sha256', $body, $app_secret, false );
if($webhook_signature == $expected_signature) {
$payload = json_decode($body);
foreach($payload['events'] as &$event) {
// do something with the event
}
header("Status: 200 OK");
}
else {
header("Status: 401 Not authenticated");
}
?>

参考

Pusher WebHooks Documentation | Pusher
http://pusher.com/docs/webhooks

PUSHERを使ってWebsocket経由のPush通知を行ってみる

Websocket周りを使ってみたいと思っていろいろ試行錯誤をしていたのですが、PUSHERというAPIサービスを利用すると思いの外容易にWebsocketを既存のWebサービスに使えそうなので使ってみました。
(激しい一週遅れ感がありますが…。)

例えば既存のWebアプリにpush実装を行いたいために、通常は稼働中のWebサービスとは別にWebsocketサーバの実装が必要となるのですが、PUSHERを利用するとPush通知を行う部分(Websocketサーバ→ブラウザ間)の実装を代替させることができます。
Push通知を行わせたい部分でWeb側の実装でPUSHERAPIを叩くことで、ブラウザへのWebsocketを通したPushを行わせることができるイメージになっています。
Websocketの接続数が最大20までで1日あたりのメッセージ発行数が10万回以内であれば無料で利用することができます。

f:id:hideack:20120129160326p:image

利用方法

1. アカウントを作成する(無償)

PUSHER(http://pusher.com/ ) のトップページでアカウントが作成できるのでアカウントを作成します。(メールアドレスが必要)

f:id:hideack:20120129151530j:image

2. APIキーを取得する

アカウントを作成するとAPIを利用するためのAPIキーが1つ表示されるので、表示されているアプリケーションID(app_id), APIアクセス用のkeyとtokenを控えます。

3. クライアント側の実装 (Webサーバから渡されるHTMLの実装)

Webアプリ側がWebsocketを通じたPush通知を受けるための実装を行います。
サンプルは、先程のAPIキーが表示される画面(API access)の中段に"Pastable HTML code"として例が掲載されているので、参考にして既存のWebアプリに組み込みます。(PUSHERの動作を確認したければ、そのままコピーすればOK)
以下の様な具合。

<html><head>
  <title>Pusher Test</title>
  <script src="http://js.pusher.com/1.11/pusher.min.js" type="text/javascript"></script>
  <script type="text/javascript">
    // Enable pusher logging - don't include this in production
    Pusher.log = function(message) {
      if (window.console && window.console.log) window.console.log(message);
    };
    // Flash fallback logging - don't include this in production
    WEB_SOCKET_DEBUG = true;
    var pusher = new Pusher('****'); // ←API取得時に控えたAPI keyを書く。
    var channel = pusher.subscribe('test_channel');
    channel.bind('test_event', function(data) {
      alert(data);
    });
  </script></head><body>
<p>pusher API test</p></body></html>

4. Push通知側の実装

Push通知側の実装(=PUSHERのREST APIを叩く)は、PUSHERでRESTのAPIを叩くためのライブラリ(http://pusher.com/docs/rest_libraries ) が各種言語用に用意されているので、これを利用すれば簡単に実装することができます。
以下、RubyPHPの場合の例。
PHPの場合は、

<?php
require('../lib/php/Pusher.php');
require('pusher-setting.php');          // pusher settings
$pusher = new Pusher(PUSHER_KEY, PUSHER_SECRET, PUSHER_APPID);
$pusher->trigger('test_channel', 'test_event', 'hello pussher!!');
printf("trigger called.");

Rubyの場合は、

require 'rubygems'
require 'pusher'
require 'eventmachine'
require 'em-http-request'
require './pusher-setting.rb'
Pusher.app_id = PUSHER_APPID
Pusher.key = PUSHER_ID
Pusher.secret = PUSHER_SECRET
EM.run {
deferrable = Pusher['test_channel'].trigger_async('test_event', 'Hello, pussher!! via Ruby')
deferrable.callback {
puts "success!"
EM.stop
}
deferrable.errback { |error|
puts error
EM.stop
}
}

となりますね。

5. 動作確認

3.で作成したHTMLをブラウザで読み込んだ状態で、4.で作成したPush通知(呼び出し側)のプログラムを実行すると、
f:id:hideack:20120129155411p:image

といった具合でPush通知されるのが確認できると思います。

6. APIの利用状況の確認

PUSHERにログインすればAPIの呼び出し状況を確認可能です。
f:id:hideack:20120129160518p:image

なかなか簡単に利用することができるので、早速試してみたいな…。

Rubyでmemcachedを使う

REMPAPI周りでキャッシュを使いたくなったので、memcachedを使おうと思ったのだけどRubyで使う場合のクライアントを知らなかったので簡単にまとめ。
Ruby用のmemcacheクライアントはいくつかある様なのだけど、dalliというクライアントが一般的な様なので使ってみた。

インストールは簡単。gemコマンドでインストールできる。

$ sudo gem install dalli
Fetching: dalli-1.1.4.gem (100%)
Successfully installed dalli-1.1.4
1 gem installed
Installing ri documentation for dalli-1.1.4...
Installing RDoc documentation for dalli-1.1.4...

使い方も比較的シンプル。
irbで試すと以下の様な形で利用できる。
(localhostの11211番ポートでmemcachedサーバが常駐している状態)

irb(main):001:0> require 'dalli'
=> true
irb(main):002:0> dc = Dalli::Client.new('localhost:11211')
=> #0}, @ring=nil>
irb(main):003:0> dc.set("hoge", "foo")
=> true
irb(main):004:0> dc.get("hoge")
=> "foo"
irb(main):005:0> dc.get("hoge2")
=> nil

REMPは、sinatraで稼働させているのでその場合は以下の様な書き方をしておけばよいかと思う。

# (中略)
configure do
cache = Dalli::Client.new('localhost:11211', :expires_in => 3600 * 24)  # キャッシュ寿命24時間
set :cache, cache
end
get '/foo' do
tmp = setting.cache.get(params['id'])
val = 0
if tmp.nil?
val = db.getValue()   # DBからデータ取得
else
val = tmp  # memcachedのキャッシュを利用
end
val
end

意外と手軽にできたな。
ちょっとRubyのmemcachedクライアントがいくつかあったので迷ったけど、これで大丈夫そう。

REMP(レンプ)開発に参加してますよ

そういえば、REMP(レンプ)というYoutube動画を連続再生できるWebアプリの開発に参加してますー*1

f:id:hideack:20111211132100p:image

Mashup Award 7 にも応募したりしましたよ。
動作環境はgoogle chromeがインストールされているPCでfacebookアカウントがあれば利用することができます。

REMPの特徴

  • Youtubeの動画で自分が好きなものをリストにして登録できます
    • 手順としては、動画検索→自分が好きな動画をリストに登録
  • 作成したプレイリストを連続再生したり、繰り返し再生できます
    • REMPのコア機能。かなり便利。
  • facebookで繋がっている友達と自分が作ったプレイリストをシェアできます
    • 難しくなくて単に友達は自分のプレイリストを見ることができるということです
  • facebookで繋がっている友達に自分が気に入った動画を紹介することができます

詳しくは紹介ページ http://remp.rockf.es/hello/ を参照。
難しい説明はさておいて、使ってもらうとわかりやすいかと思います!

いきさつなど

きっかけは、会社の開発合宿(ペパボお産合宿)で一緒のチームになった@Chromeアプリ版のREMPを紹介してもらって、そのときにWebアプリとしてtwitterfacebookの連携機能を作りたい。という話を聞いたのが始まり。
じゃ、facebook連携周りを一緒に作りましょうか。という話になって、お手伝いした次第。
初めてsinatraを作ってAPI側のロジック実装したり、データストアにmonogo dbを使ったりと初めて使う技術ばかりでしたが、なんとか動かせましたよ。
(RubyでWebアプリは初めて書きました…。いままで検証用のスクリプト程度しか使ったことがなかったので。)
実装的には@がフロント側、私がバックエンドという役割分担で、打ち合わせは会社の昼休みにご飯を食べながら、あるいは開発が業務時間外や週末だったりしたのでfresh meetingで仕様を詰めながら実装を進めました。*2
多少、このあたりでの技術的な話をこのあたり(SinatraアプリをApache2のmod_proxy_balancerを通してThinで動かす(1) - Thinの設定)で書いていたりします。
MA7の応募が近づいてきて、今度は紹介ページとかがいるぞ。デザインどうすんだ?って話になったときにデザイナーの@参加!で、素敵な紹介ページができたのでした。

使ってみたい場合

*1:このエントリ早く上げようとしてたのだけど、遅れに遅れた…orz

*2:fresh meeting便利すぎます

Solrの検索クエリーとMySQLの検索クエリーの対応

Solrで検索クエリーを作るときにどうしてもSQLクエリが先に出てしまうので対応をメモ。
クエリーパラメータでいうとqですね。

検索式 MySQL*1 Solr
一致検索 WHERE title="foo" title:foo
全件 (where無し) *:*
AND WHERE title="foo" AND price=200 title:foo AND price:200
OR WHERE title="foo" OR price=200 title:foo OR price:200
NOT WHERE title="foo" AND price<>200 title:foo NOT price:200
範囲指定(未満) WHERE price < 200 price:{* TO 200}
範囲指定(以下) WHERE price <= 200 price:[* TO 200]
範囲指定 WHERE price > 10 AND price < 200 price:{10 TO 200}
先頭一致検索 WHERE title LIKE "foo*" title:foo*

*1:というか、SQLか。

SinatraアプリをApache2のmod_proxy_balancerを通してThinで動かす(2) -mod_proxy_balancerの設定

続けて、Apache2の設定。ここでは、既にmod_proxyが入っているものとします。
フロントのApache2から複数起動しているThinのサーバに振るためにmod_proxy_balancerを入れます。
Ubuntuであれば、

$ sudo a2enmod proxy_balancer

で、mod_proxy_balancerが有効になるので、続けてconfファイルを編集。

$ sudo vi /etc/apache2/mods-available/proxy.conf

中身は以下の様な形。


ProxyRequests Off

ProxyPass /foo balancer://foo
ProxyPassReverse /api balancer://foo


BalancerMember http://127.0.0.1:4040 loadfactor=10
BalancerMember http://127.0.0.1:4041 loadfactor=10
BalancerMember http://127.0.0.1:4042 loadfactor=10
BalancerMember http://127.0.0.1:4043 loadfactor=10
BalancerMember http://127.0.0.1:4044 loadfactor=10

これで、自身のホスト内の4040番〜4044番ポートで動いているThinサーバにフォワードされる様になります。
設定を有効にするためにApacheを再起動します。

$ sudo /etc/init.d/apache2 restart

これで、apacheサーバの ~/foo にアクセスされたものがThinサーバにフォワードされます。
開発用にwebrickで動かしていたものと比べて体感的にも大分変わりました。

SinatraアプリをApache2のmod_proxy_balancerを通してThinで動かす(1) – Thinの設定

今、WebアプリケーションのAPIサーバをRubyフレームワークSinatraでつくっていて、これを本格的に動かすときにWebサーバをどうしよう。ということになって、Thinが良いらしいという情報を得たので導入してみた。
現状、動いているApacheサーバがあるので、reverse proxyを通して特定のエントリポイントに対してアクセスがあったもののみをフォワードし、Thinを通してSinatraアプリにつなぐことを目標にします。

f:id:hideack:20111103165746p:image

インストールは非常に簡単。

% gem install thin

これで準備OK。簡単ですね。
次にアプリケーションを呼び出すためのファイルを作ります。
ここでは、ファイル名をconfig.ruとします。内容的には以下の様な形。
ここでrequireされているapp.rbはSinatraアプリ本体です。

# config.ru
require 'rubygems'
require 'sinatra'
require './app.rb'
run Sinatra::Application

単純に呼び出してるだけですね。
続けて、Thin本体の設定ファイルを作ります。
まずは中身。

# thin.yaml
timeout: 30
log: log/thin.log
environment: production
servers: 5
daemonize: true
rackup: config.ru
port: 4040

おおよそ設定見てもらったままなのですが、上記の設定でThinサーバのデーモンプロセスが5本起動されます。
利用されるポート番号は4040〜4044までとなります。
続けて、ログファイルの保管先とプロセス管理用のファイル保管されるディレクトリを予め作っておきます。

$ mkdir log
$ mkdir tmp

これでThin本体の準備完了。実際に動かしてみます。

$ thin start -C thin.yaml
Starting server on 0.0.0.0:4040 ...
Starting server on 0.0.0.0:4041 ...
Starting server on 0.0.0.0:4042 ...
Starting server on 0.0.0.0:4043 ...
Starting server on 0.0.0.0:4044 ...

起動完了。試しに動作を確認したければ、wget等で起動したいずれかのポートに対してwgetを叩けば挙動を確認できますね。
止める場合は、$ thin stop -C thin.yaml でOKです。(restartも)
tmpディレクトリには、起動中のプロセスのpidファイルが、logディレクトリにはそれぞれのThinサーバへのアクセスログが記録されます。

$ ls -l tmp/pids/
total 40

  • rw-r--r-- 1 hideack staff 5 11 3 16:30 thin.4040.pid
  • rw-r--r-- 1 hideack staff 5 11 3 16:30 thin.4041.pid
  • rw-r--r-- 1 hideack staff 5 11 3 16:30 thin.4042.pid
  • rw-r--r-- 1 hideack staff 5 11 3 16:30 thin.4043.pid
  • rw-r--r-- 1 hideack staff 5 11 3 16:30 thin.4044.pid

$ ls -l log/
total 40

  • rw-r--r-- 1 hideack staff 585 11 3 16:30 thin.4040.log
  • rw-r--r-- 1 hideack staff 360 11 3 16:30 thin.4041.log
  • rw-r--r-- 1 hideack staff 360 11 3 16:30 thin.4042.log
  • rw-r--r-- 1 hideack staff 360 11 3 16:30 thin.4043.log
  • rw-r--r-- 1 hideack staff 360 11 3 16:30 thin.4044.log

RubyでHTTPSリクエスト発行する

Rubyでプログラム書いていてWeb系のAPIHTTPSでアクセスする必要があったので調べた。
RubyでPOSTメソッドを発行するには以下の様に標準添付されているライブラリのnet/httpを利用すればよい。

#接続先ホスト名
hostname = "www.foo.bar"
#接続先パス
reqpath = "/foo/bar"
#POSTする際のパラメータ
param = "foo=hogehoge&bar=hogehoge"
https = Net::HTTP.new(hostname, 443)
https.use_ssl = true
https.verify_mode = OpenSSL::SSL::VERIFY_NONE  #本当は良くないが証明書チェック省略
res = https.post(
reqpath, param
)
p res.body # HTTP応答のBODYを出力

Rubyは標準ライブラリがいいですねぇ。

Rubyでnilの判定

Rubyでnil (空オブジェクト) の判定は nil? で行える。*1
if修飾子で書くと綺麗に書ける。やはりRubyで書くと読みやすいな。

def foo do
res = db.find("id" => 1)
return 'error' if res.nil?   # resが空オブジェクトの場合は 'error' を返却
#ゴニョゴニョする
'ok' # okを返却
end

*1:nilもオブジェクトなのか。空という定義のオブジェクト