CGI and mod_perl
Speaker: Juerd
Duration: an hour
Dutch Perl Workshop 2004
Probably only works correctly in Mozilla
(?:Fire(?:Bird|Fox))? (F11 for full screen) and other Gecko based
browsers. On top of that, I use a font that probably almost nobody has :)
The slides are generated by a little script(text/plain) that uses a large semi-html-file to create separate files using
this template. Het Nederlandse origineel is hier.
Contents
- HTTP and HTML
- CGI
- Templates
- Wizards
- Sessions
- Cookies
- mod_perl
HTTP
- HTTP is sessionless
- HTTP is simple
- Few web coders know HTTP
HTTP request
- Request line
GET /cgi-bin/foo?bar=baz HTTP/1.1
or POST /cgi-bin/bar HTTP/1.1
- Headers
Host: www.greatsite.com
User-Agent: Mozilla/4.5 (Compatible; bleat)
Accept: text/html, text/plain
- Empty line
- Body (with POST requests)
HTTP response
- Status line
HTTP/1.1 200 OK
or HTTP/1.1 404 Not found
or HTTP/1.1 500 Internal Server Error
or ...
- Headers
Content-Type: text/html
- Empty line
- Body (the page itself)
HTTP abuse
- Abusing HTTP is easy
- Especially for RPC
- SOAP
- HTTP::Daemon / LWP::UserAgent
- Free proxy support
HTML forms
<form method="POST" action="test.cgi">
- method = GET or POST
- action = URL
- Input fields, drop-downs, buttons
- Equal names => array in Perl!
HTML > CGI
- End of HTTP/HTML
- Questions?
- Next: CGI
CGI
- Not Perl specific!
- STDIN has the POST body
- STDOUT goes to the browser
- %ENV is filled with info
- Headers are prefixed with HTTP_
- Like: $ENV{HTTP_USER_AGENT} and $ENV{HTTP_HOST}
A CGI script
- Registration script
- Asks for:
- Username, password
- Name, address, city
- Phone number
- Email address, homepage URL
- No error checking
- Thank you page
CGI + HTML
#!/usr/bin/perl -w
use strict;
use CGI;
my $cgi = CGI->new;
print $cgi->header;
if ($cgi->request_method eq 'POST') {
do something;
print <<'END_OF_HTML';
[[[html>
[[[head>
[[[title>Thank you![[[/title>
[[[/head>
[[[body>
Thank you very much for registering!
You will receive a confirmation by email
soon.
[[[/body>
[[[/html>
END_OF_HTML
} else {
print <<'END_OF_HTML';
[[[html>
[[[head>
[[[title>Register[[[/title>
[[[/head>
[[[body>
Fill in these fields:[[[br>
Preferred user name: [[[input type=text name=user>
Password: [[[input type=password name=pass>[[[br>
Again: [[[input type=password name=pass>
[[[hr>
Name: [[[input type=text name=name>[[[br>
Address: [[[input type=text name=address>[[[br>
Zip code: [[[input type=text name=zip>[[[br>
City: [[[input type=text name=city>
[[[hr>
Phone number: [[[input type=text name=phone>[[[br>
Email address: [[[input type=text name=mail>[[[br>
Homepage URL: [[[input type=text name=url>
[[[hr>
[[[input type=submit value="Send request">
[[[/body>
[[[/html>
END_OF_HTML
}
CGI.pm can make HTML
- Still HTML
- A little less ugly
- Much slower
- Handy 'sticky' values
CGI > Templates
- End of CGI
- Questions?
- Next: Templates
Pages in files
#!/usr/bin/perl -w
use strict;
use CGI;
use File::Slurp;
my $cgi = CGI->new;
print $cgi->header;
if ($cgi->request_method eq 'POST') {
do something;
print read_file 'thanks.html';
} else {
print read_file 'register.html';
}
More flexible with templates
- Variables!
- You can train a monkey for the HTML
- Tidy code
- Example:
Email address in thank you page:
<TMPL_VAR NAME=MAIL>
Example
#!/usr/bin/perl -w
use strict;
use CGI;
use HTML::Template;
use File::Slurp;
my $cgi = CGI->new;
print $cgi->header;
if ($cgi->request_method eq 'POST') {
do something;
my $template = HTML::Template->new(
filename => 'thanks.html'
);
$template->param(mail => $cgi->param('mail'));
print $template->output
} else {
print read_file 'register.html';
}
Templating modules
- HTML::Template
- Template (Template Toolkit II)
- Text::Template
- DIY:
s/\$(\w+)/$vars{$1}/g
- My favourite: Template Toolkit
Templates > Wizards
- End of Templates
- Questions?
- Next: Wizards, CGI::Application
User interface
- More important than your code!
- Two options:
- 1. Big page with fields
- Easy for the programmer
- Overwhelming for the user
- 2. "Wizard"
- Hard for the coder
- Easy for the user
Wizards: advantages
- Every user understands
- A lot of room for instructions
Wizards: disadvantages
- A lot of work
- Send information both ways
- Advanced users don't like it
CGI::Application
- Wizard-like
- Does a lot for us
- Uses HTML::Template
Wizards > Sessions
- End of Wizards
- Questions?
- Next: Sessions
POST problems
- Sending data forward and back is inefficient
- There might be things the user shouldn't know
- Browser bugs regarding encodings
Solution: sessions
- Data remains on the server
- Browser gets only one value: sessie-id
A session...
- has a unique, generated ID
- expires
- hides semantics, increases security
- stores values, usually in a simple hash
Sessions are handy for:
- Authentication
- Logging out still is not automatic: HTTP is sessionless
Passing the session id
- Hidden form field
- In every URL
- In the query string (a lot of work)
- In the path ($ENV{PATH_INFO})
- As a cookie!
Modules
- Enough choice
- Apache::Session
- CGI::Session
- PHP::Session
Sessions > Cookies
- End of Sessions
- Questions?
- Next: Cookies
Cookies
- Cookies are sent with every request
- Safe by design, but MSIE sucks
- CGI::Cookie makes things easy
Cookie headers
- Set-Cookie
Set-Cookie: session=15423; expires=Sun, 17-Jan-2005 19:14:07 GMT; path=/; domain=.example.com
Set-Cookie: login=juerd; expires=Sun, 17-Jan-2005 19:14:09 GMT; path=/; domain=.example.com
- Cookie
Cookie: session=15423; login=juerd
Baking a cookie
use CGI::Cookie;
my $cookie = CGI::Cookie->new(
-name => 'session',
-value => $session_id,
-domain => '.example.com',
-path => '/',
-expires => '+3M' # 3 months
);
print $cgi->header(
-cookie => $cookie
);
Eating a cookie
use CGI::Cookie;
my %cookies = CGI::Cookie->fetch;
Cookies > mod_perl
- End of Cookies
- Questions?
- Next: mod_perl
mod_perl
- Perl in apache
- Perl can communicate with Apache
- Very fast with Apache's power
- 20 to 25 times as fast as CGI!
Endless interpreter: disadvantages
- As if you use HTTP::Daemon: everything in 1 program
- Globals
- Module caching (really the other way around)
- Many special variables are reset, fortunately
Preventing problems
use strict;
Stupid question
When can you skip use strict; in mod_perl or a module?
Stupid question
When can you skip use strict; in mod_perl or a module?
If you can't answer that question yourself:
NEVER!
Why strict?
- It helps finding typos
- It helps finding potentially stupid things
- BUT: stupid things are still possible
- Thinking is not optional
CGI scripts in mod_perl
- mod_perl was never meant for this kind of thing
- But it is possible
- If you pay attention
CGI scripts in mod_perl
- Easiest: Apache::PerlRun
- Almost fully compatible
- Ugly coding more or less possible
- With PerlRunOnce possible, at the cost of speed
Migrating to mod_perl
- CGI.pm is mod_perl-compatible
- Using mod_perl directly is faster
- Easy modules can be too heavy
- CGI
- CGI::Application
- HTML::Template
mod_perl is CGI compatible
... by default
- STDIN works
- STDOUT works
- %ENV works
- Mostly because of this is CGI.pm mod_perl compatible :)
$r
- Request object
- Key to everything
- Everyone calls it $r
- So you do too
- Just like $dbh
- Just like $sth
$r->isa('Apache')
CGI vs mod_perl
- STDIN vs $r->content
- STDOUT vs$r->headerstuff, $r->print
- %ENV vs $r->foo and $r->header_in
mod_perl 'scripts'
- Apache::Registry-script
- Common name: "mod_perl script"
- mod_perl scripts don't exist
Apache::Registry
- Compiles code to a sub with eval
- Reloads automatically on change
- Does not much more than that
Your own handler
- Reloading not necessary?
- Own handler not harder
- But it is faster
- No stat() call with every request
Example
package Apache::MyHandler
use Apache::Constants qw(OK);
sub handler {
my $r = shift;
$r->send_http_header('text/plain');
$r->print('example');
return OK;
}
1;
httpd.conf
PerlModule Apache::MyHandler
e.g. for *.qhtml:
<Files *.qhtml>
PerlHandler Apache::MyHandler
</Files>
e.g. with 1 location (to replace a script)
<Location /foo/bar>
PerlHandler Apache::MyHandler
</Location>
Look out in mod_perl
- Globals
- Special variables: NEVER use $`, $& and $'
- Or modules that use them
- Text::Balanced
- Parse::RecDescent
- re (use re 'debug' is not lexical!)
- and more...
Modules in mod_perl
- Many ways to reload
- Not every module can handle reloading!
- Especially Class::DBI is a PITA
- Remedy: restart apache every time
- Better: separate development server!!
mod_perl alternatives
- FastCGI
- PPerl (?)
- HTTP::Daemon
- All the same problems, except Apache access
mod_perl > end
- End of mod_perl
- Questions?
- Next: almost nothing
CPAN
- The wheel already exists
- http://search.cpan.org/
- Loads of CGI/mod_perl modules
- Also many bad ones: ask for experiences
Mentioned modules
- Apache
- Apache::Constants
- Apache::PerlRun
- Apache::Registry
- Apache::Session
- CGI
- CGI::Application
- CGI::Cookie
- CGI::Session
- Class::DBI
- File::Slurp
- HTML::Template
- HTTP::Daemon
- LWP::UserAgent
- Parse::RecDescent
- PHP::Session
- Template
- Text::Balanced
- re
- strict