How can I run a CGI::Application run mode from the command line?
Asked Answered
P

6

5

I have a run mode in my CGI::Application web-app that I would like to be able to trigger from the command line so i can automate it. From the web-app's perspective it does some processing then sends the results in an email.

When called from the web interface it passes in a set of parameters (email address, which query to run, date, etc) so these need to be passed in.

How can I construct a call to the CGI::Application app that will be the same as if I ran it from the web?

Plugugly answered 16/7, 2009 at 23:39 Comment(0)
P
3

Upon further digging through the CGI::App and the CGI documentation, it appeared to be more straightforward than I thought. The simplest case (no real argument handling or dealing with the output from the webapp run call) is:

#!/usr/bin/perl

use strict;
use warnings;

use CGI;
use WebApp;

my $cgi = new CGI( \%{@ARGV} );

my $webapp = WebApp->new( QUERY => $cgi );
$webapp->run();

It just takes a series of space separated name value pairs to create the CGI. You need to pass in the run mode and all the arguments.

Plugugly answered 17/7, 2009 at 2:26 Comment(2)
True, the 'your_script.pl name1=value1 name2=value2' form works for running the basic CGI::App .cgi file, however I would lose any ability to control input, set defaults etc.Plugugly
This example would still print to STDOUT, including the HTTP headers, which is not desirable. Further, there's not need to involve CGI.pm. It just confuses things when you pass and process arguments directly from the command line with a standard argument processing tool like Getopt::Long.Caffrey
E
6

The original CGI specification makes it easy to run things from the command line and was fully intended not as a specific HTTP-only interface but something that could handle FTP and gopher as well as new top-level URL schemes. I know what I wanted when I helped specify it.

The spec I referenced should give you all you need, but for the most part it is just a collection of environment variables. If you see a request for:

http://some.server.com/some/path?a=b&c=d

The environment variables come out looking like this:

SERVER_PROTOCOL=http
REQUEST_METHOD=GET
HTTP_HOST=some.server.com
SERVER_PORT=80
PATH_INFO=/some/path
QUERY_INFO=a=b&c=d

To reverse the polarity of that in Perl would go something like this:

$ENV{'SERVER_PROTOCOL'} = 'http';
$ENV{'REQUEST_METHOD'} = 'GET';
$ENV{'SERVER_PORT'} = 80;
$ENV{'PATH_INFO'} = '/some/path';
$ENV{'QUERY_INFO'} = 'a=b&c=d';
system("perl your-CGI-script.pl");

Things get a bit more complicated in handling POST queries and there are more possible environment variables that may be required. Worst case you can enumerate them all with a quick CGI script something like:

print "Content-Type: text/plain\r\n\r\n";
foreach (keys(%ENV))
{
    print "$_=$ENV{$_}\r\n";
}

Now put that on the web server in place of your CGI script and you'll see all the environment that gets passed in (and the original environment so you'll need to make a few judgement calls).

Eggleston answered 17/7, 2009 at 0:41 Comment(2)
When I do that, I normally use an exec. No big whoop though.Caprification
Cool, you helped specify CGI. :)Piliform
P
3

Upon further digging through the CGI::App and the CGI documentation, it appeared to be more straightforward than I thought. The simplest case (no real argument handling or dealing with the output from the webapp run call) is:

#!/usr/bin/perl

use strict;
use warnings;

use CGI;
use WebApp;

my $cgi = new CGI( \%{@ARGV} );

my $webapp = WebApp->new( QUERY => $cgi );
$webapp->run();

It just takes a series of space separated name value pairs to create the CGI. You need to pass in the run mode and all the arguments.

Plugugly answered 17/7, 2009 at 2:26 Comment(2)
True, the 'your_script.pl name1=value1 name2=value2' form works for running the basic CGI::App .cgi file, however I would lose any ability to control input, set defaults etc.Plugugly
This example would still print to STDOUT, including the HTTP headers, which is not desirable. Further, there's not need to involve CGI.pm. It just confuses things when you pass and process arguments directly from the command line with a standard argument processing tool like Getopt::Long.Caffrey
C
3

I'm the maintainer of CGI::Application, and I do this all the time-- I have dozen of cron scripts built with CGI::Application because it's convenient to share the infrastructure with the application.

The simplest approach is this:

# There is no browser to return results to.
$ENV{CGI_APP_RETURN_ONLY} = 1;

my $app = WebApp->new;
$app->direct_run_mode_method;

In that example, you bypass the normal flow and call a method directly. Be sure you don't need any of the "setup" or "teardown" actions to happen in that case.

If you just have one run mode you are calling, you can also just set the "start_mode", and call run(), so then the default run mode is called by default.

Another idea: you can use a module like Getopt::Long and pass in values through the PARAM hash to new(), or completely replace the run-mode selection process. Here's an example where command line flags are used to determine the run mode:

sub setup {
    my $self = shift;

    $self->start_mode('send_error_digests');
    $self->run_modes([qw/
        send_error_digests
        help
    /]);

    my ($dry_run, $help);
    GetOptions(
        'dry-run' => \$dry_run,
        'help'    => \$help
    );

    $self->param('dry_run' => $dry_run);

    $self->mode_param(sub {
        return 'help' if $help;
        return $self->start_mode();
    });
}
Caffrey answered 4/5, 2011 at 13:51 Comment(0)
B
0

Thusly:

$ perl yourscript.pl  field1=value1 field2=value2

Perl's CGI library takes care of the magic for you, and it appears that CGI::Application relies on CGI (judging from their example code).

Bridegroom answered 16/7, 2009 at 23:42 Comment(3)
You'll also need to set various environment variables to simulate the stuff the web server would set up. CGI.pm looks for those so it knows what to do.Caprification
@brian d foy: not unless your code for some reason depends on those; CGI.pm does fine without them.Orchitis
@ysth: if this run mode has side effects, it should be using POST since its not an idempotent request. In that case, you have to add -debug to CGI.pm's import to allow this stuff to work. That's a change to the code which has side effects too. It's much easier to do it as George says.Caprification
C
0

Instead of having to go through CGI::Application every time you want to get something done, enforce a proper separation of concerns, perhaps using an MVC setup. All of the functionality should exist outside of the CGI::Application stuff since that should only work as a controller. Once you separate out those bits, you can easily write other controllers for other input methods.

Don't write a web application; write an an application that happens to have a web interface. When you have that, you can easily give your application other sorts of interfaces.

Caprification answered 16/7, 2009 at 23:45 Comment(4)
I completely agree, there is a library that does the actual work generating the result and i've tried to limit the amount of code in the 'Controller' portion of the CGI::A to the point where its just formatting. Its just the 'Laziness' part of me that thinks I could use the CGI::App to send the email rather than write another script to call the library.Plugugly
Well, Laziness with a capital L allows you to easily do other tasks without future work: that Laziness is not the avoidance of work but the upfront work to save time later.Caprification
(obligatory reference to YAGNI, although I tend to operate in capital L mode most of the time)Bridegroom
It's ironic to mention YAGNI in a case where he's actually asking for it.Caprification
K
0

You could automate by calling the web app using curl, wget, or an LWP GET-script with the appropriate parameters. I've used a similar system for cron-driven tasks with a Catalyst application.

That deals with all the environment variables for you..

Ka answered 19/7, 2009 at 23:36 Comment(1)
This solution adds an additional problem to solve: Now you have to make sure the URI of the cron job is protected, so that it can only be accessed from the cron job.Caffrey

© 2022 - 2024 — McMap. All rights reserved.