ブログタイトル変更
統一感を出してみました!!!!!!!!!!!!!!!!
App::RadというCLIがだいぶ良い
App::Rad というコマンドラインツールがあるのですが、
手軽にサブコマンド作れてだいぶウマーな感じなのに
日本語情報が全くないので紹介してみます。
基本系
まずは「rad.pl」に「bucho」というサブコマンドを作ってみたいと思います。
use App::Rad; App::Rad->run; sub bucho { return "Hello Bucho!"; }
たったこれだけです。まずは実行してみましょう。
$ perl rad.pl Usage: rad.pl command [arguments] Available Commands: bucho help show syntax and available commands
いい感じにヘルプが出ました。
定義したサブルーチン「bucho」が勝手にサブコマンドとして認識されていますね。
buchoコマンドを実行してみましょう。
$ perl rad.pl bucho Hello Bucho!
実行されました。簡単!
help
buchoコマンドもヘルプに出したいですよね。
こんな感じで書いてみましょう。
use App::Rad; App::Rad->run; sub bucho :Help(give a nice compliment) { return "Hello Bucho!"; }
変な構文ですね。ヘルプを実行してみましょう。
$ perl rad.pl Usage: rad.pl command [arguments] Available Commands: bucho give a nice compliment help show syntax and available commands
ヘルプがついた!
setup
setupコマンドはスクリプトの初期化処理のようなものです。
いくつか例を挙げてみましょう。
以下は先ほどのbuchoコマンドを定義したものです。
sub setup { my $c = shift; $c->register_commands( { bucho => 'give a nice compliment!', }); }
もちろん、ここに書いておけば:Helpみたいな変な構文もいりません。
また、サブルーチン全てがコマンドとして登録したくないこともあるでしょう。
その時にはこのように明示的に指定するのが良いでしょう。
逆に、コマンドとして認識させない、という設定もあります。
以下は「_」で始まるサブルーチンをコマンドとして登録させません。
sub setup { my $c = shift; $c->register_commands( { -ignore_prefix => '_' } ); }
ただし、コマンド管理については
最初に紹介した「使うコマンドを登録する」方式がオススメです。
なぜならuseしたときにExportされるサブルーチンも自動で登録されてしまうため、
除外方式だとカバーしきれなくなってくるからです。*1
ちなみにコマンドを削除する「unregister_command 」
というサブルーチンもあったりします。
引数/バリデーション
引数を扱うにはoptionsを使います*2。
$ perl rad.pl create --id=aaa ... sub create { my $c = shift; print $c->options->{id}, "\n"; # aaa }
バリデーションを行いたい場合、getoptを使う方法と
自前でチェックする方法が考えられます。
お好みでどうぞ。
# getopt sub create { my $c = shift; $c->getopt( 'id=s', 'version=i' ) or $c->execute('usage') or return undef; } # Data::Validator sub create { my $c = shift; my $args = eval { state $rule = Data::Validator->new( account_id => { isa => 'Str'}, ); $rule->validate($c->options); }; if($@) { $c->execute('usage') or return undef; } }
共有データ
stashを使うと変数を共有できます。
use Redis::hiredis; sub setup { my $c = shift; my $redis = Redis::hiredis->new(); $redis->connect('127.0.0.1', 6379); # stashに入れる $c->stash->{redis} = $redis; } sub create { my $c = shift; … # stashから取り出す $c->stash->{redis}->command('set foo bar'); }
他の便利メソッド
pre_processやpost_processもとても便利ですが
デフォルトの動作が変わってしまう可能性があるので
気をつけて使う必要があるでしょう。*3
example
DynamoDBを操作する簡易CRUDコマンドを作ってみます。
# 例1:createサブコマンド実行 perl dynamodb_crud.pl create --id=1 --name=tori
簡易デプロイツールのテンプレのようなもの。
# 例1:deployサブコマンドをdevelopmentモードで実行 perl deploy.pl deploy --env=development
その他注意点
デフォルトではサブコマンドの戻り値が標準出力されるため、*4
何も出力したくない場合はいちいち
return undef;
しないといけません。
最後に
今までコマンドラインツールといえばGetopt::Longでしたが、
簡単なスクリプトならお手軽にかけますし
Getopt::Longの代わりとしてもアリなのではないでしょうか。
defaultメソッドに処理を書けばサブコマンドなしでも動きますし。*5
use App::Rad; App::Rad->run(); sub default { rand(10) }
紹介しきれなかった機能もたくさんあるので
ドキュメントをご参照ください。
http://search.cpan.org/~garu/App-Rad-1.04/lib/App/Rad.pm
App::Monitor::Simpleというモジュール書いた
https://github.com/toritori0318/p5-App-Monitor-Simple
シンプルに「リトライ数だけ指定して全部エラーなら何かする」
ということがしたくて探してみたけど見つからず。
コードボリュームからしてもわざわざモジュールにする程でもないかもしれませんが、
あったらあったでまあまあ嬉しいかも、と思い書いてみました。
仕様
やることは至極単純で
「コマンド実行した戻り値が0 or 0以外でOKかNGを判定するだけ」
です。
オプションとしてリトライ数とリトライ間隔を指定することが出来ます。
コマンドラインツール
monitor-simpleというのがついてきます。
詳しくは--help参照ですが、例を説明してみます。
応用編
monitor-simple 'curl http://hogehoge.co.jp/' && '正常時の処理' monitor-simple 'curl http://hogehoge.co.jp/' || 'エラー時の処理'
このようにするとcronなどで使えるのではないでしょうか。
またコマンドの部分をシェルやLLで書いてももちろんOKで、
むしろそういった応用を利かす使い方が多いかもしれません。
Perlモジュールとして使う
もちろんPerlからも使えます。
以下はSYNOPSISのコピペ。なんとなくわかるはず。
my $ret = run( { command => 'ping -c 1 blahhhhhhhhhhhhhhhh.jp', interval => 10, retry => 5, quiet => 1, } );
example
いくつか例を書いてみます。*1
複数サーバのステータスを独自のデータストアに保存したい。
例えばですが、
「HTTP監視をした結果、それが成功しているサーバのリストを保持しておきたい」
という要望があったとしましょう。
以下のようなコードをcronで回すとよいです。
use strict; use warnings; use Data::Dumper; use Parallel::ForkManager; use Redis; use App::Monitor::Simple qw/run/; my $redis = Redis->new(server => 'localhost:6379'); my @hosts = ('xxx.xxx.xxx.xxx', 'yyy.yyy.yyy.yyy'); my $workers = scalar @hosts; $workers = 10 if $workers > 10; my $pm = Parallel::ForkManager->new($workers); do { print "parallel: $workers process\n"; $pm->run_on_start( sub { my ($pid, $ident) = @_; printf "[%s] start\n", $ident; } ); $pm->run_on_finish( sub { my ($pid, $exit, $ident) = @_; printf "[%s] completes.\n", $ident; } ); }; # run foreach my $host (@hosts) { $pm->start($host) and next; my $cmd = sprintf('curl --max-time 3 http://%s/', $host); my $status = run( { command => $cmd, retry => 3, interval => 5, quiet => 1, } ); if($status == 0) { warn "success!"; $redis->set($host, 1); } else { warn "error!"; $redis->set($host, 0); } $pm->finish; } # wait... $pm->wait_all_children;
Redisをフェイルオーバーしたい
Redisでは自動フェイルオーバーの機能は提供されていない…ですよね?
AWS上にマスター/スレーブ構成のRedisがあるとします。
マスターにはElasticIPがついていて、
WebアプリケーションからはそのIPを参照しています。
そのような構成になっていると仮定し、
App::Monitor::Simpleで定期的に死活監視しつつ
マスターが落ちたらスレーブを昇格させて
ElasticIPを自動的に付け替える、ということを実現してみたいと思います。
(AWS デザインパターンのこれですね)
http://aws.clouddesignpattern.org/index.php/CDP:Floating_IP%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3
use strict; use warnings; use Clone qw(clone); use App::Monitor::Simple qw/run/; use VM::EC2; my $ec2 = VM::EC2->new(-access_key=>$id,-secret_key=>$key,-endpoint=>$url); # マスターサーバ情報 my $master = { host => 'xxx.xxx.xxx.xxx', instance_id => 'aaaaaaaaaaaaa', is_master => 1, }; # スレーブサーバ情報 my $slave = { host => 'yyy.yyy.yyy.yyy', instance_id => 'bbbbbbbbbbbbb', is_master => 0, }; my $elastic_id='zzz.zzz.zzz.zzz'; for my $server ($master, $slave) { my $host = $server->{host}; warn "[$host] start!"; # redisの死活監視 my $ret = run( { command => "redis-cli -h $host -p 6379 dbsize", interval => 5, retry => 5, quiet => 1, } ); if($ret == 0) { warn "[$host] ok!"; } else { warn "[$host] ng!"; # マスターが落ちている場合 if($server->{is_master}) { # スレーブをマスターに昇格 my $change_host = $slave->{host}; system("redis-cli -h $change_host -p 6379 slaveof no one"); # Elastic IP 付け替え my @addr = $ec2->describe_addresses($elastic_ip); my $result = $addr[0]->associate_address($elastic_addr => $slave->{instance_id}); # 情報swap my $tmp = clone($master); $master = clone($slave); $slave = clone($tmp); $master->{is_master} = 1; $slave->{is_master} = 0; } # 自動でスレーブ作り直し? # ... # もしくは 通知して exit? return; } }
Todo
- タイムアウト処理もこのモジュールで出来たほうがいいのかなー
- Warningも別判定にしたほうがいいのかなー
最後に
もちろんNagiosなどツールを使えば同じことが出来ますが、
シンプルな監視をしたいときには有用かと思うのですがいかがでしょうか。
*1:全てフィクションです!!!!
お遊びワンライナー
なんていうんでしたっけ。反射神経試す奴。
ワンライナーにしてみました*1
以下のコマンド叩いて「!」が出たらすばやく「Ctrl+C」を実行しましょう。
perl -MTime::HiRes -E '*z=*Time::HiRes::gettimeofday;$SIG{INT}=sub{if($f){$e=[z()];printf "\n %.4f\n",Time::HiRes::tv_interval($s,$e);exit;}else{say "NG!";exit;}};say "wait";sleep int(rand(15));$s=[z()];$f=1;say "!";while(1){}'
仕事にお疲れのあなた!
息抜きに遊んでみてください(・0・)
Amon2でエラーHTMLをカスタマイズしたい
Amon2のstatic以下にエラーページ用のHTML置いてあるけど
デフォルトでは見ていないようでどうすれば見てくれるのかなーと考えてたんだけど
Middlewareレベルでやらせればいいのかな。
app.psgi のbuilder にこんなの追加しておく。
enable "Plack::Middleware::ErrorDocument", 404 => "static/404.html", 500 => "static/500.html", 502 => "static/502.html", 503 => "static/503.html", 504 => "static/504.html", ; enable "Plack::Middleware::HTTPExceptions";
Amon2でFacebookAPIを使う その2
前回はFacebook::Graphを使ってFacebookにアクセスできました。
しかし実はAmon2にはFacebookAPIにコネクトするプラグインも存在します。
プラグインを使うとFacebook::Graphなしでも簡単にトークンを取得することができます。
試してみましょう。
とりあえず再度新しくアプリケーションを作っておきましょう。
amon2-setup.pl FacebookAPIPlugin cd FacebookAPIPlugin
プラグインの設定
FacebookAPIPlugin::Web.pmにプラグインの設定とコールバックを書いておきます。
__PACKAGE__->load_plugin( 'Web::Auth', { module => 'Facebook', on_finished => sub { my ($c, $token, $user) = @_; my $name = $user->{name} || die; $c->session->set('name' => $name); $c->session->set('site' => 'facebook'); $c->session->set('token' => $token); return $c->redirect('/'); }, on_error => sub { my ( $c, $error ) = @_; warn ("auth_error!![$error]"); return $c->redirect('/'); }, } );
プラグインの設定内容はconf/development.pl に追記しておきます。
Auth => { Facebook => { client_id => 'cccccccccccccc', # アプリケーションID client_secret => 'dddddddddddddddd', # アプリケーション秘密鍵 scope => 'read_stream', # 権限 } }
これだけでOK。
あとは /auth/facebook/authenticate にアクセスさせれば
そのまま認証プロセスに入り、上記で設定したコールバックで
アクセストークンを取得できます。
コントローラ
生でアクセスするのであれば以下のように書けばよいでしょう。
FacebookAPIPlugin::Web::Dispatcher
any '/' => sub { my ($c) = @_; my $data; my $token = $c->session->get('token'); if ( $token ) { # loggedin my $ua = LWP::UserAgent->new(); my $res = $ua->get("https://graph.facebook.com/me/home?access_token=${token}"); $res->is_success or die $res->status_line; $data = decode_json($res->decoded_content); } $c->render( 'index.tt', { name => $c->session->get('name'), data => $data->{data}, } ); };
簡単ですね!
APIへのアクセスはFacebook::Graphを使うのが楽だと思うので、
プラグイン+Facebook::Graphのハイブリッドで使うのが良いのではないでしょうか。
yattane!
Amon2でFacebookAPIを使う その1
FacebookAPIを使う要件が出てきそうなのでいろいろ調査中。
perlでFacebookAPIを使うにはFacebook::Graphがいいんでしたよね!
Facebook::Graphのドキュメントにちょうどチュートリアルがあったので
試しに今回はその手順通りに設定していきたいと思います。
ただしそのままではなく一部Amon2用に置き換えてます。
また、今回は表示するデータを自分のニュースフィードにしてみました。
あと、Facebook アプリ登録画面ってログインするたびちょこちょこ変わってて
他のサイトの設定画面が参考にならなかったりするので
スクリーンショットも添付してみましょう。
Step 1: Set up the developer application on Facebook.
Step 2: Create your application.
https://developers.facebook.com/apps へ行き
「+新しいアプリケーションを作成」をクリックします。
Step 3: The Connect tab.
「アプリをFacebookに結合する方法を選択してください 」→「ウェブサイト」に
URL( ここでは http://localhost:5000/ )を入力します。
Step 4: Note your application settings.
"アプリケーションID"および"アプリケーションの秘訣"*1をメモしておくか、
このページをブックマークします。
後でこれらが必要になります。
Step 5: Create app
今回はAmon2で書くので、amon2-setup.pl を実行します
amon2-setup.pl FacebookAPI cd FacebookAPI
Step 6: Create your Facebook::Graph object.
Facebook::Graphはリクエストごとに使いまわしたいので
アプリケーションのサブルーチンにしてしまいます。
今回はFacebookAPI.pm に以下のような記述を追加しました。
use Facebook::Graph; sub fb { my $self = shift; if ( !defined $self->{fb} ) { my $conf = $self->config->{'Facebook::Graph'} or die "missing configuration for 'Facebook::Graph'"; my $fb = Facebook::Graph->new( postback => $conf->{postback}, app_id => $conf->{app_id}, secret => $conf->{secret}, ); $self->{fb} = $fb; } return $self->{fb}; }
Facebook::Graphの設定はconf/development.pl に追記しておきます。
'Facebook::Graph' => { postback => 'http://localhost:5000/facebook/postback', app_id => 'aaaaaaaaaaaaaaaaaaaaaa', secret => 'bbbbbbbbbbbbbbbbbbbbbb', },
Step 7: Create your application's connect page.
認証リダイレクトを作成する必要があります。
Facebookの必要なアクセス許可を行う場所です。http://developers.facebook.com/docs/authentication/permissions で
権限についての完全なリストがあります。
今回はサンプルと異なり、ニュースフィードを表示したいため
権限に read_stream を指定します。
vi lib/FacebookAPI/Web/Dispatcher.pm
get '/facebook' => sub { my ($c) = @_; $c->redirect( $c->fb->authorize->extend_permissions( qw(read_stream) )->uri_as_string ); };
Step 8: Create the Facebook access token postback page.
接続/認証ページには、アプリを承認するためにFacebookにユーザーをリダイレクトします。
ユーザーがFacebookからリダイレクトされるページを作成する必要があります。
これは、Step 6で作成したpostbackを指定します。
チュートリアルとは異なり、今回は取得したトークンをセッションに保持します。
vi lib/FacebookAPI/Web/Dispatcher.pm
get '/facebook/postback' => sub { my ($c) = @_; $c->fb->request_access_token($c->req->param('code')); $c->session->set('token' => $c->fb->access_token); $c->redirect( 'http://localhost:5000/' ); };
Step 9: Let's do something already!
アクセストークンを持っているとAPIへ要求を行うことができます。
vi lib/FacebookAPI/Web/Dispatcher.pm
get '/' => sub { my ($c) = @_; my $data; my $token = $c->session->get('token'); if ( $token ) { # loggedin $c->fb->access_token( $token ); $data = $c->fb->fetch('me/home')->{data}; } $c->render( 'index.tt', { data => $data, } ); };
テンプレートを書きます。
vi tmpl/index.tt
[% WRAPPER 'include/layout.tt' %] <section> [% IF data %] Hi! [% name %]. <section> [% FOR v IN data %] <ul> <li>[% v.created_time %] [% v.from.name %] [% v.message %] </li> </ul> [% END %] </section> <form method="post" action="/account/logout"> <input type="submit" value="logout" class="btn primary" /> </form> [% ELSE %] <a href="/facebook">login</a> [% END %] </section> [% END %]
Step 10: Start the application and let's test this puppy out.
サーバー上で(あなたがapp.psgiのフォルダにいると仮定して)次のコマンドを実行します。
plackup
次に http://localhost:5000/ を表示します。
login リンクをクリックすると認証ページヘ遷移し、
認可すると認証プロセスを経て最終的に自分のニュースフィードが表示されます。
yatta!
*1:秘訣って何…