まだちゃんと使ってないから問題点とかわからないけど、とりあえずStarmanを使うとかで結構スケールできる気がする。そのあたりを自由に変えられるのもPlack++だね。
使用想定としては、これをplackupしておいて、クライアント AnyEvent::HTTPで http_postして非同期で応答待ちする感じ。
package Lyra::Server::Worker;
use Moose;
use Router::Simple;
use JSON::XS;
use namespace::autoclean;
use constant NO_SUCH_WORKER =>
[ 404, [ "Content-Type" => "application/json" ],
[ q|{ "status" => 0, "message" => "no such worker" }| ] ];
use constant BAD_PAYLOAD =>
[ 500, [ "Content-Type" => "application/json" ],
[ q|{ "status" => 0, "message" => "bad payload" }| ] ];
use constant WORKER_ERROR =>
[ 500, [ "Content-Type" => "application/json" ],
[ q|{ "status" => 0, "message" => "worker error" }| ] ];
use constant WORKER_SUCCESS =>
[ 200, [ "Content-Type" => "application/json" ],
[ q|{ "status" => 1 }| ] ];
has router => (
is => 'ro',
isa => 'Router::Simple',
default => sub { Router::Simple->new() }
);
sub register {
my ($self, $path, $worker, $method) = @_;
$method ||= 'process';
$self->router->connect( $path, { controller => $worker, action => $method } );
}
sub psgi_app {
my $self = shift;
return sub {
$self->process(@_);
}
}
sub process {
my ($self, $env) = @_;
my $matched = $self->router->match( $env );
if (! $matched) {
return NO_SUCH_WORKER;
}
my $payload;
eval {
$payload = $self->fetch_payload( $env );
};
if ($@) {
warn $@;
return BAD_PAYLOAD;
}
my $response;
eval {
my $worker = $matched->{controller};
my $method = $matched->{action};
$response = $worker->$method( $payload );
};
if ($@) {
warn $@;
return WORKER_ERROR;
}
if (! $response ) {
$response = WORKER_SUCCESS;
}
return $response;
}
sub fetch_payload {
my ($self, $env) = @_;
if ($env->{REQUEST_METHOD} ne 'POST') {
return;
}
my $cl = $env->{ CONTENT_LENGTH };
my $ct = $env->{ CONTENT_TYPE };
# $ct should be application/json, but we're not checking this right now
my $input = $env->{ 'psgi.input' };
# Just in case if input is read by middleware/apps beforehand
$input->seek(0, 0);
my $buffer = '';
my $spin = 0;
while ($cl > 0) {
$input->read(my $chunk, $cl < 8192 ? $cl : 8192);
my $read = length $chunk;
$cl -= $read;
$buffer .= $chunk;
if ($read == 0 && $spin++ > 2000) {
Carp::croak "Bad Content-Length: maybe client disconnect? ($cl bytes remaining)";
}
}
decode_json $buffer;
}
__PACKAGE__->meta->make_immutable();
1;