NAME

PApp - multi-page-state-preserving web applications

SYNOPSIS

* This module requires quite an elaborate setup (see the INSTALL file). *
* Please read the LICENSE file (PApp is neither GPL nor BSD licensed).  *

DESCRIPTION

PApp is a complete solution for developing multi-page web applications that preserve state across page views. It also tracks user id's, supports a user access system and provides many utility functions (html, sql...). You do not need (and should not use) the CGI module.

Advantages:

  • Speed. PApp isn't much slower than a hand-coded mod_perl handler, and this is only due to the extra database request to fetch and restore state, which typically you would do anyway. To the contrary: a non-trivial Apache::Registry page is slower than the equivalent PApp application (or much, much more complicated); Note: as of version 0.10, this is no longer true, but I am working on it ;) It is, however, much easier to use than anything else (while still not being slow).

  • Embedded Perl. You can freely embed perl into your documents. In fact, You can do things like these:

    <h1>Names and amounts</h1>
    <:
       my $st = sql_exec \my($name, $amount), "select name, amount from ...",
    
       while ($st->fetch) {?>
          Name: $name, Amount: $amount<p>
       <:}
    :>
    <hr>

    That is, mixing html and perl at statement boundaries.

  • State-preserving: The global hash %S is automaticaly preserved during the session. Everything you save there will be available in any subsequent pages that the user accesses.

  • XML. PApp-applications are written in XML. While this is no advantage in itself, it means that it uses a standardized file format that can easily be extended. PApp comes with a DTD and a vim syntax file, even ;)

  • Easy internationalization. I18n has never been that easy: just mark you strings with __"string", either in html or in the perl source. The "poedit"-demo-application enables editing of the strings on-line, so translaters need not touch any text files and can work diretcly via the web.

  • Feature-Rich. PApp comes with a lot of small-but-nice-to-have functionality.

Disadvantages:

  • Unfinished Interface: To admit it, this module is young and many features have a kind-of-unfinished interface. PApp will certainly be changed and improved to accomodate new features (like CGI-only operation).

  • No documentation. Especially tutorials are missing, so you are most probably on your own.

  • Perl5.8.0 is required (actually, a non-released bugfixed version of 5.8.1). While not originally an disadvantage in my eyes, Randal Schwartz asked me to provide some explanation on why this is so (at the time I only required 5.6):

    "As for an explanation, I require perl5.6 because I require a whole lot of features of 5.6 (e.g. DB.pm, utf-8 support, "our", bugfixes, 3-argument open, regex improvements, probably many others, especially changes on the XS level). In the future it will likely require weak references, filehandle autovivification, the XSLoader for extra speed in rare circumstances... I don't want to backport this to older versions ;)"

To get a quick start, read the bench.papp module, the dbedit.papp module, the cluster.papp module and the papp.dtd description of the papp file format.

Also, have a look at the doc/ subdirectory of the distribution, which will have some tutorials in sdf and html format.

GLOBAL VARIABLES

Some global variables are free to use and even free to change (yes, we still are about speed, not abstraction). In addition to these variables, the globs *state, *S and *A (and in future versions *L) are reserved. This means that you cannot define a scalar, sub, hash, filehandle or whatsoever with these names.

$request [read-only]

The Apache request object (Apache), the same as returned by Apache-request>.

%state [read-write, persistent]

A system-global hash that can be used for almost any purpose, such as saving (global) preferences values. All keys with prefix papp are reserved for use by this module. Everything else is yours.

%S [read-write, persistent]

Similar to %state, but is local to the current application. Input arguments prefixed with a dash end up here.

%A [read-write, input only]

A global hash that contains the arguments to the current module. Arguments to the module can be given to surl or any other function that calls it, by prefixing parameter names with a minus sign (i.e. "-switch").

%P [read-write, input only]

Similar to %A, but it instead contains the parameters from forms submitted via GET or POST (see parse_multipart_form, however). Everything in this hash is insecure by nature and must should be used carefully.

Normally, the values stored in %P are plain strings (in utf-8, though). However, it is possible to submit the same field multiple times, in which case the value stored in $P{field} is a reference to an array with all strings, i.e. if you want to evaluate a form field that might be submitted multiple times (e.g. checkboxes or multi-select elements) you must use something like this:

my @values = ref $P{field} ? @{$P{field}} : $P{field};
%temporary [not exported]

Is empty at the beginning of a request and will be cleared at request end.

$userid [read-only]

