# NAME ASP4 - Fast, Simple and Scalable Web Development for Perl # DESCRIPTION ASP4 is a modern web development platform for Perl with a focus on speed, simplicity and scalability. # ASP OBJECTS ASP4 brings our old friends `$Request`, `$Response`, `$Server` and `$Session` back from the 1990's, but adds some new ever-present objects as well. Together the ASP objects provide a consistent interface to the incoming request, outgoing response, server environment and configuration in-use by the application itself. ## $Form This is a simple hash reference which contains the names and values of the incoming request parameters for both GET and POST requests. For example, the following request... /foo.asp?name=joe&color=red ...produces the following `$Form` object: $VAR1 = { name => 'joe', color => 'red' }; Access form data just like any other hashref: Hello, <%= $Form->{name} %>, I see your favorite color is <%= $Form->{color} %>. ## $Server The `$Server` object offers a few utility methods that don't really fit anywhere else. ### $Server->HTMLEncode( $string ) Given a string like `
` returns a string like `<br/>` ### $Server->HTMLDecode( $string ) Given a string like `<br/>` returns a string like `
` ### $Server->URLEncode( $string ) Given a string like `billg@microsoft.com` returns a string like `billg%40microsoft.com` ### $Server->URLDecode( $string ) Given a string like `billg%40microsoft.com` returns a string like `billg@microsoft.com` ### $Server->MapPath( $path ) Given a `$path` of `/foo.asp` would return something like `/var/www/example.com/htdocs/foo.asp` ### $Server->Mail( %args ) Sends an email via [Mail::Sendmail](http://search.cpan.org/perldoc?Mail::Sendmail). In fact it simply calls the `sendmail(...)` function provided by [Mail::Sendmail](http://search.cpan.org/perldoc?Mail::Sendmail). Simple Example: $Server->Mail( from => 'foo@bar.com', to => 'bar@foo.com', subject => 'Hello, world!', message => 'this is a test message' ); To send an HTML email do the following: use MIME::Base64; $Server->Mail( from => 'foo@bar.com', to => 'bar@foo.com', subject => 'Hello, world!', 'content-type' => 'text/html', 'content-transfer-encoding' => 'base64', message => encode_base64(<<"HTML")

This is an html email.

You can see that this text is bold.

HTML ); Please see [Mail::Sendmail](http://search.cpan.org/perldoc?Mail::Sendmail) for further details and examples. ### $Server->RegisterCleanup( sub { ... }, \@args ) After the final response has been sent to the client, the server will execute your subref and provide it the `\@args` passed in. This is useful for long-running or asynchronous processes that don't require the client to wait for a response. ## $Request An instance of [ASP4::Request](http://search.cpan.org/perldoc?ASP4::Request), the `$Request` object contains specialized methods for dealing with whatever the browser sent us. Examples: ### $Request->Cookies( $name ) my $cookie = $Request->Cookies("some-cookie-name"); ### $Request->FileUpload( $field_name ) if( my $file = $Request->FileUpload('avatar_pic') ) { # Handle the uploaded file: $file->SaveAs( "/var/media/$Session->{user_id}/avatar/" . $file->FileName ); } See also the [ASP4::FileUpload](http://search.cpan.org/perldoc?ASP4::FileUpload) documentation. ## $Response An instance of [ASP4::Response](http://search.cpan.org/perldoc?ASP4::Response), the `$Response` object gives shortcuts for dealing with the outgoing reply from the server back to the client. Examples: ### $Response->Write( $string ) The following example prints the string `Hello, World!` to the browser: $Response->Write("Hello, World!"); Or, within an ASP script, `<%= "Hello, World" %>` ### $Response->Redirect( $url ) $Response->Redirect( "/new/url/?foo=bar" ); ### $Response->SetCookie( %args ) Setting cookies works as follows: $Response->SetCookie( name => "cookie-name", value => "the-value", # The rest of these arguments are optional: # Expires: (If you don't specify the "expires" argument, the cookie will # be deleted when the browser is closed. expires => "3D", # 3 days expires => "3H", # or 3 hours expires => "3M", # or 3 minutes # Domain: (defaults to $ENV{HTTP_HOST}) domain => ".example.com", # works for *.example.com domain => "www.example.com", # will ONLY work for www.example.com # Path: path => "/some/folder/" # will ONLY work within /some/folder/ on your website ); ### $Response->Include( $path, %args ) ASP4's `$Response` object offers 3 different include methods. If you want to supply arguments to the included ASP script you can use `$Response->Include($path, \%args)` # Add the output of C to the current output buffer: my %args = ( foo => "bar" ); $Response->Include( $Server->MapPath("/includes/page.asp"), \%args ); `\%args` is optional. Within the included ASP script, `\%args` is accessible like this: <% my ($self, $context, $args) = @_; %> ### $Response->TrapInclude( $path, %args ) Or if you need to capture the result of executing an ASP script and use it within a variable, use `$Response->TrapInclude($path, \%args)` # Capture the output of C: my %args = ( foo => "bar" ); my $html = $Response->TrapInclude( $Server->MapPath("/includes/page.asp"), \%args ); `\%args` is optional. Within the included ASP script, `\%args` is accessible like this: <% my ($self, $context, $args) = @_; %> ## $Session The `$Session` object is an instance of a subclass of [ASP4::SessionStateManager](http://search.cpan.org/perldoc?ASP4::SessionStateManager) (depending on your website's configuration). The `$Session` object is a simple blessed hashref and should be used like a hashref. Examples: ### Set a session variable $Session->{foo} = "bar"; $Session->{thing} = { banana => "yellow", cherry => "red", peach => "pink, }; ### Get a session variable my $foo = $Session->{foo}; ### $Session->save() Called automatically at the end of every successful request, causes any changes to the `$Session` to be saved to the database. ### $Session->reset() Call `$Session->reset()` to clear all the data out of the session and save it to the database. ## $Config The ASP4 `$Config` object is stored in a simple JSON format on disk, and accessible everywhere within your entire ASP4 application as the global `$Config` object. If ever you find yourself in a place without a `$Config` object, you can get one like this: use ASP4::ConfigLoader; my $Config = ASP4::ConfigLoader->load(); See [ASP4::Config](http://search.cpan.org/perldoc?ASP4::Config) for full details on the ASP4 `$Config` object and its usage. ## $Stash The `$Stash` is a simple hashref that is guaranteed to be the exact same hashref throughout the entire lifetime of a request. Anything placed within the `$Stash` at the very beginning of processing a request - such as in a RequestFilter - will still be there at the very end of the request - as in a RegisterCleanup handler. Use the `$Stash` as a great place to store a piece of data for the duration of a single request. # DATABASE While ASP4 __does not require__ its users to choose any specific database (eg: MySQL or PostgreSQL) or ORM (object-relational mapper) the __recommended__ ORM is [Class::DBI::Lite](http://search.cpan.org/perldoc?Class::DBI::Lite) since it has been completely and thoroughly tested to be 100% compatible with ASP4. For full documentation about [Class::DBI::Lite](http://search.cpan.org/perldoc?Class::DBI::Lite) please view its documentation. __NOTE:__ [Class::DBI::Lite](http://search.cpan.org/perldoc?Class::DBI::Lite) must be installed in addition to ASP4 as it is a separate library. # ASP4 QuickStart Here is an example project to get things going. In the `data_connections.main` section of `conf/asp4-config.json` you should have something like this: ... "main": { "dsn": "DBI:mysql:database_name:data.mywebsite.com", "username": "db-username", "password": "db-pAsswOrd" } ... Suppose you had the following tables in your database: create table users ( user_id bigint unsigned not null primary key auto_increment, email varchar(200) not null, password char(32) not null, created_on timestamp not null default current_timestamp, unique(email) ) engine=innodb charset=utf8; create table messages ( message_id bigint unsigned not null primary key auto_increment, from_user_id bigint unsigned not null, to_user_id bigint unsigned not null, subject varchar(100) not null, body text, created_on timestamp not null default current_timestamp, foreign key fk_messages_to_senders (from_user_id) references users (user_id) on delete cascade, foreign key fk_messages_to_recipients (to_user_id) references users (user_id) on delete cascade ) engine=innodb charset=utf8; __NOTE:__ It's best to assign every ASP4 application its own namespace. For this example the namespace is `App::db::` Create the file `lib/App::db/model.pm` and add the following lines: package App::db::model; use strict; use warnings 'all'; use base 'Class::DBI::Lite::mysql'; use ASP4::ConfigLoader; # Get our configuration object: my $Config = ASP4::ConfigLoader->load(); # Get our main database connection info: my $conn = $Config->data_connections->main; # Setup our database connection: __PACKAGE__->connection( $conn->dsn, $conn->username, $conn->password ); 1;# return true: Add the following `Class::DBI::Lite` entity classes: `lib/App/db/user.pm` package App::db::user; use strict; use warnings 'all'; use base 'App::db::model'; use Digest::MD5 'md5_hex'; use ASP4::ConfigLoader; __PACKAGE__->set_up_table('users'); __PACKAGE__->has_many( messages_in => 'App::db::message' => 'to_user_id' ); __PACKAGE__->has_many( messages_out => 'App::db::message' => 'from_user_id' ); # Hash the password before storing it in the database: __PACKAGE__->add_trigger( before_create => sub { my ($self) = @_; # Sign the password instead of storing it as plaintext: unless( $self->{password} =~ m{^([a-f0-9]{32})$}i ) { $self->{password} = $self->hash_password( $self->password ); } }); # Hash the new password before storing it in the database: __PACKAGE__->add_trigger( before_update_password => sub { my ($self, $old, $new) = @_; unless( $new =~ m{^([a-f0-9]{32})$}i ) { $self->{password} = $self->hash_password( $new ); } }); # Verify an email/password combination and return the user if a match is found: sub check_credentials { my ($self, %args) = @_; my ($result) = $self->search( email => $args{email}, password => $self->hash_password( $args{password} ), ); $result ? return $result : return; } # Convert a password string into its hashed value: sub hash_password { my ($self, $str) = @_; my $key = ASP4::ConfigLoader->load->system->settings->signing_key; return md5_hex( $str . $key ); } 1;# return true: `lib/App/db/message.pm` package App::db::message; use strict; use warnings 'all'; use base 'App::db::model'; __PACKAGE__->set_up_table('messages'); __PACKAGE__->belongs_to( sender => 'App::db::user' => 'from_user_id' ); __PACKAGE__->belongs_to( recipient => 'App::db::user' => 'to_user_id' ); 1;# return true: Create your MasterPage like this: File: `htdocs/masters/global.asp` <%@ MasterPage %> <asp:ContentPlaceHolder id="meta_title"></asp:ContentPlaceHolder>

File: `htdocs/index.asp` <%@ Page UseMasterPage="/masters/global.asp" %> Register Register <% # Sticky forms work like this: if( my $args = $Session->{__lastArgs} ) { map { $Form->{$_} = $args->{$_} } keys %$args; } # Our validation errors: my $errors = $Session->{validation_errors} || { }; $::err = sub { my $field = shift; my $error = $errors->{$field} or return; %><%= $Server->HTMLEncode( $error ) %><% }; %>

<% $::err->("email"); %>

<% $::err->("password"); %>

<% $::err->("password2"); %>

The form submits to the URL `/handlers/app.register` which means `handlers/app/register.pm` File: `handlers/app/register.pm` package app::register; use strict; use warnings 'all'; use base 'ASP4::FormHandler'; use vars __PACKAGE__->VARS; # Import $Response, $Form, $Session, etc use App::db::user; sub run { my ($self, $context) = @_; # If there is an error, return the user to the registration page: if( my $errors = $self->validate() ) { $Session->{validation_errors} = $errors; $Session->{__lastArgs} = $Form; $Session->save; return $Response->Redirect( $ENV{HTTP_REFERER} ); } # Create the user: my $user = eval { App::db::user->do_transaction(sub { return App::db::user->create( email => $Form->{email}, password => $Form->{password}, ); }); }; if( $@ ) { # There was an error: $Session->{validation_errors} = {email => "Server error. Sorry!"}; $Session->{__lastArgs} = $Form; $Session->save; return $Response->Redirect( $ENV{HTTP_REFERER} ); } else { # No error - Sign them in: $Session->{user_id} = $user->id; $Session->{msg} = "Thank you for registering!"; $Session->save; # Redirect to /profile.asp: return $Response->Redirect("/profile.asp"); }# end if() } sub validate { my ($self) = @_; $self->trim_form; my $errors = { }; no warnings 'uninitialized'; # email: if( length($Form->{email}) ) { # Basic email validation: unless( $Form->{email} =~ m{[^@]+@[^@]+\.[^@]+} ) { $errors->{email} = "Invalid email address"; } } else { $errors->{email} = "Required"; } # password: unless( length($Form->{password} ) { $errors->{password} = "Required"; } # password2: if( length($Form->{password2}) ) { if( length($Form->{password}) ) { unless( $Form->{password} eq $Form->{password2} ) { $errors->{password2} = "Passwords don't match"; } } } else { $errors->{password2} = "Required"; } # Bail out of we already have errors: return $errors if keys %$errors; # See if the user already exists: if( App::db::user->count_search( email => $Form->{email} ) ) { $errors->{email} = "Already in use"; } # Errors or not?: keys %$errors ? return $errors : return; } 1;# return true: File: `htdocs/profile.asp` <%@ Page UseMasterPage="/masters/global.asp" %> My Profile My Profile <% if( my $msg = $Session->{msg} ) { %>
<%= $msg %>
<% }# end if() %> <% # Get our $user: use App::db::user; my $user = App::db::user->retrieve( $Session->{user_id} ); %>

Incoming Messages

<% foreach my $msg ( $user->messages_in(undef, { order_by => "created_on ASC"} ) ) { %>
<%= $msg->sender->email %> says:
<%= $Server->HTMLEncode( $msg->body ) %>
<%= $msg->created_on %>
<% }# end foreach() %>

Send New Message

The form submits to `/handlers/app.send` which maps to `handlers/app/send.pm` File: `handlers/app/send.pm` package app::send; use strict; use warnings 'all'; use base 'ASP4::FormHandler'; use vars __PACKAGE__->VARS; use App::db::user; use App::db::message; sub run { my ($self, $context) = @_; # Create the message: my $msg = eval { App::db::message->do_transaction(sub { my $msg = App::db::message->create( from_user_id => $Session->{user_id}, to_user_id => $Form->{to_user_id}, subject => $Form->{subject}, body => $Form->{body}, ); # Send an email to the recipient: $Server->Mail( from => 'root@localhost', 'reply-to' => $msg->sender->email, to => $msg->recipient->email, subject => 'New in-club message', message => <<"MSG", Dear user, Another user (@{[ $msg->sender->email ]}) has sent you an in-club message. Please login and view it on your profile at http://$ENV{HTTP_HOST}/ Yours, The "In Club" MSG ); # Finally: return $msg; }); }; if( $@ ) { $Session->{msg} = "Error: Your message could not be sent."; $Session->save; return $Response->Redirect( $ENV{HTTP_REFERER} ); } else { $Session->{msg} = "New message sent successfully."; $Session->save; return $Response->Redirect( $ENV{HTTP_REFERER} ); } } # BUGS It's possible that some bugs have found their way into this release. Use RT [http://rt.cpan.org/NoAuth/Bugs.html?Dist=ASP4](http://rt.cpan.org/NoAuth/Bugs.html?Dist=ASP4) to submit bug reports. # HOMEPAGE Please visit the ASP4 homepage at [http://0x31337.org/code/](http://0x31337.org/code/) to see examples of ASP4 in action. # AUTHOR John Drago # COPYRIGHT This software is Free software and may be used and redistributed under the same terms as perl itself.