Created
August 29, 2011 17:13
-
-
Save nihen/1178857 to your computer and use it in GitHub Desktop.
isucon app
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use 5.14.1; | |
use Plack::Request; | |
use Plack::Builder; | |
use FindBin; | |
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 $env = shift; | |
if ( $env->{PATH_INFO} =~ m{\A/article/(\d+)\z}o ) { | |
my $articleid = $1; | |
return ['200', $header, [ | |
$base_top, | |
$sidebar, | |
$article_contents->[$articleid], | |
$base_bottom, | |
# feersumは下記が使える | |
#\$base_top, | |
#\$sidebar, | |
#\$article_contents->[$articleid], | |
#\$base_bottom, | |
]]; | |
} | |
elsif ( $env->{PATH_INFO} eq '/' ) { | |
return ['200', $header, [ | |
$base_top, | |
$sidebar, | |
$index_content, | |
$base_bottom, | |
# feersumは下記が使える | |
#\$base_top, | |
#\$sidebar, | |
#\$index_content, | |
#\$base_bottom, | |
]]; | |
} | |
elsif ( $env->{PATH_INFO} =~ m{\A/comment/(\d+)\z}o ) { | |
my $param = Plack::Request->new($env)->body_parameters; | |
comment($1, $param->{name}, $param->{body}); | |
return ['302', [Location => '/article/' . $1], []]; | |
} | |
elsif ( $env->{PATH_INFO} eq '/post' ) { | |
if ( $env->{REQUEST_METHOD} eq 'GET' ) { | |
return ['200', $header, [ | |
$base_top, | |
$sidebar, | |
$post_content, | |
$base_bottom, | |
# feersumは下記が使える | |
#\$base_top, | |
#\$sidebar, | |
#\$post_content, | |
#\$base_bottom, | |
]]; | |
} | |
else { | |
my $param = Plack::Request->new($env)->body_parameters; | |
post($param->{title}, $param->{body}); | |
return ['302', [Location => '/'], []]; | |
} | |
} | |
return ['404', [], []]; | |
}; | |
builder { | |
enable 'Static', | |
path => qr!\A/(?:(?:css|js|images)/|favicon\.ico\z)!, | |
root => $FindBin::Bin . '/../staticfiles/'; | |
\&app; | |
}; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
起動前に
rm logfile
を毎回実施して、ベンチとった結果
corona: 73853
twiggy: 70728
feersum/scalar: 94054
feersum/scalar_ref: 96550
feersumはやいすねー