The current userid. User-Id's are automatically assigned, you are encouraged to use them for your own user-databases, but you mustn't trust them. $userid is zero in case no userid has been assigned yet. In this case you can force a userid by calling the function getuid, which allocated one if necessary,

$sessionid [read-only]

A unique number identifying the current session (not page). You could use this for transactions or similar purposes. This variable might or might not be zero indicating that no session has been allocated yet (similar to $userid == 0).

$session [PApp::Session]

The current session object, which stores session-specific variables.

$session->get("shoppingcart");
$session->set("shoppingcart", $cart);
$curprefs, $prefs [PApp::Prefs]

The current application's ($curprefs) and the global ($prefs) preferences object.

$curprefs->get("bg_color");
ef_string $curprefs->ref("bg_color"), 15;
$PApp::papp (a hash-ref) [read-only] [not exported] [might get replaced by a function call]

The current PApp::Application object (see PApp::Application). The following keys are user-readable:

config   the argument to the C<config>option given to C<mount>.
$ppkg [read-only] [might get replaced by a function call]

This variable contains the current PApp::Package object (see PApp::Package). This variable might be replaced by something else, so watch out. This might or might not be the same as $PApp::ppkg, so best use $ppkg when using it. Ah, actually it's best to not use it at all.

$PApp::location [read-only] [not exported] [might get replaced by a function call]

The location value from mount.

$PApp::module [read-only] [not exported] [might get replaced by a function call]

The current module within the application (full path).

$NOW [read-only]

Contains the time (as returned by time) at the start of the request. Highly useful for checking cache time-outs or similar things, as it is faster to use this variable than to call time.

FUNCTIONS/METHODS

PApp->search_path(path...);

Add a directory in where to search for included/imported/"module'd" files.

PApp->configure(name => value...);

Configures PApp, must be called once and once only. Most of the configuration values get their defaults from the secured config file and/or give defaults for applications.

pappdb        The (mysql) database to use as papp-database
              (default "DBI:mysql:papp")
pappdb_user   The username when connecting to the database
pappdb_pass   The password when connecting to the database
cipherkey     The Twofish-Key to use (16 binary bytes),
              BIG SECURITY PROBLEM if not set!
              (you can use 'mcookie' from util-linux twice to generate one)
cookie_reset  delay in seconds after which papp tries to
              re-set the cookie (default: one day)
cookie_expires time in seconds after which a cookie shall expire
              (default: one year)
logfile       The path to a file where errors and warnings are being logged
              to (the default is stderr which is connected to the client
              browser on many web-servers)

The following configuration values are used mainly for development:

checkdeps     when set, papp will check the .papp file dates for
              every request (slow!!) and will reload the app when necessary.
delayed       do not compile applications at server startup, only on first
              access. This greatly increases memory consumption but ensures
              that the httpd startup works and is faster.
onerr         can be one or more of the following characters that
              specify how to react to an unhandled exception. (default: 'sha')
              's' save the error into the error table
              'v' view all the information (security problem)
              'h' show the error category only
              'a' give the admin user the ability to log-in/view the error
PApp->mount_appset($appset)

Mount all applications in the named application set. Usually used in the httpd.conf file to mount many applications into the same virtual server etc... Example:

mount_appset PApp 'default';
PApp->mount_app($appname)

Can be used to mount a single application.

The following description is no longer valid.

location[*]   The URI the application is mounted under, must start with "/".
              Currently, no other slashes are allowed in it.
src[*]        The .papp-file to mount there
config        Will be available to the application as $papp->{config}
delayed       see C<PApp->configure>.

[*] required attributes
($name, $version) = PApp->interface

Return name and version of the interface PApp runs under (e.g. "PApp::Apache" or "PApp:CGI").

dprintf "format", value...
dprint value...

Work just like print/printf, except that the output is queued for later use by the debugbox function.

echo value[, value...]

Works just like the print function, except that it is faster for generating output.

capture { code/macros/html }

Captures the output of "code/macros/perl" and returns it, instead of sending it to the browser. This is more powerful than it sounds, for example, this works:

<:
   my $output = capture {

      print "of course, this is easy\n";
      echo "this as well";
      :>

      Yes, this is captured as well!
      <:&this_works:>
      <?$captureme:>

      <:

   }; # close the capture
:>
content_type $type [, $charset]

Sets the output content type to $type. The content-type should be a registered MIME type (see RFC 2046) like text/html or image/png. The optional argument $charset can be either "*", which selects a suitable output encoding dynamically (e.g. according to $state{papp_locale}) or the name of a registered character set (STD 2). The special value undef suppresses output character conversion entirely. If not given, the previous value will be unchanged (the default; currently "*").

