App-Alice
view release on metacpan or search on metacpan
lib/App/Alice/HTTPD.pm view on Meta::CPAN
package App::Alice::HTTPD;
use AnyEvent;
use AnyEvent::HTTP;
use Twiggy::Server;
use Plack::Request;
use Plack::Builder;
use Plack::Middleware::Static;
use Plack::Session::Store::File;
use IRC::Formatting::HTML qw/html_to_irc/;
use App::Alice::Stream;
use App::Alice::Commands;
use JSON;
use Encode;
use utf8;
use Any::Moose;
use Try::Tiny;
has 'app' => (
is => 'ro',
isa => 'App::Alice',
required => 1,
);
has 'httpd' => (is => 'rw');
has 'ping_timer' => (is => 'rw');
has 'config' => (
is => 'ro',
isa => 'App::Alice::Config',
lazy => 1,
default => sub {shift->app->config},
);
my $url_handlers = [
[ qr{^/$} => \&send_index ],
[ qr{^/say/?$} => \&handle_message ],
[ qr{^/stream/?$} => \&setup_stream ],
[ qr{^/config/?$} => \&send_config ],
[ qr{^/prefs/?$} => \&send_prefs ],
[ qr{^/serverconfig/?$} => \&server_config ],
[ qr{^/save/?$} => \&save_config ],
[ qr{^/tabs/?$} => \&tab_order ],
[ qr{^/login/?$} => \&login ],
[ qr{^/logout/?$} => \&logout ],
[ qr{^/logs/?$} => \&send_logs ],
[ qr{^/search/?$} => \&send_search ],
[ qr{^/range/?$} => \&send_range ],
[ qr{^/view/?$} => \&send_index ],
[ qr{^/get} => \&image_proxy ],
];
sub url_handlers { return $url_handlers }
has 'streams' => (
is => 'rw',
auto_deref => 1,
isa => 'ArrayRef[App::Alice::Stream]',
default => sub {[]},
);
sub add_stream {push @{shift->streams}, @_}
sub no_streams {@{$_[0]->streams} == 0}
sub stream_count {scalar @{$_[0]->streams}}
sub BUILD {
my $self = shift;
my $httpd = Twiggy::Server->new(
host => $self->config->http_address,
port => $self->config->http_port,
);
$httpd->register_service(
builder {
if ($self->app->auth_enabled) {
mkdir $self->config->path."/sessions"
unless -d $self->config->path."/sessions";
enable "Session",
store => Plack::Session::Store::File->new(dir => $self->config->path),
expires => "24h";
}
enable "Static", path => qr{^/static/}, root => $self->config->assetdir;
sub {$self->dispatch(shift)}
}
);
$self->httpd($httpd);
$self->ping;
lib/App/Alice/HTTPD.pm view on Meta::CPAN
if ($self->app->auth_enabled) {
unless ($req->path eq "/login" or $self->is_logged_in($req)) {
my $res = $req->new_response;
$res->redirect("/login");
return $res->finalize;
}
}
for my $handler (@{$self->url_handlers}) {
my $re = $handler->[0];
if ($req->path_info =~ /$re/) {
return $handler->[1]->($self, $req);
}
}
return $self->not_found($req);
}
sub is_logged_in {
my ($self, $req) = @_;
my $session = $req->env->{"psgix.session"};
return $session->{is_logged_in};
}
sub login {
my ($self, $req) = @_;
my $res = $req->new_response;
if (!$self->app->auth_enabled or $self->is_logged_in($req)) {
$res->redirect("/");
return $res->finalize;
}
elsif (my $user = $req->param("username")
and my $pass = $req->param("password")) {
if ($self->app->authenticate($user, $pass)) {
$req->env->{"psgix.session"}->{is_logged_in} = 1;
$res->redirect("/");
return $res->finalize;
}
$res->body($self->app->render("login", "bad username or password"));
}
else {
$res->body($self->app->render("login"));
}
$res->status(200);
return $res->finalize;
}
sub logout {
my ($self, $req) = @_;
my $res = $req->new_response;
if (!$self->app->auth_enabled) {
$res->redirect("/");
} else {
$req->env->{"psgix.session"}{is_logged_in} = 0;
$req->env->{"psgix.session.options"}{expire} = 1;
$res->redirect("/login");
}
return $res->finalize;
}
sub ping {
my $self = shift;
$self->ping_timer(AnyEvent->timer(
after => 5,
interval => 10,
cb => sub {
$self->broadcast({
type => "action",
event => "ping",
});
}
));
}
sub shutdown {
my $self = shift;
$_->close for $self->streams;
$self->streams([]);
$self->ping_timer(undef);
$self->httpd(undef);
}
sub image_proxy {
my ($self, $req) = @_;
my $url = $req->request_uri;
$url =~ s/^\/get\///;
return sub {
my $respond = shift;
http_get $url, sub {
my ($data, $headers) = @_;
my $res = $req->new_response($headers->{Status});
$res->headers($headers);
$res->body($data);
$respond->($res->finalize);
};
}
}
sub broadcast {
my ($self, @data) = @_;
return if $self->no_streams or !@data;
my $purge = 0;
for my $stream ($self->streams) {
try {
$stream->send(@data);
} catch {
$stream->close;
$purge = 1;
};
}
$self->purge_disconnects if $purge;
};
sub setup_stream {
my ($self, $req) = @_;
$self->app->log(info => "opening new stream");
my $min = $req->param('msgid') || 0;
return sub {
my $respond = shift;
my $stream = App::Alice::Stream->new(
queue => [ map({$_->join_action} $self->app->windows) ],
writer => $respond,
start_time => $req->param('t'),
# android requires 4K updates to trigger loading event
min_bytes => $req->user_agent =~ /android/i ? 4096 : 0,
);
$self->add_stream($stream);
$self->app->with_messages(sub {
return unless @_;
$stream->enqueue(
map {$_->{buffered} = 1; $_}
grep {$_->{msgid} > $min}
@_
);
$stream->send;
});
}
}
( run in 0.952 second using v1.01-cache-2.11-cpan-39bf76dae61 )