Tatsumaki是Plack作者的一个小框架,亮点是很好的利用了psgi.streaming的接口可以async的完成响应。不过因为缺少周边支持,所以除了几个webchat的example,似乎没看到什么应用。笔者之前纯为练手,却用tatsumaki写了个sync响应的小demo,算是展示一下用tatsuamki做普通web应用的基础步骤吧:
(代码本来是作为一个ElasticSearch数据分析的平台,不过后来发现社区有人开始做纯js的内嵌进ElasticSearch的plugin了,所以撤了repo,这里贴下代码)
use Tatsumaki::Error; use Tatsumaki::Application; use Tatsumaki::HTTPClient; use Tatsumaki::Server;
use File::Basename; use YAML::Syck; my $config = LoadFile(dirname(FILE) . ‘/config.yml’);
use ElasticSearch; #这里yml的写法借鉴Dancer::Plugin::ElasticSearch了 my $elsearch = ElasticSearch->new( $config->{‘options’} );
use POSIX qw(strftime); my $index = join ‘-‘, ( (+split( ‘-‘, $config->{‘index’} ))[0], strftime( (+split( ‘-‘, $config->{‘index’} ))[1], localtime ) ); my $type = $config->{‘type’}; #首页类,调用了模板 package MainHandler; use parent qw(Tatsumaki::Handler); sub get { my $self = shift; $self->render(‘index.html’); }; #具体的API类 package ListHandler; use parent qw(Tatsumaki::Handler); sub get { #这里自动把urlpath切分好了 my ( $self, $group, $order, $interval ) = @_; return ‘Not valid order’ unless $order eq ‘count’ or $order eq ‘mean’; return ‘Not valid interval’ unless $interval =~ m#\d+(h|m|s)#; my ($key_field, $value_field); if ( $group eq ‘url’ ) { $key_field = ‘url’; $value_field = ‘responsetime’; } elsif ( $group eq ‘ip’ ) { $key_field = ‘oh’; $value_field = ‘upstreamtime’; } else { return ‘Not valid group field’; };
# get index mapping and sort into array my $mapping = $elsearch->mapping( index => “$index”, type => “$type”, ); my @res_map; for my $property ( sort keys %{ $mapping->{$type}->{‘properties’} } ) { if ($property eq ‘@fields’ ) { my @fields; push @fields, { name => $, type => $mapping->{$type}->{‘properties’}->{$property}->{‘properties’}->{$}->{‘type’} } for sort keys %{ $mapping->{$type}->{‘properties’}->{$property}->{‘properties’} }; push @res_map, \@fields; } else { push @res_map, { name => $property, type => $mapping->{$type}->{‘properties’}->{$property}->{‘type’} }; } }
# get value stat group by key field my $data = $elsearch->search( index => “$index”, type => “$type”, size => 0, query => { “range” => { ‘@timestamp’ => { from => “now-$interval”, to => “now” }, }, }, facets => { “$group” => { “terms_stats” => { “value_field” => “$value_field”, “key_field” => “$key_field”, “order” => “$order”, “size” => 20, } }, } ); my @res_tbl; for ( @{$data->{facets}->{“$group”}->{terms}} ) { my $key = $->{term}; my $mean = sprintf “%.03f”, $->{mean}; my $code_count = code_count($key_field, $key, $interval); push @res_tbl, { key => $key, min => $->{min}, max => $->{max}, mean => $mean, code => $code_count, count => $_->{count}, }; };
$self->render('index.html', { table => \@res_tbl, mapping => \@res_map }); };
sub code_count { my ($key_field, $key, $interval) = @; my $result; my $data = $elsearch->search( index => “$index”, type => “$type”, size => 0, query => { range => { ‘@timestamp’ => { from => “now-$interval”, to => “now” }, }, }, facets => { “code” => { facet_filter => { term => { $key_field => “$key” } }, terms => { field => “status”, } } } ); for ( @{$data->{facets}->{code}->{terms}} ) { $result->{$->{term}} = $_->{count}; }; return $result; }; #画图数据API类,因为响应的是Ajax请求,所以这里开启了async,不过其实没意义了。因为这个ElasticSearch代码不是async格式的。应该改造用ElasticSearch::Transport::AEHTTP才能做到全程async。 package ChartHandler; use parent qw(Tatsumaki::Handler); PACKAGE->asynchronous(1); use JSON; sub post { my $self = shift; my $api = $self->request->param(‘api’) || ‘term’; my $key = $self->request->param(‘key’) || ‘oh’; my $value = $self->request->param(‘value’); my $status = $self->request->param(‘status’) || ‘200’;
my $field = $key eq 'oh' ? 'upstreamtime' : 'responsetime';
my $data = $elsearch->search(
index => "$index",
type => "$type",
size => 0,
query => {
match_all => { }
},
facets => {
"chart" => {
facet_filter => {
and => [
{
term => {
status => $status,
},
},
{
$api => {
$key => $value,
},
},
]
},
date_histogram => {
value_field => $field,
key_field => '@timestamp',
interval => "1m"
}
},
},
);
my @result;
for ( @{$data->{'facets'}->{'chart'}->{'entries'}} ) {
push @result, {
time => $_->{'time'},
count => $_->{'count'},
mean => sprintf "%.3f", $_->{'mean'} * 1000,
};
};
header('Content-Type' => 'application/json');
to_json(\@result); }; #主函数 package main; use File::Basename; #通过Tatsumaki::Application绑定urlpath到不同的类上。注意下面listhandler那里用的正则捕获。对,上面类里传参就是这么来的。注意最多不超过$9。 my $app = Tatsumaki::Application->new([
'/' => 'MainHandler',
'/api/chartdata' => 'ChartHandler',
'/api/(\w+)/(\w+)/(\w+)' => 'ListHandler', ]); #指定template和static的路径。类似Dancer里的views和public $app->template_path(dirname(__FILE__) . '/templates'); $app->static_path(dirname(__FILE__) . '/static'); #psgi app组建完成 return $app->psgi_app; true; ``` static里都是bootstrap的东西就不贴了。然后说说template。目前Tatsumaki只支持Text::MicroTemplate::File一种template,当然自己在handler里调用其他的template然后返回字符串也行。不过其实Text::MicroTemplate也蛮强大的。下面上例子: ```html %# 这就是Text::MicroTemplate强大的地方了,行首加个百分号就可以直接使用perl而不像TT那样尽量搞自己的语法 %# 配合render传来的handler(前面说了是类$self),整个环境全可以任意调用。 % my $mapping = $_[0]->{'mapping'}; % my $table = $_[0]->{'table'}; %# 比如这里其实就是通过handler调用request了。 % my $group = $_[0]->{handler}->args->[0]; % my @codes = qw(200 206 302 304 400 403 404 499 502 503 504); <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%= $group %> | 平均响应时间 | 最大响应时间 | 下载数 | % for ( @codes ) {<%= $_ %> | % }|
---|---|---|---|---|---|
<%= $list->{'key'} %> | <%= $list->{'mean'} %> | <%= $list->{'max'} %> | <%= $list->{'count'} %> | % for ( @codes ) { % if ( $list->{'code'}->{$_} ) {<%= $list->{'code'}->{$_} %> | % } else {% } % } |
```
效果如下:
2012 年 12 月 30 日附注:
更好的纯 js 版本已经作为独立的 elasticsearch-plugin 项目发布在 github 上。地址:https://github.com/chenryn/elasticsearch-logstash-faceter 。欢迎大家试用!!