RayApp
Framework for data-centric Web applications
Synopsis
use RayApp; my $rayapp = new RayApp; my $dsd = $rayapp->load_dsd('structure.dsd'); print $dsd->serialize_data( $data );
Introduction
The RayApp provides a framework for data-centric Web applications. Instead of writing Perl code that prints HTML, or a code that calls functions that print HTML, or embedding the code inside of HTML markup, the Web applications only process and return Perl data. No markup handling is done in the code of individual applications, thus application code can focus on the business logic. This reduces the presentation noise in individual applications, increases maintainability and speeds development.
The data returned by the application is then serialized to XML and can be postprocessed by XSLT to desired output format, which may be HTML, XHTML, WML or anything else. In order to provide all parties involved (analysts, application programmers, Web designers, ...) with a common specification of the data format, data structure description (DSD) file is a mandatory part of the applications. The DSD describes what parameters the application expects and what data it will return, therefore, what XML will come out of that data. The data returned by the application in a form of hash by the Perl code is fitted into the data structure, creating XML file with agreed-on elements.
This way, application programmers know what data is expected from their applications and Web designers know what XMLs the prostprocessing stage will be dealing with, in advance. In addition, application code can be tested separately from the presentation part, and tests for both application and presentation part can be written independently, in parallel. Of course, this also works if you are the sole person on the project, playing the above mentioned roles.
The system will never produce unexpected data output, since the data output is based on DSD which is known.
Configuration
Most of the use of RayApp is expected in the Web context. This section summarizes configuration steps needed for the Apache HTTP server. This version of RayApp works with Apache 2.0 and mod_perl 2.0.
Assume you have a Web application that should reside on URL
http://server/sub/app.html
The application consists of three files:
/opt/www/app.dsd /opt/www/app.pl /opt/www/app.xsl
Yes, instead of sticking application code and presentation into one file, we separate them completely.
Whenever a request for /sub/app.html comes, the DSD /opt/www/app.dsd is loaded, app.pl (or app.mpl) executed and its output serialized to HTML using app.xsl. For syntax of app.dsd, see RayApp::DSD. In the app.pl script, there has to be at least one function, called handler, and this function should return Perl hash with data matching the DSD. The whole content of the .pl is evaluated in a package context in a Apache::Registry manner, so two handler methods from different applications do not clash. In app.xsl, there should be an XSLT stylesheet.
If you issue a request for /sub/app.xml, the presentation postprocessing is skipped and you get the XML output -- ideal for debugging.
If the app.html file exists in the filesystem, it "overrides" any attempts to is generate dynamic content, and the file is returned. Likewise, if there is a app.xml file in the filesystem and there is a request for app.xml, the XML file is returned. If there is app.xml but no app.html and a request for app.html comes, the app.xml is serialized using app.xsl. So RayApp can be used not only for fully dynamic sites, but also as a XSLT processor.
You will need to configure Apache for RayApp to do its job. It can operate both in the mod_perl and in pure CGI way.
Pure mod_perl approach
If you have a mod_perl 2 support in your Apache 2 and want to use it to run you RayApp-based applications, the following setup will give you the correct result:
Alias /sub /opt/www <LocationMatch ^/sub/(.+\.(html|xml))?$/> SetHandler perl-script PerlResponseHandler RayApp::mod_perl </LocationMatch>
The Alias directive ensures that the DSD and Perl code will be correctly found in the /opt/www/ directory, for requests coming to /sub/ Location.
Instead of perl-script, you can also use SetHandler modperl.
CGI approach
In the RayApp distribution, there is a script rayapp_cgi_wrapper. Assuming it was installed in the /usr/bin directory, the configuration is
ScriptAliasMatch ^/sub/(.+(\.(html|xml))?)?$ /usr/bin/rayapp_cgi_wrapper/$1 <Location /sub> SetEnv RAYAPP_DIRECTORY /opt/www </Location>
With the ScriptAliasMatch directive in effect, the request will be processed by usr/bin/rayapp_cgi_wrapper, and the SetEnv RAYAPP_DIRECTORY tells RayApp where on the filesystem should it find the necessary files of the application (the same as Alias with mod_perl setup). Here we partly simulate the work that Apache would have done for us in the static files case.
Both LocationMatch in mod_perl case and Location in CGI case can of course contain additional configuration options for Apache, like Order / Allow, and configuration of RayApp, described in the next section.
More detailed setup
For mod_perl, the configuration is done using PerlSetVar and the variable name in in mixed capitals. For CGI, enviroment variables are used, set using SetEnv and the variable name is in all capitals, words separated by underscores. For example, for mod_perl the directive would be
PerlSetVar RayAppDirectoryIndex index.html
and for CGI, it would read
SetEnv RAYAPP_DIRECTORY_INDEX index.html
Supported options:
- RayAppDirectoryIndex / RAYAPP_DIRECTORY_INDEX
-
Similar to Apache's DirectoryIndex directive, this will be used for requests ending with a slash, or generaly requests resulting into requests for directories. As RayApp's output is dynamically generated, pure DirectoryIndex cannot be used.
If not set, nothing will be served for directory requests.
- RayAppInputModule / RAYAPP_INPUT_MODULE
-
As already noted, RayApp runs the handler function found in the application file (app.pl). Often applications share the same context -- all of them want to be passed an open $dbh database handler (instead of doing their own DBI->connect), all of them want to be passed the request object to query the input parameters.
The option RayAppInputModule specifies a module name which will be loaded and from which a handler function will be called for every request. The function should return a list of values that will be passed in as parameters to the application handler.
The first parameter passed to this input module handler is the RayApp::DSD object, for mod_perl the second argument is the request (Apache2::RequestRec) object. An example of an input module might be
package Application::Input; use RayApp::Request (); use DBI (); sub handler { my ($dsd, $r) = @_; if (defined $dsd) { $dsd->validate_parameters($q) or die $dsd->errstr; } my $dbh = DBI->connect('dbi:Oracle:prod', 'scott', 'tiger', { RaiseError => 1, AutoCommit => 0 }); my $q = new RayApp::Request($r); return ($dbh, $q); } 1;
Here we first validate parameters against DSD (you can optionally die or just log an error, depending on how strict you want to be), we connect to the database, so that the applications get connection to their database backend, and we use RayApp::Request to get uniform (for mod_perl and CGI) query object. The values $dbh and $q are returned and will be passed as argument to the application handler.
- RayAppStyleParamModule / RAYAPP_STYLE_PARAM_MODULE
-
There are often additional data except the core data of the application that you might like to process in your XSLT stylesheets -- id of the authenticated user, the full and relative URLs of the currently running application, some sticky preferences of the user. They are more related to the presentation than to the business logic of the application, so you do not want to have them in your DSD and have all your applications generate them and return them.
The option RayAppStyleParamModule specifies a module name from which a handler function will be called for every request that goes to the postprocessing stage. It will be passed the DSD object and the same arguments as the application handler (those returned by RayAppInputModule) and it should return a list of key => value pairs that will be passed to the stylesheet.
A simple style parameter module might look like this:
package Application::Style_params; sub handler { my ($dsd, $dbh, $q) = @_; return ( my_full_url => $q->url( -full => 1 ), my_relative_url => $q->url( -relative => 1 ), dbuser => $dbh->{'Username'}, ); } 1;
and in the XSLT you will get to them for example via
<xsl:value-of select="$my_relative_url"/>
- RayAppStyleStaticParams / RAYAPP_STYLE_STATIC_PARAMS
-
When a static .xml file is processed into HTML, the XSL transformation is run, even if there was no application invocation in the process. Normally, the handler of module specified by RayAppInputModule would not be run in this case. If you want the module to be run even in this case (thus generating input argument for the RayAppStyleParamModule module), set this option to true.
The applications
Having the Web server set up, you can write your first application in RayApp manner. For start, a simplistic application which only returns two values will be enough.
First the DSD file, /opt/www/app.dsd:
<?xml version="1.0"?> <root> <_param name="name"/> <name/> <time/> </root>
The application will accept one parameter, name and will return hash with two values, name and time. The code in /opt/www/app.mpl can be
sub handler { my ($dbh, $q) = @_; return { name => scalar($q->param('name')), time => time, }; } 1;
Note that the $dbh and $q values are generated by the module specified by RayAppInputModule.
The application returns a hash with two elements. A request for
http://server/sub/app.xml?name=Peter
should return
<?xml version="1.0"?> <root> <name>Peter</name> <time>1075057209</time> </root>
Adding the /opt/www/app.xsl file with XSLT templates should be easy now.
See also
RayApp::DSD(3), RayApp::Request(3)
Author
Copyright (c) Jan Pazdziora 2001--2006
Version
This documentation is believed to describe accurately RayApp version 2.004.
Pod errors
Hey! The above document had some coding errors, which are explained below:
- Around line 567:
-
You forgot a '=back' before '=head2'