Slide 1

Slide 1 text

Exploring Plack Middlewares Oleg Komarov 2013-03-15

Slide 2

Slide 2 text

Section 1 Overview: PSGI & Plack

Slide 3

Slide 3 text

PSGI Perl Web Server Gateway Interface https://metacpan.org/module/PSGI web servers PSGI server/application container provides environment applications mappings: environment -> response

Slide 4

Slide 4 text

Benefits portability reusability/composability frameworks support Mojolicious::Plugin::MountPSGI $self->plugin("MountPSGI", {"/", => "ext/MyApp/app.psgi"} ); Catalyst: use MyApp; MyApp->setup_engine("PSGI"); my $app = sub {MyApp->run(@_)};

Slide 5

Slide 5 text

Frameworks Figure: with Dancer

Slide 6

Slide 6 text

Hello world sub { my $env = shift; return [ 200, ["Content-Type" => "text/plain"], ["Hello World"], ]; }

Slide 7

Slide 7 text

$env An unblessed hashref CGI keys without period REQUEST METHOD SCRIPT NAME QUERY STRING . . . PSGI-specific keys psgi.version psgi.url scheme psgi.input psgi.errors psgi.multithread psgi.multiprocess psgi.run once psgi.nonblocking psgi.streaming

Slide 8

Slide 8 text

Middleware package Plack::Middleware::Foo; use parent qw(Plack::Middleware); sub call { my ($self, $env) = @_; # pre-process $env my $response = $self->app->($env); # post-process $response return $response; } # or for delayed response Plack::Util::response_cb($response, sub { my $response = shift; ... })

Slide 9

Slide 9 text

How to use it use Plack::Middleware::Foo; ... $app = Plack::Middleware::Foo->wrap($app, %options);

Slide 10

Slide 10 text

Easier with Plack::Builder builder { enable "Foo", foo => 1, bar => 2; enable "Baz"; ... $app; };

Slide 11

Slide 11 text

Access to %options package Plack::Middleware::Foo; use parent qw(Plack::Middleware); use Plack::Util::Accessor qw(foo bar); sub call { ... $self->foo $self->bar ... }

Slide 12

Slide 12 text

Prepare sub prepare_app { my $self = shift; die "it’s boring" unless $self->fun; }

Slide 13

Slide 13 text

Top-level logic builder { if ($location eq "Rome") { enable "Behaviour::Roman"; } ... };

Slide 14

Slide 14 text

Top-level logic builder { ... enable_if {$visitor_count++ == 1E6} "ShowBanner"; ... };

Slide 15

Slide 15 text

Top-level logic builder { enable_if {$_[0]->{QUERY_STRING} =~ /&debug=1/} "Debug" ... };

Slide 16

Slide 16 text

Top-level logic builder { enable "Foo", cb => \&a_cunning_plan; ... };

Slide 17

Slide 17 text

Section 2 Examples

Slide 18

Slide 18 text

Replacing parts of Apache Access Auth:: ErrorDocument Rewrite SSI

Slide 19

Slide 19 text

And even more Cache Header logging profiling SizeLimit Throttle

Slide 20

Slide 20 text

Developer tools Debug Delay InteractiveDebugger REPL

Slide 21

Slide 21 text

Developer tools

Slide 22

Slide 22 text

Aspect (lack of) Figure: use Aspect!

Slide 23

Slide 23 text

unless is production() unless (is_production()) { enable "DevTools"; }

Slide 24

Slide 24 text

DevTools with Aspect package Plack::Middleware::DevTools; use parent qw(Plack::Middleware); use Aspect (); sub call { my ($self, $env) = @_; my @hooks; ... # hooks ... my $response = $self->app->($env); # extract data from $env undef $env; return $response; }

Slide 25

Slide 25 text

Tracing hook $env->{"devtools.http"} = []; push @hooks, Aspect::before { my (undef, $url) = $_->args; push @{$env->{"devtools.http"}}, $url; } Aspect::call qr/^LWP::UserAgent::(?:get|post)$/;

Slide 26

Slide 26 text

Faking hook push @hooks, Aspect::after { my @args = $_->args; my $true_result = $_->return_value; my $fake_result = get_fake_result($true_result, @args); $_->return_value($fake_result); } Aspect::call "API::method" & Aspect::returning;

Slide 27

Slide 27 text

Find missing translations push @hooks, Aspect::after { if ($cur_lang ne $default_lang) { my $msgid = [$_->args]->[0]; my $msgstr = [$_->return_value]->[0]; if ($msgid eq $msgstr) { # replace into db } else { # delete from db } } } Aspect::call qr/::gettext$/;

Slide 28

Slide 28 text

Section 3 Refactoring existing code

Slide 29

Slide 29 text

Dependencies Plack::Builder might have provided information about outer middlewares (but it doesn’t) Communication via $env P::M::MyAuth sets $env->{"myauth.user id"} P::M::MyUser fetches user data from DB using $env->{"myauth.user id"} and puts it in $env->{"myuser.user"}

Slide 30

Slide 30 text

Special-purpose middlewares E.g. P::M::MyAuth and P::M::MyUser Captcha FakeAuth MailFatals Reqid

Slide 31

Slide 31 text

Thank you Questions? https://github.com/komarov