Catalyst for CGI programmers

IMPORTANT: This document is a work in progress. The original can be found at http://jc.ngo.org.uk/svnweb/jc/browse/nik/CPAN/Catalyst-Tutorial-CGI/trunk/

AUDIENCE

This document is intended for people who are familiar with the basic concepts of web programming, and who probably have a familiarity with CGI.pm.

Existing Catalyst documentation requires you to take in a large number of (quite possibly unfamiliar) concepts before Catalyst becomes understandable. This document aims to introduce those concepts in a linear fashion so that they are easier to comprehend.

WHAT CATALYST DOES

Catalyst carries out many of the operations that a web application has to do in a manner that frees the programmer from having to worry about them.

To compare with CGI: if you <use CGI; you are bringing a library that implements many features relating to CGI programming in to your program. But you still have to write the entirety of the program.

So a typical program that uses CGI will have a startup routine, a routine that parses the CGI parameters in to useful values, and a mechanism that calls various routines in your application depending on the URL that was used and the parameters specified. Your application then has to generate output (typically, but not always, HTML), and send that to the browser.

Catalyst, by contrast, is a framework. It determines, based on the URL, which subroutines to call, and provides mechanisms to return content to the browser.

CGI is a library that you use with your application.

    +------------+
    | Web server |
    +---+--------+
        |
  +-----|---[ Your Application ]--+
  |     V                         |
  | +-------------+    +--------+ |
  | | routine_1() | -> |        | |
  | +---+---------+    |        | |
  |     |              |        | |
  | +---v---------+    |        | |
  | | routine_2() | -> | CGI.pm | |
  | +---+---------+    |        | |
  |     |              |        | |
  | +---v---------+    |        | |
  | | routine_3() | -> |        | |
  | +---+---------+    +--------+ |
  |     |                         |
  +-----|-------------------------+
        V
    +------.
    |      |\
    |      --|
    |  HTML  |
    |        |
    |        |
    +--------+

Catalyst is a framework that provides the structure of the application, where you fill in the blanks to provide the logic of your application.

    +------------+
    | Web server |
    +---+--------+
        |
  +-----|---[ Your Application ]--+
  |     V                         |
  | +----------+  +-------------+ |
  | |          |->| routine_1() | |
  | | Catalyst |  +-------------+ |
  | |          |                  |
  | |          |  +-------------+ |
  | |          |->| routine_2() | |
  | |          |  +-------------+ |
  | |          |                  |
  | |          |  +-------------+ |
  | |          |->| routine_3() | |
  | +---+------+  +-------------+ |
  |     |                         |
  +-----|-------------------------+
        V
    +------.
    |      |\
    |      --|
    |  HTML  |
    |        |
    |        |
    +--------+

GETTING STARTED

Creating your application

As a framework, Catalyst takes care of a great many of the details for you. However, since your application is going to be embedded in the framework, it stands to reason that you need to create the framework as the first thing you do when writing your application.

