Saturday, July 2, 2016

Nonlocality

Three years ago, I was porting a Perl CGI-based system to FastCGI, one URL at a time, using mod_rewrite to do the dispatching.  (If the handler.pm exists where the FCGI dispatcher will look for it, invoke this request via FastCGI.)  A consequence of this is that the core library code needs to run on both paradigms, since both front-ends use it.  That runs straight into problems when the CGI access checking functions blithely print and call exit when the access is denied.

Instead of updating 60+ scripts to read e.g. $session = get_session() or Site::CGI->go_login; I decided to get hackier.


I decided to tell the core library that it was in FastCGI mode after loading it in the FCGI dispatcher.  When an access check fails in FastCGI mode, the regular CGI logic is replaced by sending a signal back to the dispatcher.

There's only one way to jump stack frames in Perl, and that's with die jumping out to the nearest eval (and no other).  It's adequate for exception handling, but not superb.  The dispatcher has an eval block, of course, but the death would be intercepted by any other eval blocks between the access check and the dispatcher.  Furthermore, any other kinds of death will be received by the dispatcher, and it must somehow reliably determine the difference between "ordinary access denied signal" and "something horrible went wrong, and this message should be logged."

In Ruby, there are a handful of choices for this: besides continuations or maybe an epic hack around blocks, there's the throw/catch channel for non-exception signals, and the only thing to worry about there is a naming convention to avoid clashes.

In many other languages, there's a fuller exception system, which doesn't require intermediate levels to receive exceptions they can't understand.  (They can, by catching the base exception type; but then, you can be reasonably expected to rethrow once you're done with it.)

In Perl, there is just die, which has all sorts of well-known problems, such as possibly stomping on the global $@ variable.  eval is effectively always catching the base exception type.

Thus, my dispatcher sets the response up in Site::CGI, calls the handler, and down there in the bottom of Site::Common I call Site::CGI->exit_with_template(403) if $_isFCGI;.  That uses the response to set the cgi-error/403.html template, then dies with a new Site::CGI object, which the dispatcher can recognize.  For the other half of the problem, it turns out there are no intermediate layers because the handlers want to let failure propagate to the dispatcher.

Spooky action at a distance, coordinating effort across four files.  I don't expect anyone else to understand it.

No comments: