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 。欢迎大家试用!!