use 5.14.1; | |
use Plack::Request; | |
use Text::Xslate::Util qw/html_escape/; | |
use POSIX qw/strftime/; | |
use List::Util qw/first/; | |
use JSON::XS; | |
use IO::Handle; | |
use Encode; | |
my $header = ['content-type' => 'text/html']; | |
my $articles = []; | |
my $recent_articles = []; | |
my $article_contents = []; | |
my $sidebar = ''; | |
my $index_content = ''; | |
my $max_articleid = 0; | |
my $json_encoder = JSON::XS->new->latin1; | |
my $json_decoder = JSON::XS->new->utf8; | |
my $log_file = './logfile'; | |
my $log; | |
my $base_top =<<_EOF_; | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html" charset="utf-8"> | |
<title>isucon now!</title> | |
<link type="text/css" href="/css/jquery-ui-1.8.14.custom.css" rel="stylesheet"> | |
<link type="text/css" href="/css/isucon.css" rel="stylesheet"> | |
<script type="text/javascript" src="/js/jquery-1.6.2.min.js"></script> | |
<script type="text/javascript" src="/js/jquery-ui-1.8.14.custom.min.js"></script> | |
<script type="text/javascript" src="/js/isucon.js"></script> | |
</head> | |
<body> | |
<div id="view"> | |
<div id="titleimage"><a href="/post"><img src="/images/isucon_title.jpg" style="border: none;"></a></div> | |
<div id="mainview"> | |
_EOF_ | |
my $base_bottom =<<_EOF_; | |
</div> | |
</body> | |
</html> | |
_EOF_ | |
my $post_content =<<_EOF_; | |
<div id="articleview"> | |
<form method="POST" action="/post"> | |
<table> | |
<tr><td><input type="text" name="title" size="60"/></td></tr> | |
<tr><td><textarea name="body" cols="60" rows="8"></textarea></td></tr> | |
</table> | |
<input type="submit"/> | |
</form> | |
</div> | |
_EOF_ | |
{ | |
#preload | |
if ( -e $log_file ) { | |
open $log, '<', $log_file; | |
my @lines = <$log>; | |
$log->close; | |
open $log, '>', $log_file; | |
for my $line (@lines) { | |
chomp $line; | |
my $ops = $json_decoder->decode($line); | |
for my $str ( @$ops ) { | |
$str = Encode::encode('utf-8', $str); | |
} | |
if ( $ops->[0] eq 'post' ) { | |
post(@{$ops}[1..@$ops-1]); | |
} | |
elsif ( $ops->[0] eq 'comment' ) { | |
comment(@{$ops}[1..@$ops-1]); | |
} | |
} | |
} | |
else { | |
open $log, '>', $log_file; | |
for ( 0..10 ) { | |
post($_, $_); | |
} | |
} | |
} | |
sub get_sidebar { | |
my @body = ('<div id="sidebar"><table><tr><td>新着コメントエントリ</td></tr>'); | |
for my $article ( @{$recent_articles}[0..9] ) { | |
push @body, sprintf( | |
'<tr><td><a href="/article/%s">%s</a></td></tr>', | |
$article->{id}, | |
$article->{title}, | |
); | |
} | |
push @body, '</table></div>'; | |
return join '', @body; | |
} | |
sub get_index_content { | |
my @body = ('<div id="articleview">'); | |
for my $article ( @{[reverse @{$articles}]}[0..9] ) { | |
push @body, sprintf( | |
'<div class="article"><div class="title">%s</div><div class="created">%s</div><div class="body">', | |
html_escape($article->{title}), html_escape($article->{created_at}) | |
); | |
my @lines = split "\n", $article->{body}; | |
for my $line (@lines[0..8]) { | |
push @body, html_escape($line), '<br />'; | |
} | |
push @body, '</div><div class="articlelink">'; | |
if ( @lines > 7 ) { | |
push @body, '... ...'; | |
} | |
push @body, sprintf('<a href="/article/%s">read more</a></div></div>', $article->{id}); | |
} | |
return join '', @body, '</div>'; | |
} | |
sub get_article_content { | |
my $articleid = shift; | |
my $article = $articles->[$articleid]; | |
my $abody = ''; | |
for my $body_line ( split "\n", $article->{body} ) { | |
$abody .= html_escape($body_line); | |
$abody .= '<br />'; | |
} | |
my @body = sprintf('<div id="articleview"><div class="article"> | |
<div class="title">%s</div> | |
<div class="created">%s</div> | |
<div class="body">%s</div> | |
</div> | |
<div class="comments"> | |
<div class="commenttitle">以下みなさまの反応</div> | |
', html_escape($article->{title}), html_escape($article->{created_at}), $abody); | |
for my $comment (@{$article->{comments}}) { | |
my $cbody = ''; | |
for my $body_line ( split "\n", $comment->{body} ) { | |
$cbody .= html_escape($body_line); | |
$cbody .= '<br />'; | |
} | |
push @body, sprintf(' | |
<div class="comment"> | |
<div class="name">%s</div> | |
<div class="created">%s</div> | |
<div class="body">%s</div> | |
</div> | |
', html_escape($comment->{name} eq '' ? '名無しさん' : $comment->{name}), html_escape($comment->{created_at}), $cbody); | |
} | |
push @body, sprintf(' | |
<div class="commentform"> | |
<div class="commenttitle">あなたの反応</div> | |
<form method="POST" action="/comment/%s"> | |
<table> | |
<tr><td>おなまえ: <input type="text" name="name" size="30"/></td></tr> | |
<tr><td><textarea name="body" cols="60" rows="4"></textarea></td></tr> | |
</table> | |
<input type="submit"/> | |
</form> | |
</div> | |
</div>', $articleid); | |
return join '', @body, '</div>'; | |
} | |
sub post { | |
$log->printflush($json_encoder->encode([post => @_]), "\n"); | |
my ($title, $body) = @_; | |
my $articleid = $max_articleid++; | |
my $article = { | |
id => $articleid, | |
title => $title, | |
body => $body, | |
created_at => strftime('%Y-%m-%d %H:%M:%S', localtime()), | |
comments => [], | |
}; | |
$articles->[$articleid] = $article; | |
$article_contents->[$articleid] = get_article_content($articleid); | |
$index_content = get_index_content(); | |
} | |
sub comment { | |
$log->printflush($json_encoder->encode([comment => @_]), "\n"); | |
my ($articleid, $name, $body) = @_; | |
my $article = $articles->[$articleid]; | |
push @{$article->{comments}}, { | |
name => $name, | |
body => $body, | |
created_at => strftime('%Y-%m-%d %H:%M:%S', localtime()), | |
}; | |
my $this_article = first { $_->{id} eq $articleid } @$recent_articles; | |
if ( $this_article ) { | |
$recent_articles = [$this_article, grep { $_->{id} ne $articleid } @$recent_articles]; | |
} | |
else { | |
unshift @$recent_articles, $article; | |
if ( @$recent_articles > 11 ) { | |
pop @$recent_articles; | |
} | |
} | |
$sidebar = get_sidebar(); | |
$article_contents->[$articleid] = get_article_content($articleid); | |
} | |
sub app { | |
my $path_info = $_[0]->{PATH_INFO}; | |
if ( $path_info =~ m{\A/article/(\d+)\z}o ) { | |
return ['200', $header, [ | |
\$base_top, | |
\$sidebar, | |
\$article_contents->[$1], | |
\$base_bottom, | |
]]; | |
} | |
elsif ( $path_info eq '/' ) { | |
return ['200', $header, [ | |
\$base_top, | |
\$sidebar, | |
\$index_content, | |
\$base_bottom, | |
]]; | |
} | |
elsif ( $path_info =~ m{\A/comment/(\d+)\z}o ) { | |
my $param = Plack::Request->new($_[0])->body_parameters; | |
comment($1, $param->{name}, $param->{body}); | |
return ['302', [Location => '/article/' . $1], []]; | |
} | |
elsif ( $path_info eq '/post' ) { | |
if ( $_[0]->{REQUEST_METHOD} eq 'GET' ) { | |
return ['200', $header, [ | |
\$base_top, | |
\$sidebar, | |
\$post_content, | |
\$base_bottom, | |
]]; | |
} | |
else { | |
my $param = Plack::Request->new($_[0])->body_parameters; | |
post($param->{title}, $param->{body}); | |
return ['302', [Location => '/'], []]; | |
} | |
} | |
return ['404', [], []]; | |
} | |
\&app; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
nginxでstaticファイルを処理したら、feersumとの組み合わせで214,204req/minでた