The following is not yet implemented and will probably never be:

The charset argument might also be an array-reference giving charsets that should be tried in order (similar to the language preferences). The last charset will be forced, i.e. characters not representable in the output will be replaced by some implementation defined way (if possible, this will be &#charcode;, which is as good a replacement as any other ;)

How this interacts with Accept-Charset is still an open issue (for non-microsoft browsers that actually generate this header ;)

setlocale [$locale]

Sets the locale used by perl to the given (PApp-style) locale string. This might involve multiple calls to the "real" setlocale which in turn might be very slow (setlocale is very slow on glibc based systems for example). If no argument is given it sets the locale to the current user's default locale (see SURL_SET_LOCALE). NOTE: Remember that PApp (and Perl) requires iso-8859-1 or utf-8, so be prepared to do any conversion yourself. In future versions PApp might help you doing this (e.g. by setting LC_CTYPE to utf-8, but this is not supported on many systems).

At the moment, PApp does not automatically set the (Perl) locale on each request, so you need to call setlocale before using any locale-based functions.

Please note that PApp-style locale strings might not be compatible to your system's locale strings (this function does the conversion).

reference_url $fullurl

Return a url suitable for external referencing of the current page. If $fullurl is given, a full url (including a protocol specifier) is generated. Otherwise a partial uri is returned (without http://host:port/).

This is only a bona-fide attempt: The current module must support starting a new session and only "import"-variables and input parameters are preserved.

$url = surl ["module"], arg => value, ...

surl is one of the most often used functions to create urls. The first argument is a comma-seperated list of target modules that the url should refer to. If it is missing the url will refer to the current module state, as will a module name of ".". The most common use is just a singular module name. Examples:

.            link to the current module
menu         link to module "menu" in the current package
fall/wahl    link to the current module but set the subpackage
             "fall" to module "wahl".
fall/,menu   link to the menu module and set the subpackage
             "fall" to the default module (with the empty name).

The remaining arguments are parameters that are passed to the new module. Unlike GET or POST-requests, these parameters are directly passed into the %S-hash (unless prefixed with a dash), i.e. you can use this to alter state values when the url is activated. This data is transfered in a secure way and can be quite large (it will not go over the wire).

When a parameter name is prefixed with a minus-sign, the value will end up in the (non-persistent) %A-hash instead (for "one-shot" arguments).

Otherwise the argument name is treated similar to a path under unix: If it has a leading "/", it is assumed to start at the server root, i.e. with the application location. Relative paths are resolved as you would expect them. Examples:

(most of the following hasn't been implemented yet)

/papp_locale  $state{papp_locale}
/tt/var       $state{'/tt'}{var} -OR- $S{var} in application /tt
/tt/mod1/var  $state{'/tt'}{'/mod1'}{var}
../var        the "var" statekey of the module above in the stack

The following (symbolic) modifiers can also be used:

SURL_PUSH(<path> => <value>)
SURL_UNSHIFT(<path> => <value>)
  treat the following state key as an arrayref and push or unshift the
  argument onto it.

SURL_POP(<path-or-ref>)
SURL_SHIFT(<path-or-ref>)
  treat the following state key as arrayref and pop/shift it.

SURL_EXEC(<coderef>) [obsolete]
  treat the following parameter as code-reference and execute it
  after all other assignments have been done. this SURL modifier
  is deprecated, PApp::Callback callbacks don't need this modifier
  anymore.

  Nowadays, code-references found anywhere in the surlargs are treated
  as if they had a SURL_EXEC wrapped around them. IF you want to pass a
  coderef, you therefore have to pass a reference to it or wrap it into
  an object.

SURL_EXEC_IMMED(<coderef>)
  Like SURL_EXEC, but will be executed immediately when parsing. This
  can be used to implement special surl behaviour, because it can affect
  values specified after this specification. Normally, you don't want
  to use this call.

SURL_SAVE_PREFS
  call save_prefs

SURL_STYLE_URL
SURL_STYLE_GET
SURL_STYLE_STATIC
  set various url styles, see C<surl_style>.

SURL_SUFFIX(<file>)
  sets the filename in the generated url to the given string. The
  filename is the last component of the url commonly used by browsers as
  the default name to save files. Works only with SURL_STYLE_GET.

Examples:

SURL_PUSH("stack" => 5)   push 5 onto @{$S{stack}}
SURL_SHIFT("stack")       shift @{$S{stack}}
SURL_SAVE_PREFS           save the preferences on click
SURL_EXEC($cref->refer)   execute the PApp::Callback object
surl_style [newstyle]

Set a new surl style and return the old one (actually, a token that can be used with surl_style. newstyle must be one of:

SURL_STYLE_URL
  The "classic" papp style, the session id gets embedded into the url,
  like C</admin/+modules-/bhWU3DBm2hsusnFktCMbn0>.

SURL_STYLE_GET
  The session id is encoded as the form field named "papp" and appended
  to the url as a get request, e.g. C</admin/+modules-?papp=bhWU3DBm2hsusnFktCMbn0>.

SURL_STYLE_STATIC
  The session id is not encoded into the url, e.g. C</admin/+modules->,
  instead, surl returns two arguments. This must never be set as a
  default using C<surl_style>, but only when using surl directly.
postpone { ... } [args...]

Can only be called inside (or before) SURL_EXEC callbacks, and postpones the block to be executed after all other callbacks. Just like callbacks themeselves, these callbacks are executed in FIFO order. The current database handle will be restored.

This is just "alink shift, &url", that is, it returns a link with the given contants, and a url created by surl (see above). For example, to create a link to the view_game module for a given game, do this:

<? slink "Click me to view game #$gamenr", "view_game", gamenr => $gamenr :>

The view_game module can access the game number as $S{gamenr}.

suburl [surl-args]

Creates a URL like surl, but also pushes the current module state onto the return stack. It preserves all current locals for the "return jump".

Just like suburl but creates an A HREF link with given contents.

Return true when the return stack has some entries, otherwise false.

returl [surl-args]

Return a url that has the effect of returning to the last suburl-caller.

Just like returl, but creates an A HREF link with the given contents.

my ($marker, $ref) = fixup_marker [$initial_content]

Create a new fixup marker and return a scalar reference to it's replacement text (initially empty if not specified). At page output time any fixup markers in the document are replaced by this scalar.

sform [\%attrs,] [module,] arg => value, ...
cform [\%attrs,] [module,] arg => value, ...
multipart_form [\%attrs,], [module,] arg => value, ...
endform

Forms Support

These functions return a <form> or </form>-Tag. sform ("simple form") takes the same arguments as surl and return a <form>-Tag with a GET-Method. cform ("complex form") does the same, but sets method to POST. Finally, multipart_form is the same as cform, but sets the encoding-type to "multipart/form-data". The latter data is not parsed by PApp, you will have to call parse_multipart_form (see below) when evaluating the form data.

All of these functions except endform accept an initial hashref with additional attributes (see PApp::HTML), e.g. to set the name attribute of the generated form elements.

Endform returns a closing </form>-Tag, and must be used to close forms created via sform/cform/multipart_form.

parse_multipart_form \&callback;

Parses the form data that was encoded using the "multipart/form-data" format. Returns true when form data was present, false otherwise.

For every parameter, the callback will be called with four arguments: Handle, Name, Content-Type, Content-Type-Args, Content-Disposition (the latter two arguments are hash-refs, with all keys lowercased).

If the callback returns true, the remaining parameter-data (if any) is skipped, and the next parameter is read. If the callback returns false, the current parameter will be read and put into the %P hash. This is a no-op callback:

sub my_callback {
   my ($fh, $name, $ct, $cta, $cd) = @_;
   my $data;
   read($fh, $data, 99999);
   if ($ct =~ /^text\/i) {
      my $charset = lc $cta->{charset};
      # do conversion of $data
   }
   (); # do not return true
}

The Handle-object given to the callback function is actually an object of type PApp::FormBuffer (see PApp::FormBuffer). It will not allow you to read more data than you are supposed to. Also, remember that the READ-Method will return a trailing CRLF even for data-files.

HINT: All strings (pathnames etc..) are probably in the charset specified by $state{papp_lcs}, but maybe not. In any case, they are octet strings so watch out!

PApp::flush [not exported by default]

Send generated output to the client and flush the output buffer. There is no need to call this function unless you have a long-running operation and want to partially output the page. Please note, however, that, as headers have to be output on the first call, no headers (this includes the content-type and character set) can be changed after this call. Also, you must not change any state variables or any related info after this call, as the result might not get saved in the database, so you better commit everything before flushing and then just continue output (use GET or POST to create new links after this).

Flushing does not yet harmonize with output stylesheet processing, for the semi-obvious reason that PApp::XSLT does not support streaming operation.

BUGS: No links that have been output so far can be followed until the document is finished, because the neccessary information will not reach the disk until the document.... is finished ;)

PApp::send_upcall BLOCK

Immediately stop processing of the current application and call BLOCK, which is run outside the handler compartment and without state or other goodies. It has to return one of the status codes (e.g. &PApp::OK). Never returns.

You should never need to call this function directly, rather use internal_redirect and other functions that use upcalls to do their work.

redirect url
internal_redirect url

Immediately redirect to the given url. These functions do not return!. redirect_url creates a http-302 (Page Moved) response, changing the url the browser sees (and displays). internal_redirect redirects the request internally (in the web-server), which is faster, but the browser might or might not see the url change.

abort_to surl-args

Similar to internal_redirect, but filters the arguments through surl. This is an easy way to switch to another module/webpage as a kind of exception mechanism. For example, I often use constructs like these:

my ($name, ...) = sql_fetch "select ... from game where id = ", $S{gameid};
abort_to "games_overview" unless defined $name;

This is used in the module showing game details. If it doesn't find the game it just aborts to the overview page with the list of games.

abort_with BLOCK

Abort processing of all modules and execute BLOCK as if it were the top-level module and never return. This function is handy when you are deeply nested inside a module stack but want to output your own page (e.g. a file download). Example:

abort_with {
   content_type "text/plain";
   echo "This is the only line ever output";
};
PApp::abort_with_file *FH [, content-type]

Abort processing of the current module stack, set the content-type header to the content-type given and sends the file given by *FH to the client. No cleanup-handlers or similar functions will get called and the function does of course not return. This function does not call close on the filehandle, so if you want to have the file closed after this function does its job you should not leave references to the file around.

debugbox

Create a small table with a single link "[switch debug mode ON]". Following that link will enable debugigng mode, reload the current page and display much more information (%state, %P, %$papp and the request parameters). Useful for development. Combined with the admin package ("admin" in macro), you can do nice things like this in your page:

#if admin_p
  <: debugbox :>
#endif
language_selector $translator [, $current_langid]

Create (and output) html code that allows the user to select one of the languages reachable through the $translator. If $current_langid is missing, uses $PApp::langs to select a suitable candidate.

This function might move elsewhere, as it is clearly out-of-place here ;)

Usually used like this:

<:language_selector $papp_translator:>

If you want to build your own language selector, here's how:

# iterate over all languages supported by this translator
for my $lang ($translator->langs) {

   # translate the language id into the vernacular language name
   my $name = PApp::I18n::translate_langid($lang, $lang);

   if ($lang eq $current) {
      # this is the currently selected language...
      echo "[$name]";
   } else {
      # or a language we could switch to
      echo slink "[$name]", SURL_SET_LOCALE($lang);
   }

}
preferences_url

Returns the url which, when selected, will guide the user to the papp preferences editor.

Creates an html-button (actually just A HREF at the moment) which guides the user to the papp preferences editor.

reload_p

Return the count of reloads, i.e. the number of times this page was reloaded (which means the session was forked).

This is a relatively costly operation (a database access), so do not do it by default, but only when you need it.

getpref $key

Return the named user-preference variable (or undef, when the variable does not exist) for the current application.

User preferences can be abused for other means, like timeout-based session authenticitation. This works, because user preferences, unlike state variables, change their values simultaneously in all sessions.

See also PApp::Prefs.

setpref $key, $value

Set the named preference variable. If $value is undef, then the variable will be deleted. You can pass in (serializable) references.

See also PApp::Prefs.

save_prefs

Save the preferences for all currently loaded applications.

switch_userid $newuserid

Switch the current session to a new userid. This is useful, for example, when you do your own user accounting and want a user to log-in. The new userid must exist, or bad things will happen, with the exception of userid zero, which sets the current user to the anonymous user (userid zero) without changing anything else.

$userid = PApp::newuid

Create a new (anonymous) user id.

$userid = getuid

Return a user id, allocating it if necessary (i.e. if the user has no unique id so far). This can be used to force usertracking, just call getuid in your newuser-callback. See also $userid to get the current userid (which might be zero).

PApp::config_eval BLOCK

Evaluate the block and call PApp->config_error if an error occurs. This function should be used to wrap any perl sections that should NOT keep the server from starting when an error is found during configuration (e.g. Apache <Perl>-Sections or the configuration block in CGI scripts). PApp->config_error is overwritten by the interface module and should usually do the right thing.

SEE ALSO

The macro/admin-package in the distribution, the demo-applications (.papp-files).

AUTHOR

Marc Lehmann <pcg@goof.com>
http://www.goof.com/pcg/marc/