To create the framework use catalyst (which may have been installed as catalyst.pl.

% catalyst MyApp
created "MyApp"
...

This has created a great many files. Don't worry, you don't need to understand their purpose yet.

Running your application

One of the very useful tools that Catalyst includes is a simple webserver. This allows you to do development and debugging of your application without needing to configure a full webserver.

Catalyst's webserver also includes a very useful feature -- it can detect when critical files in your application have changed and automatically reload the relevant files. No more having to stop and start the webserver every time you make a change.

To run your application

% cd MyApp
% script/myapp_server.pl -r -p 3000

This will start the webserver on port 3000. Adjust the -p 3000 if you need to change that. 3000 is also the default port, so the option can be omitted if you wish. -r is the option that instructs the server to reload the application after it detects a change.

If you start a web browser and go to http://localhost:3000/ you will see the default page for your application.

As you do this you should see a healthy amount of debug information scroll up the terminal in which you ran myapp_server.pl.

WRITING "hello, world"

Synopsis

Let's write the canonical hello, world application. The application will respond on the URL /greet/hello and print

hello, world

In this section you will learn:

  • How Catalyst maps URLs to functions
  • How to return data to the browser

The controller

Catalyst makes it easy to use the Model/View/Controller pattern when writing your application. You may not be familiar with this pattern at this point. Don't worry, you don't need to be.

As a framework, Catalyst takes care of processing the CGI request for you. It parses the URL and any of the parameters, and determines which routine in your application to call.

Consider the URL that we want to use

/greet/hello

When Catalyst receives a request for that URL it looks in your application and determines which routine to call. The first place it will look is:

MyApp::Controller::Greet::hello()

As you can see, the URL has been broken down in to its path components. The last item in the path becomes a subroutine name, and the remainder of the items become packages rooted at MyApp::Controller.

If you check the lib/ directory that catalyst created you'll see that lib/MyApp/Controller exists, but there are no files in that directory. So it stands to reason that we need to create Greet.pm.

Greet.pm will need to contain a certain amount of boilerplate code (or scaffolding) to work with Catalyst. Rather than require you to enter this each time, Catalyst includes a number of helper applications to create this scaffolding for you.

To create Greet.pm with the appropriate scaffolding run

% script/myapp_create.pl controller Greet
created ".../lib/MyApp/Controller/Greet.pm"
created ".../t/controller_Greet.t"

This has created the controller in Greet.pm and a basic test suite.

Fleshing out the controller

Load Greet.pm in to your editor, and take a quick look at the boilerplate code that myapp_create.pl created for you. It should look relatively simple.

Now we have to create the routine, hello(), that will be called when the user visits /greet/hello. To do that, cut/paste the following code in to Greet.pm below the

=head1 METHODS

=cut

block:

sub hello : Local {
    my($self, $c) = @_;

    $c->response()->body('hello, world');
}

and restart myapp_server.pl. You should notice the following in the output from myapp_server.pl.

+------------------+----------------------------------+------------+
| Private          | Class                            | Method     |
+------------------+----------------------------------+------------+
| /default         | MyApp                            | default    |
| /greet/hello     | MyApp::Controller::Greet         | hello      |
+------------------+----------------------------------+------------+

This is Catalyst's way of telling you, for each URL (in the Private column) the name of the class and method that will be called when that URL is requested. As you can see, /greet/hello will result in a call to hello() in MyApp::Controller::Greet`.

Test this by pointing your browser at http://localhost:3000/greet/hello.

Under the covers

Let's pick hello() apart, and see what's actually happening.

Line 1

The first line is simple enough.

sub hello : Local {

This starts the definition of a new subroutine, called hello. That much is standard Perl. : Local is specific to Catalyst.

: Local is a subroutine attribute. This attribute tells Catalyst that hello() should be treated as an action.

When I said earlier that Catalyst takes URLs and turns them in to subroutine calls, that wasn't strictly true.

Catalyst takes URLs and turns them in to calls to actions. An action is a subroutine that has been labeled with an attribute that indicates it's an action. : Local is one such label. It tells Catalyst to take the full name of this routine (MyApp::Controller::Greet::hello(), strip off the MyApp::Controller:: portion, and form the URL from the remaining components, lowercasing them as necessary. Which leads to /greet/hello in our example.

There are other labels that can be used on subroutines to indicate that they are actions, and we will come to them later.

Line 2

Line 2 unpacks the parameters passed to this routine.

my($self, $c) = @_;

Catalyst is object-oriented. The code you write in Greet.pm is providing methods for objects in the MyApp::Controller::Greet class.

Accordingly, every routine is going to be called as a method. This means that the first parameter will always be a reference to the object instance, and, following Perl convention, this example calls it $self.

The second parameter, $c, is called the Context. It is automatically provided as the second argument to every action you may write, and is conventionally named $c, although you may use any name you wish.

The context provides methods to retrieve several different objects that are used for processing the current request:

  • Catalyst::Request, $c->request();
  • Catalyst::Response, $c->response();
  • Catalyst::Config, $c->config();
  • Catalyst::Log, $c->log();
  • Catalyst::Stash, $c->stash();

More on these objects later. For now it is enough to know that the Context exists, and that it is the second argument passed to actions.

Line 4

The last line tells Catalyst what our response to the browser is going to be.

$c->response()->body('hello, world');

$c->response() returns a Catalyst::Response object. This object provides methods for responding to the current request.

The body() method gets or sets the body of the response that will be sent back to the browser. In this code we use it to set the body to the string hello, world. This could have been written as two lines;

my $response = $c->response();
$response->body('hello, world');

but the method chaining syntax results in slightly less typing.

This may be a big change in style for you. "Traditional" CGI programming allows you to write a program that generates output piecemeal. The following is common;

print func_that_returns_html();
print another_func_that_returns_html();
print final_func_that_returns_html();

The analogous process in Catalyst would be:

my $output = func_that_returns_html();
$output .= another_func_that_returns_html();
$output .= final_func_that_returns_html();

$c->response()->body($output);

PROCESSING CGI PARAMETERS

Synopsis

Web applications often need to process parameters, either given in the URL as a path, or provided as CGI parameters sent using GET or POST requests.

In this section you will learn:

  • How to process CGI parameters

Before reading this chapter you should:

  • Have carried out the steps in the previous chapter.

Greeting by name

In the previous chapter you created a simple Catalyst application that responded on a single URL. In this chapter we will extend this application to support a new URL, and to process parameters that are passed.

Our root URL will remain /greet. However, we will support an additional URL /greet/by_name. This will take a CGI parameter, called name, that gives the name of the person to greet. This will lead to a URL like

http://localhost:3000/greet/by_name?name=nik

Implementing by_name()

Edit Greet.pm and add the following routine.

sub by_name : Local {
    my($self, $c) = @_;

    my $name = 'unknown';

    $c->response()->body("hello, $name");
}

This should be straightforward. It's almost identical to hello(). However, this time there's a $name variable used in the greeting, currently set to unknown.

We need to extract the value of the name CGI parameter and assign it to $name.

Extracting parameters

Using CGI you would write something like this:

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

$name = 'unknown' unless defined $name;

The Catalyst approach is almost identical. However, instead of using a CGI object you would use a [Catalyst::Request][http://search.cpan.org/~mramberg/Catalyst-Runtime-5.7012/lib/Catalyst/Request.pm) object.

Catalyst::Request is a class that provides methods for extracting information about the current request, including the CGI parameters. It features methods that deliberately have similar or identical names to their CGI counterparts.

Replace my $name = 'unknown'; in by_name() with these lines:

my $req  = $c->request();
my $name = $req->param('name');

$name = 'unknown' unless defined $name;

The first line retrieves the Catalyst::Request object for the current request. It does that using the request() method of the current context $c, and assigns the result to $req.

The second line uses the Catalyst::Request::param() method to retrieve the value of the parameter called name and assign it to $name.

Finally, the name is set to unknown if no value was passed in.

My tags:
 
Popular tags:
 
Powered by Catalyst
Powered by MojoMojo Hosted by Shadowcat - Managed by Nordaaker