=pod =head1 NAME Apache2::ASP::Manual::BestPractices - How to use Apache2::ASP properly =head1 DESCRIPTION This is a collection of best practices for using Apache2::ASP in the real world. As more "Best Practices" are discovered, they will end up in some kind of collection. Probably a Wiki or similar. =head1 FORMATTING Do this: <%= $title %> ... <% %>

<% for( 1...5 ) { %> Hello <%= $_ %> <% }# end for() %>

B: <%= $title %> ... <% %>

<% for( 1...5 ) { %> Hello <%= $_ %> <% }# end for() %>

The reason is that visually locating the "<%" and "%>" tags becomes increasingly difficult when they are not lined up vertically in the first column. The "<%=" tags can be interspersed through your code where needed, since they do not enclose blocks of logic. =head1 SUBROUTINES Do not declare subroutines within your code. To get around this limitation, do the following: <% my $add = sub { my ($arg) = shift; ... return $arg + 1; }; ... my $val = $add->( 10 ); # returns 11 %> =head1 PAGE COMPOSITION Apache2::ASP supports multiple modes of page composition. =head2 SSI Includes Only basic SSI includes (aka "Apache-Style" or "Server-Side" includes) are supported. Example: ... =head2 $Response->Include( $path, { args } ) If you need to pass in some arguments to your include, do this: Example: <% $Response->Include( $Server->MapPath("/inc/top.asp"), { foo => "bar" } ); %> ... <% $Response->Include( $Server->MapPath("/inc/bottom.asp"), { foo => "bar" } ); %> Your includes would access the passed-in arguments like this: <% my ($s, $context, $args) = @_; $args->{foo}; # bar %> =head2 MasterPages Think of MasterPages as a kind of page "class" that other pages can subclass. Example (MasterPage): - C <%@ MasterPage %> <asp:ContentPlaceHolder id="ph_title" runat="server"></asp:ContentPlaceHolder>
Example (normal Page): <%@ Page UseMasterPage="/masters/main.asp"> The Title Hello World! The resulting page that will be printed to the browser will look like this: The Title
Hello World!
=head2 Nested Master Pages MasterPages can "subclass" other MasterPages as well, as many levels deep as necessary. Example MasterPage: C <%@ MasterPage %> <asp:ContentPlaceHolder id="ph_title" runat="server"></asp:ContentPlaceHolder>
Example "nested" MasterPage: C <%@ MasterPage %> <%@ Page UseMasterPage="/masters/main.asp">
And a page that uses C as a MasterPage would look like this: <%@ Page UseMasterPage="/masters/child.asp" %> The Title Hello World! The resulting HTML printed to the browser would look like this: The Title
Hello World!
=head2 Advantages of MasterPages Not only do you get inheritance for your web pages, they actually execute faster. Why? Because includes require extra overhead of setting up "mock" requests in which the included ASP scripts are executed. MasterPages do not require this extra work. =head1 HANDLERS Generally speaking, all forms should submit to handlers, rather than other ASP scripts. This results in a predictable MVC setup. Of course, Apache2::ASP doesn't B you to do this. You can do whatever you want. However, if you upload a file, you must upload it to a subclass of L. Generally you would inherit from L though, if you plan on doing much with uploaded files. =head2 Namespaces Because of the way namespaces work in Perl, web servers with multiple VirtualHosts should keep all handlers in their own namespaces. For example: B: /handlers/site1.user.login /handlers/site1.user.logout /handlers/site1.user.register B: /handlers/site2.user.login /handlers/site2.user.logout /handlers/site2.user.register If you were to simply use C, that one handler would be invoked for any website's C URI. Unless this is what you want, avoid the namespace clashes by going with the naming convention described above. =head2 Path-to-Class Mapping Apache2::ASP converts URI's matching C to their corresponding Perl class names. Examples: =over 4 =item * C C =item * C C =back =head1 FILE UPLOADS Just inherit from L unless you need more control. See the documentation for L for more information. =head1 VALIDATION Apache2::ASP supports - but does not provide - server-side validation. In fact, it is recommended that all validation is performed on the server, in one way or another. AJAX may be your preferred means of doing form validations and such, which Apache2::ASP fully supports. Apache2::ASP simply does not B the use of AJAX or any other idiom. =head2 How-To The recommended form validation idiom for Apache2::ASP is as follows: B: <% if( my $args = delete($Session->{__lastArgs}) ) { $Form->{$_} = $args->{$_} foreach keys(%$args); }# end if() my $errors = delete($Session->{validation_errors}) || { }; my $errLabel = sub { my $name = shift; return unless $errors->{$name}; %><%= $Server->HTMLEncode( $errors->{$name} ) %><% }; %> <% if( my $msg = delete($Session->{msg}) ) { %>
<%= $Server->HTMLEncode( $msg ) %>
<% }# end if() %> <% if( $errors->{general} ) { %>
<%= $Server->HTMLEncode( $errors->{general} ) %>
<% }# end if() %>
<% $errLabel->( 'username' ); %>
<% $errLabel->( 'password' ); %>
The form submits to the URI C which maps to the package C. It is recommended that inside your C folder you have a YAML file, C: B: (C) --- user_login: username: is_missing: Required is_invalid: Invalid username password: is_missing: Required is_invalid: Invalid password general: success: Successfully Logged In fail: Invalid username and/or password. Please try again. B: (C) package site1::user::login; use strict; use warnings 'all'; use base 'Apache2::ASP::FormHandler'; use vars __PACKAGE__->VARS; use Data::Properties::YAML; #============================================================================ sub run { my ($s, $context) = @_; if( my $errors = $s->validate( $context ) ) { # We found some kind of validation error: $Session->{__lastArgs} = $Form; $Session->{validation_errors} = $errors; return $Response->Redirect( $ENV{HTTP_REFERER} ); }# end if() # Success! - no validation errors: my ($user) = find_user( ... ); $Session->{user} = $user; # Find our success message: my $props = Data::Properties::YAML->new( properties_file => $Config->web->application_root . '/etc/properties.yaml' )->user_login; $Session->{msg} = $props->general->success; # Redirect the user to the logged-in page: return $Response->Redirect("/logged-in.asp"); }# end run() #============================================================================ sub validate { my ($s, $context) = @_; # Remove leading and trailing whitespace: map { $Form->{$_} =~ s/^\s+//; $Form->{$_} =~ s/\s+$//; } keys(%$Form); my $props = Data::Properties::YAML->new( properties_file => $Config->web->application_root . '/etc/properties.yaml' )->user_login; my $errors = { }; no warnings 'uninitialized'; # username: if( length($Form->{username}) ) { # Username cannot contain whitespace: if( $Form->{username} =~ m/\s/ ) { $errors->{username} = $props->username->is_invalid; }# end if() } else { $errors->{username} = $props->username->is_missing; }# end if() # password: if( length($Form->{password}) ) { # Password cannot contain whitespace: if( $Form->{password} =~ m/\s/ ) { $errors->{password} = $props->password->is_invalid; }# end if() } else { $errors->{password} = $props->password->is_missing; }# end if() # Only check to see if the user exists if we haven't encountered other errors: unless( keys(%$errors) ) { if( ! find_user( ... ) ) { $errors->{general} = $props->general->fail; }# end if() }# end unless() return unless keys(%$errors); return $errors; }# end validate() 1;# return true: =head1 UNIT TESTING Unit testing was the number one reason behind the development of Apache2::ASP. Apache2::ASP offers a unit testing environment that is not dependent on Apache or any other server. Unit tests are made possible via instances of L and use L to make "requests" to ASP scripts and handlers in your Apache2::ASP web application. =head2 Example Supposing your website is at C, create a folder C at C. Inside C create C which contains: #!/usr/bin/env perl -w use strict; use warnings 'all'; use Test::More 'no_plan'; use base 'Apache2::ASP::Test::Base'; # Create our base test object: my $s = __PACKAGE__->SUPER::new(); # Make a request: my $res = $s->ua->get("/index.asp"); # $res is a normal HTTP::Response object: ok( $res->is_success => "Got /index.asp" ); like $res->content, qr/Hello, World/, "Contents look right"; is( $res->header('content-type') => 'text/html' ); Run your tests with: prove t All of your tests will be run. =head1 CODE COVERAGE Along with unit testing, code coverage is another great reason to use Apache2::ASP. Just by using the L utility C you can get code coverage for not only your website's libraries, but also its handlers and ASP scripts. =head1 PROFILING Profiling an Apache2::ASP web application fits right in with your unit tests and code coverage. L is an excellent profiler tool for Perl and works very well with Apache2::ASP web applications. =head1 ERROR HANDLING Errors are handled by subclasses of L. The default ErrorHandler prints a stacktrace to the browser and sends a copy to the email address specified in your config file. =head2 Configuration Open your C file and look for the following: ... ... ... ... Make changes as necessary. =head1 FILE UPLOADS Almost any time you need to process a file upload, your best bet is to subclass L. See L for details. If you really need to do something special, either subclass L or write your own C handler and submit to it. =head1 SECURITY =head2 Restricting Access Apache2::ASP simplifies this by providing the RequestFilter interface (L). B: Suppose you want all requests to C to require authentication. Adjust your C like this: ... ... /members/.* My::MembersOnlyFilter ... Somewhere in your C<@INC> (like, say, C) add C with the following code: package My::MembersOnlyFilter; use strict; use warnings 'all'; use base 'Apache2::ASP::RequestFilter'; use vars __PACKAGE__->VARS; #====================================================== sub run { my ($s, $context) = @_; unless( $Session->{logged_in} ) { # User is *not* logged in: return $Response->Redirect("/login.asp"); }# end unless() # User is logged in: return $Response->Declined; }# end run() 1;# return true: Now, every request to C will be denied unless the Session variable C is set to a true value. =head2 Cross-Site-Scripting (xSS) Never, ever, ever, Ever $Response->Write() or <%= %> something you received from your users. That goes for user data that might have been stored in a database as well. Examples: B: B: The reason is that the user could have input text like the following: red"/>

Using Server->HTMLEncode causes that same attack to be rendered harmless, like this: The attacker gets nothing, and moves on. =head1 DATABASE ACCESS By default, Apache2::ASP uses 3 database handles, defined within the element in the XML config: =head2 Connections =over 4 =item * session Sessions are serialized and stored in a database, one record per session. =item * application Applications are serialized and stored in a database, one record per application. =item * main Most web applications use a "main" database handle. This is the configuration for that connection. =back =head2 Choosing an ORM Depending on your preference and the task at hand, an ORM (Object/Relational Mapper) such as L or L may be chosen without any problems. Apache2::ASP was developed specifically to work with L and this combination has been tested thoroughly. =head2 Preventing SQL Injection Whatever you do, always use SQL placeholders (C). For more information on preventing SQL injection attacks, see L =head1 AUTHOR John Drago =head1 COPYRIGHT Copyright 2008 John Drago. All rights reserved. =head1 LICENSE This software is Free software and may be used and redistributed under the same terms as perl itself. =cut