Modern Perl CGI
The contemporarily unique strengths of CGI as a deployment strategy are that CGI scripts ⓐ can just be dumped in the filesystem to deploy them and ⓑ do not have any of the issues of long-running processes: they tie up no resources when not in use and are extremely reliable because of the execution model, in which global state always starts from a blank slate when serving a request and there is no process that outlives the request and could wedge itself. Anyone who consciously chooses CGI over alternative deployment strategies nowadays probably has a fire-and-forget use case where the script will be seeing too little traffic to be worth any effort to tend to it regularly.
In his article about modern CGI, Grinnz suggested using Mojolicious rather than CGI.pm as a framework for writing CGI scripts. Mojolicious is explicitly intended for users who are willing to keep changing their own application code in order to enjoy a framework whose API design can be changed (hopefully for the better) without sacrificing the framework’s code quality. In the Venn diagram of the CGI-for-deployment and Mojolicious-for-framework audiences, there is no overlap. So I consider Mojolicious an oxymoronic alternative to CGI.pm.
A much better alternative to CGI.pm is simply raw Plack. It is lower-level than Mojolicious, so the code will be more verbose, but Plack’s stance on compatibility matches a fire-and-forget use case far better. CGI::Alternatives does not do a great job of selling that option, so let’s just see what it would look like in practice for the given example.
In the mentioned article, Grinnz presents a hypothetical uppercase.cgi
:
#!/usr/bin/env perl
use strict;
use warnings;
use CGI;
use Encode::Simple;
use JSON::MaybeXS;
use Syntax::Keyword::Try;
my $cgi = CGI->new;
try {
my $input = decode 'UTF-8', scalar $cgi->param('input');
print $cgi->header('application/json; charset=UTF-8'), encode_json {output => uc $input};
} catch {
print $cgi->header('text/html; charset=UTF-8', '500 Internal Server Error'), '<h1>An error has occurred.</h1>';
die $@;
}
In a real CGI scenario, with each request being processed with a blank slate of global state, eval
’s pitfalls do not really come into play, so we can leave out Syntax::Keyword::Try. In fact, the Mojolicious example derives most of its concision from the lack of any error handling, which we can do just as well even with raw CGI.pm:
#!/usr/bin/env perl
use strict;
use warnings;
use CGI;
use Encode::Simple;
use JSON::MaybeXS;
my $cgi = CGI->new;
my $input = eval { decode 'UTF-8', scalar $cgi->param('input') };
print $cgi->header( 'application/json; charset=UTF-8' ), encode_json { output => uc $input };
A modernisation of this using Plack is straightforwardly equivalent:
#!/usr/bin/env perl
use strict;
use warnings;
use Plack::Request;
use Encode::Simple;
use JSON::MaybeXS;
my $app = sub {
my $req = Plack::Request->new($_[0]);
my $input = eval { decode 'UTF-8', $req->parameters->{'input'} };
[ 200, [ 'Content-Type', 'application/json; charset=UTF-8' ], [ encode_json { output => uc $input } ] ];
};
This is now a full PSGI app and as such gains all the same additional deployment options of the Mojolicious example. To deploy it as a CGI script you add this line at the bottom:
use Plack::Handler::CGI; Plack::Handler::CGI->new->run($app);
In all it takes about twice as long to load as the CGI.pm example, compared to the Mojolicious example taking about 7× as long. In exchange you get all the benefits of the PSGI ecosystem – without losing any of the strengths of deploying as a CGI script, unlike the Mojolicious example.
I appreciate the example of a lower-level alternative, since my post only showed a Mojolicious solution for brevity and because that is where my experience is, and this is certainly a good alternative as well. However, "the Mojolicious example derives most of its concision from the lack of any error handling" is incorrect, since error handling (similar to that I added to the CGI.pm example, but much more useful) is built in to Mojolicious, as well as most other higher level alternatives. It is a common problem with CGI.pm-based scripts that unhandled exceptions result in no useful response to the user at best, and broken/missing response headers at worst.
Good points. I've added some more details to the CGI::Alternatives docs to include this: https://github.com/leejo/cgi-alternatives/commit/c3473c013a58b80fc841125cf07abc94d5981202
v0.16 on its way to CPAN (as always: PR welcome on this if the docs can be improved, updated, etc, please make suggestions)
I think that an even easier way to deploy code in a CGI environment is to just change the shebang line to
#!/usr/bin/plackup
No need to add the Plack::Handler code.
Dave, that won’t work exactly as written because
plackup
is a script, and you can only use binaries in the shebang. You would have to useBut I recommend against this. Going through
plackup
via the shebang line means means that every request requires loading Plack::Runner and then Plack::Loader only to ultimately pass the app to Plack::Handler::CGI… same as you can just do directly. But it doesn’t allow you to deploy an unmodified PSGI app as a CGI script, so you gain nothing more than a little bit of syntactic sugar and you pay for it with pure overhead indirection on every request.