Tatsumaki是Plack作者的一个小框架,亮点是很好的利用了psgi.streaming的接口可以async的完成响应。不过因为缺少周边支持,所以除了几个webchat的example,似乎没看到什么应用。笔者之前纯为练手,却用tatsumaki写了个sync响应的小demo,算是展示一下用tatsuamki做普通web应用的基础步骤吧:

(代码本来是作为一个ElasticSearch数据分析的平台,不过后来发现社区有人开始做纯js的内嵌进ElasticSearch的plugin了,所以撤了repo,这里贴下代码)

  • 所有的psgi/plack应用都一样有自己的app.psgi文件: ```perl our $VERSION = 0.01;

    app.psgi

    use Tatsumaki::Error; use Tatsumaki::Application; use Tatsumaki::HTTPClient; use Tatsumaki::Server;

    read config

    use File::Basename; use YAML::Syck; my $config = LoadFile(dirname(FILE) . ‘/config.yml’);

    elasticsearch init

    use ElasticSearch; #这里yml的写法借鉴Dancer::Plugin::ElasticSearch了 my $elsearch = ElasticSearch->new( $config->{‘options’} );

    index init

    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}, }; };

render可以接收参数,并且默认把$self带进去,具体key是handler

$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">
Bubble -- a perl webui for logstash & elasticsearch
% if ( $table ) { % for ( @codes ) { % } % for my $list ( @{ $table } ) { % for ( @codes ) { % if ( $list->{'code'}->{$_} ) { % } else { % } % } % }
<%= $group %> 平均响应时间 最大响应时间 下载数<%= $_ %>
<%= $list->{'key'} %> <%= $list->{'mean'} %> <%= $list->{'max'} %> <%= $list->{'count'} %><%= $list->{'code'}->{$_} %>
% }

```

效果如下:

查询表格并提交最多次的url绘图


2012 年 12 月 30 日附注:

更好的纯 js 版本已经作为独立的 elasticsearch-plugin 项目发布在 github 上。地址:https://github.com/chenryn/elasticsearch-logstash-faceter 。欢迎大家试用!!