The New Perl Way: "You're doing it wrong"


use CGI;

“I’m sorry, Dave, I can’t do that.”

cpanm CGI

“You really shouldn’t use that any more. It’s bad for you.”

perldoc CGI

“The rationale for this decision is that CGI.pm is no longer considered good practice for developing web applications, including quick prototyping and small web scripts. There are far better, cleaner, quicker, easier, safer, more scalable, more extensible, more modern alternatives available at this point in time. These will be documented with CGI::Alternatives.”

perldoc CGI::Alternatives

No documentation found for “CGI::Alternatives”.

cpanm CGI::Alternatives
perldoc CGI::Alternatives

“Let me build this strawman that doesn’t actually make good use of CGI.pm to show you how you can easily switch to one of half a dozen different frameworks that let you use half a dozen different templating systems launched with half a dozen different embedded web servers, and replace your self-contained 100-line CGI script with half a dozen files located in half a dozen directories. For more fun, my sample code gets mangled if you try to view it as a manpage, so you really should download the raw file from CPAN.”

cpanm --uninstall CGI::Alternatives
cpanm Dancer2
perldoc Dancer2
cpanm --uninstall Dancer2
cpanm Mojolicious
perldoc Mojolicious
perldoc Mojolicious::Lite

use Mojolicious::Lite;
plugin CGI => [ '/' => "trivialscript.cgi" ];
app->start;

use CGI;


I thought it would be easy, like in the deal. Knock together a quick Perl script with some command-line options for easy debugging, then add a little bit of GUI-glue to create a simple HTML form that allows me to play with the output and easily tweak the script to add features. Y’know, the way the web was built.

Nope. It’s all

  • Thou Shalt Pick A Templating Language.
  • Thou Shalt Spread Thy Code, Data, And Presentation Across The File System.
  • Thou Shalt Copy Ruby And Python, For Blessed Are The Web-Children.

I couldn’t find a single example that showed how to easily replace the straightforward use of CGI.pm and its helper functions with any of the “quicker, easier” alternatives. Pretty much every tutorial starts with how to build a database-backed login page with the form elements coded by hand, which is pretty much the exact opposite of what I wanted.

I’m sure all of these frameworks look great on your Github résumé, but I was just trying to do something quickly and easily, things that used to be Perl’s strengths. Hell, I was using Mason back in 2002 (and since it’s still around, I may rebuild my old project pages at some point), so it’s not like I wasn’t aware there were alternatives to CGI.pm, but what does the current Mason tutorial do? Show you how to build a database-backed login page with the form elements coded by hand. Sigh.

What was the project? A complete rewrite of my random-word generator from 2003. The old one was a straightforward digram generator loosely based on Chris Pound’s scripts, that mixed-and-matched his language sample files. The new one is based on looking at the old one and finding nothing worth keeping.

Instead of using digrams again or generalizing to n-grams, I got the idea to go with n-character unigrams. That is, for N = 3, an n-gram generator would take an input word like “ageless”, break it up into (age, gel, ele, les, ess), and use it to decide what character can come after “el”. My way splits “ageless” into (age, les, s), picks a start token, and then randomly selects valid transition tokens until it reaches an end token.

The fun comes when you feed it multiple source files. It randomly selects from all the valid tokens at each step, so you can transition from ageless to hanageishi to firishtih, ending up with “ageishtih”. IMHO, these discrete N-character tokens preserve a little more of the distinct flavor of the source files, so that a Lovecraft/Norse mix (Móulýr, Dýcgug) is clearly different from a Japanese/Irish/Jargon mix (Mockinkotoji, Tarindent), and, most importantly, feels like a Lovecraft/Norse mix.

Being able to (coughcough) quickly, easily generate the web UI with CGI.pm made it possible to focus on the code, improving the performance and output quality, and I was even able to make it vaguely pretty with Bootstrap, since I’d already learned the basics of that when I rebuilt the blog in Hugo (whose templating system has exactly nothing in common with any of the Perl template modules, of course; so many new wheels being invented, and not one of them as round as the old ones…).

I’ll put it online soon. Now that it’s pretty much done, I’m willing to go through the pain of figuring out how to use Mojolicious::Lite directly rather than through the CGI shim.

Update

That was… painful. Parameter handling is clumsy as hell, and rendering is poorly-documented black magic. Actually, I’d give a D to all the Mojolicious documentation, because it tells you everything about implementation details and not a damn thing about usage. As far as I can tell, the only way to find out if you’ve been called in response to a POST is to see if the array referenced by $c->req->body_params->pairs has non-zero length. Yes, you have POST parameters; no, you don’t.

But you get a very pretty debug page when something goes wrong! It’s not particularly useful, and you need to make sure to disable it before releasing your web app (while also overriding the rainbow-vomiting-dinosaur error page that no sane person would ever want to display), but gosh is it pretty!

How did I end up rendering a page that’s a mix of static text, dynamic form data, and multiple columns of randomly-generated words? By building up a large string in memory and then saying $c->render(text => $entirepage). Because you only get to call render once per route, apparently, and the last call wins. I guess most of your logic is supposed to be in the fucking templates.

“Quicker, easier”, my ass.

Anyway, the new generator is here.

One week later…

I moved the form-rendering to the “proper” embedded-perl tag helpers, which was deeply annoying, because the context switch between the real Perl and the embedded made it difficult to preserve the state of checkboxes, which are simply not passed in a POST if their state is off. I ended up having to duplicate the top-level route code, so that the default “on” checkbox value only gets passed to the form on a GET, allowing the oversimplified template logic to work. Also, I took a look at their form validation support, but since I’m more interested in input sanitization, it was a poor fit.


Comments via Isso

Markdown formatting and simple HTML accepted.

Sometimes you have to double-click to enter text in the form (interaction between Isso and Bootstrap?). Tab is more reliable.