Should I migrate from CGI::Fast to something else in light of CGI.pm's deprecated status and how should I do so?

Viewed 462

I am using CGI::Fast to leverage the speed and scalability of FastCGI, and I also CGI.pm's query string parsing. I do not use CGI.pm's deprecated HTML writing functions.

Stopping the use of CGI.pm is strongly advised in the community, but in my use case, should I also be thinking of migrating away? And if so, how do I

1) still leverage FastCGI

2) grab query params

...without adopting a framework like Dancer or Mojolicious?

The code I am looking at replacing is just:

 while ( $main::cgi = new CGI::Fast ) {
      my $name = $main::cgi->param('name');
 }

I am open to using something like CGI::PSGI in conjunction with Plack::Request, but I couldn't see how to finesse the FastCGI functionality as CGI::Fast and CGI::PSGI both want to subclass CGI for the creation of the object. And I'd still have CGI.pm in the mix to enable CGI::Fast. Plack seems like a lot to learn to replace what is now a few lines of code.

3 Answers

This neatly illustrates one reason why PSGI is an improvement on CGI (and its related technologies like FastCGI). In CGI-style programs, the code is tightly coupled with the deployment method and when you change the deployment method, you usually need to make quite large changes to the code. With PSGI-style programs, the code is the same no matter how you deploy it.

You haven't shown us any of your code, but you talk about "a few lines of code". So here's the approach I'd take.

  1. Rip all FastCGI-specific code out of your program - leaving a pure CGI-style program.
  2. You then have a choice. You could use the techniques I describe in Easy PSGI to convert your program to a pure PSGI program. Or, if that's going to be too much work, use CGI::PSGI to run your CGI program in a PSGI environment.
  3. You now have a PSGI program that you can use in pretty much any web server environment. You can serve it as a CGI program. You can use Plack::Handler::FCGI to run it under FCGI. Or you can deploy it as a standalone service behind a web proxy (that last option is the one I'd choose).

But any choice you make at step 3 isn't irrevocable. The same code will work in all deployment environments. And, therefore, moving between them is usually pretty simple.

p.s. CGI.pm isn't exactly deprecated. It has been removed from the standard Perl distribution but that's because its use is discouraged.

This is a short guide to help you get started. I'm going to use CGI, not FastCGI, but it's really the same thing. Just imagine the loop being around the parameter selection.

A simple CGI

Let's start with a simple CGI program. I will be using cgi_this to run it for lack of having a real web server handy.

#!/usr/bin/env perl
use strict;
use warnings;
use CGI;

my $q    = CGI->new;
my $name = $q->param('name');

print $q->header;
if ($name) {
  print <<"HTML";
<html><body><h1>Hello, $name</hi></body></html>
HTML
}
else {
  print <<"HTML";
<html><body><form method="GET">
    <label>What's your name? <input type="text" name="name"></label>
</form></body></html>
HTML
}

1;

This is hello.pl. It needs an executable flag.

Running the program

You start it with cgi_this as follows.

$ ls
hello.pl
$ cgi_this
Exporting '.', available at:
   http://127.0.0.1:3000/

Found the following scripts:
    http://127.0.0.1:3000/hello.pl

Now you can open it in the browser.

Firefox with CGI program and form

If you enter a name and submit the form, it will display a greeting.

Firefox with CGI program and greeting

Converting to PSGI

All of this is straight-forward. Now let's convert it.

We'll start with a new file named hello.psgi. It doesn't really matter if it's called .psgi, but this is the convention.

We have to do a couple of steps in order to make it work with the PSGI protocol. We will use Plack::Request to help us do that.

The entire program needs to be wrapped in a my $app = sub { ... }; call.

#!/usr/bin/env plackup
use strict;
use warnings;

use Plack::Request;

my $app = sub {
  my $env = shift;    # this is the Plack environment

  my $req = Plack::Request->new($env);

  my $q    = CGI->new;
  my $name = $q->param('name');

  print $q->header;
  if ($name) {
    print <<"HTML";
<html><body><h1>Hello, $name</hi></body></html>
HTML
  }
  else {
    print <<"HTML";
<html><body><form method="GET">
    <label>What's your name? <input type="text" name="name"></label>
</form></body></html>
HTML
  }
};

# no 1; here, we want it to return $app;

Now obviously we haven't loaded CGI, and there's no CGI environment anyway. So next we need to get the parameter. Get rid of the $q bit and use the $req instead.

my $name = $req->parameters->{name};

Now run this with plackup, then request it at http://localhost:5000.

$ plackup hello.psgi
HTTP::Server::PSGI: Accepting connections at http://0:5000/
<html><body><form method="GET">
    <label>What's your name? <input type="text" name="name"></label>
</form></body></html>
Response should be array ref or code ref: 1 at ...

BANG! It fails, because we're not done yet. As you can see, it wrote the HTML to STDOUT, but that didn't go to the browser. That's because PSGI passes around references, and your program does not talk directly to STDOUT or STDERR.

It also complains about the missing reference. Let's deal with that first. At the end of the code reference, add this:

# prepare the response
my $res = $req->new_response(200);
$res->content_type('text/html');

return $res->finalize;

We're asking our Plack::Request to make a new Plack::Response for us, set a content type, and return the finalized (think immutable) response to the Plack handler, which will serialise and it to the browser as an actual HTTP response.

Now replace all the print statements. Make a new variable $content and instead of printing output, concatenate it to that variable. Then hand that to the response.

  my $content;
  if ($name) {
    $content .= <<"HTML";
<html><body><h1>Hello, $name</hi></body></html>
HTML
  }
  else {
    $content .= <<"HTML";
<html><body><form method="GET">
    <label>What's your name? <input type="text" name="name"></label>
</form></body></html>
HTML
  }

  # prepare the response
  my $res = $req->new_response(200);
  $res->content_type('text/html');
  $res->body($content);

Now restart your app in the terminal and access it again. It will look the same as the CGI.

PSGI version output

Related