Andrew Fresh

andrew@afresh1.com

Twitter/App.net/ello @AFresh1

Duct Taping Some P to Your CGI

Bringing some old perl closer to the future

At work, we have apps that make money and perform as they need to, but don't have enough changes that it makes sense to spend the time to rewrite them in Modern Perl.

Where are we starting?

Why change what works?

Decided to turn it into a Plack app

Lets do this

Challenges

What am I going to do?

Ended up with CGI::Emulate::PSGI

my $psgi_app = CGI::Emulate::PSGI->handler( sub { $app->handle_legacy_request() } );

app.psgi

 use CGI::Emulate::PSGI;
 ...;

 my $psgi_app = CGI::Emulate::PSGI->handler( sub {
    ...;
    $app->use($site); # changes $app->site

    # Nginx would normally do this.
    # If we're running under a different server, we may not get that.
    (my $path_info = $ENV{PATH_INFO} || '') =~ s{^/}{};
    if ( $path_info and my $file = $app->find_htdoc($path_info) ) {
        return serve_static($file);
    }

    return $app->handle_legacy_request;
 } );

 # From Plack::App::File which doesn't work with an ever changing $app->site.
 sub serve_static { ...; }

Removed the Apache module

 -    Apache::Cookie->new($r, -name  => $name, -value => $value)->bake;
 +    http_header( 'cookie' => CGI::cookie( -name  => $name, -value => $value ) );

 -    Apache->request->status(301);
 -    http_header( Location => $location );
 -    send_http_header();
 +    print CGI::redirect( -uri => $location, -status => '301 Moved Permanently' );

 # After sending the headers:
 +    $GLOBAL{HEADER_SIZE} = tell STDOUT;
 # Then at the end of the request
 -    my $bytes_sent = Apache->request->bytes_sent;
 +    my $bytes_sent = tell(STDOUT) - ($GLOBAL{HEADER_SIZE} || 0);

Changes from perl/system replacement

Annoyance that still needs work

Interesting Tidbits

Questions?

Questions?

Questions?

Andrew Fresh

Twitter/App.net/ello @AFresh1

andrew@afresh1.com

Thank you!

As far as I know

Grantstreet Group

is hiring.

use strict;
our %GLOBAL;

my %key_map = (
    #'status', #'-expires',
    '-content_type' => '-type',
    '-set_cookie'   => '-cookie',
    '-cookies'      => '-cookie',
);

my %allowed_multiple_values = map { $_ => 1 } (
    '-cookie',
    '-p3p',
);

my @HTTP_HEADERS;    # file local variable

sub http_header { push @HTTP_HEADERS, [@_] if @_ }

sub send_http_header {
    my $content_type = shift;

    push @HTTP_HEADERS, [ type => $content_type ] if $content_type;

    if ( $GLOBAL{id} ) {
        push @HTTP_HEADERS, (
            [ 'cache-control' => 'no-cache,no-store,must-revalidate' ],
            [ 'expires'       => '-1' ],
        );
    }

    my %headers;
    foreach (@HTTP_HEADERS) {
        my ( $key, $value ) = @{ $_ };

        # make the keys uniform so we throw away any duplicates
        $key = lc $key;     # lowercase
        $key =~ s/^-//;     # remove an optional leading daesh
        $key =~ s/-/_/g;    # change dashes to underscores
        $key = "-$key";     # and put back the required leading dash
        $key = $key_map{$key} || $key;    # see if we have a custom map

        next if $key eq '-';    # no empty headers

        if ($allowed_multiple_values{$key}) {
            push @{ $headers{$key} }, $value;
        }
        else {
            $headers{$key} = $value;
        }
    }

    @HTTP_HEADERS = ();                      # Reset

    print CGI::header(%headers);

    $GLOBAL{HEADER_SIZE} = tell STDOUT;
}