I'll attempt to explain how we do it but first I'll tell you why.
Initially when I was designing our web infrastructure I wanted to keep
design, content and code separate. Unfortunately the normal process is
for the stylesheets to determine what goes on a page. I didn't like
this. I wanted to drive what appears on a page from the XML and for the
stylesheets to simply render what XML was available. I also wanted to
split the components of a page into separate XML files. So the menu,
header, footer etc would all be separate XML components and each would
have a separate stylesheet to render it. We also wanted the components'
stylesheets to inherit qualities from the main page stylesheet.

The way we achieved this was to have a default.xml file that forms the
basis of every page on our site.

It looks a bit like this..

<?xml version="1.0" encoding="ISO-8859-1"?>

<?xml-stylesheet type="application/x-xsp" href="."?>

<?xml-stylesheet type="application/x-xpathscript"
                 href="/stylesheets/<<ROOTSTYLESHEET>>"
                 ?>

<xsp:page
    xmlns:util="http://apache.org/xsp/util/v1";
    xmlns:xsp="http://apache.org/xsp/core/v1";
    xmlns:web="http://axkit.org/NS/xsp/webutils/v1";
    xmlns:error="http://axkit.org/NS/xsp/error/redirector/v1";
    xmlns:f="http://axkit.org/NS/xsp/perform/v1";
    xmlns:service="http://axkit.org/NS/xsp/service/v1";
    language="perl">

<xsp:logic>
  <![CDATA[ 

  # This provides the Connector object for our application server
  use AppServer::Connector;
  my $c = new AppServer::Connector();

  # Then all our PerForm callbacks go here..
  # for example the signin submit which passes the signin form
parameters
  # to the applications User::sign_in method.
  sub submit_signin {
    my ($ctxt) = @_;
    my $form = { map {$_, $ctxt->{Form}->get($_) } keys %{$ctxt->{Form}
};
    my $u = $c->new_obj('User');
    $u->sign_in($form); 
    return {$r->args}->{returnto} || '/';
  }

  ...

  <roottag>
   <xsp:logic>
 
    # here, at the beginning of every page execution
    # we start the connection to the app server
    $c->start($r);   

    # here we have code that forces redirection if 
    # the page needs to be in HTTPS 

   </xsp:logic>

    <<COMPONENTSGOHERE>>

  </roottag>
</xsp:page>

----------------------

The tag <<COMPONENTSGOHERE>> is replaced by any components that need to
be on the page. 

If we were visiting www.mysite.com/homepage.xml

The first (primary) component to be included would be homepage.xml. This
might look like this

<?xml version="1.0" encoding="ISO-8859-1"?>
  <page>
    <config>
     <stylesheet>homepage.xps</stylesheet>
     <include>/components/news.xml</include>
     <include>/components/menu.xml</include>
     <leftpanelcolor>FF44GG</leftpanelcolor>
     <shorttitle language="en">MySite Homepage!</shorttitle>
    </config>
 
    <content language="en">
      <blurb> 
       <para> some content... </para>
       <para> some content... </para>
      </blurb>
    </content>
 
    <content language="de"
      <blurb>
        <para> german translated content .. </para>
      </blurb>
    </content>

  </page>

You will see that the homepage xml file 'includes' the news components.
The news.xml file looks like this.

<?xml version="1.0" encoding="ISO-8859-1"?>
  <page>
    <config>
     <stylesheet>news.xps</stylesheet>
    </config>

    <content>
      <xsp:logic>
        # the $c connector object is always available in every XSP
component 
        # because it is initialised in the default.xml page

        my $news = $c->new_obj('News');
        foreach my $news_item ( $news->get_latest() ) {
      </xsp:logic>

        <newsitem>
          <title><xsp:expr>$news_item->{title}</xsp:expr></title>
          <desc><xsp:expr>$news_item->{desc}</xsp:expr></desc>
          <date><xsp:expr>$news_item->{date}</xsp:expr></date>
        </newsitem>

      <xsp:logic>
        }
      </xsp:logic>

    </content>
</page>
 
Here you see we use the XML do define the nature of the news. The news
might well be held in the XML itself but in this example I have shown
how we get the news from the application server. We use the $c connector
to instantiate the News object on the application server and then we
pull the latest news (which we have to suck from Lotus Notes :[ ).


And the menu.xml and other components are similar.

The xml files themselves are NEVER loaded on the fly. We have a script
that parses the files, extracts the <config> sections and the <content>
sections and saves the whole site into one file (xml_data) using
Storable::nstore.

The reason we do this is because we wanted the files to inherit from one
another. so for example, any <include> directives in an index.xml file
are automatically inherited by every other file within the same
directory and also in every file in any subdirectory. This allows us to
control the layout and design of whole sections of the site with
relative ease but means that we have to run a this preparation script
after making changes to any xml.

This preparation phase also manages the content between different
(country) SITES. All our generic (english) content sites in a folder
called 'default'. All site specific content sits in folders called 'gb',
'fr', 'de' etc. All content is pulled from default UNLESS content exists
specifically for that site within its own site folder. To exclude
default content from being displayed on a particular site we make an
empty page in that site's folder with a <hide/> flag. Note, this is
different to the different language versioning which is all contained
within the individual XML pages as shown above.

In order to combine these various components with the default xml page
described above, we have a handler (Custom::AxKit) that runs BEFORE
AxKit. So in our Apache Config, we use Apache Filter and do   

<FilesMatch "\.xml$">
    AddType text/xml .xml
    SetHandler perl-script
    PerlSetVar Filter On
    PerlHandler Custom::AxKit AxKit
    ....

</FilesMatch>

The Custom Handler examines the URI which is used to determine the
primary component, loads the component from the xml_data file and any
other components that are required to render the page. These are then
combined and placed into the default XML file above. The
<<ROOTSTYLESHEET>> is also set. It defaults to default.xps unless a page
specifically wants to override it, for example for the purposes of a
popup window. I'll explain our stylesheets next.

The final XML (XSP) page looks like this..

<?xml version="1.0" encoding="ISO-8859-1"?>

<?xml-stylesheet type="application/x-xsp" href="."?>

<?xml-stylesheet type="application/x-xpathscript"
                 href="/stylesheets/default.xps"
                 ?>

<xsp:page
    xmlns:util="http://apache.org/xsp/util/v1";
    xmlns:xsp="http://apache.org/xsp/core/v1";
    xmlns:web="http://axkit.org/NS/xsp/webutils/v1";
    xmlns:error="http://axkit.org/NS/xsp/error/redirector/v1";
    xmlns:f="http://axkit.org/NS/xsp/perform/v1";
    xmlns:service="http://axkit.org/NS/xsp/service/v1";
    language="perl">

<xsp:logic>
  <![CDATA[ 

  # This provides the Connector object for our application server
  use AppServer::Connector;
  my $c = new AppServer::Connector();

  # Then all our PerForm callbacks go here..
  # for example the signin submit which passes the signin form
parameters
  # to the applications User::sign_in method.
  sub submit_signin {
    my ($ctxt) = @_;
    my $form = { map {$_, $ctxt->{Form}->get($_) } keys %{$ctxt->{Form}
};
    my $u = $c->new_obj('User');
    $u->sign_in($form); 
    return {$r->args}->{returnto} || '/';
  }

  ...

  <roottag>
   <xsp:logic>
 
    # here, at the beginning of every page execution
    # we start the connection to the app server
    $c->start($r);   

    # here we have code that forces redirection if 
    # the page needs to be in HTTPS 

   </xsp:logic>

   <homepage>
     <content language="en>
       <blurb> 
        <para> some content... </para>
        <para> some content... </para>
       </blurb>
     </content>
   </homepage>

   <components>

     <news stylesheet="news.xps">
       <xsp:logic>
         my $news = $c->new_obj('News');
         foreach my $news_item ( $news->get_latest() ) {
       </xsp:logic>

        <newsitem>
          <title><xsp:expr>$news_item->{title}</xsp:expr></title>
          <desc><xsp:expr>$news_item->{desc}</xsp:expr></desc>
          <date><xsp:expr>$news_item->{date}</xsp:expr></date>
        </newsitem>

       <xsp:logic>
       }
       </xsp:logic>     
     </news> 

     <menu stylsheet="menu.xps">
       ..contents from menu.xml..
     </menu>

     <header stylesheet="header.xps">
       ..contents from header.xml..
     </header>

     <footer stylesheet="footer.xps">
       ..contents from footer.xml..
     </footer>

   </components>

  </roottag>
</xsp:page>

---------

Once this page is created it is cached, keyed against the URL, SITE and
LANGUAGE that it was created from. Then it is passed to AxKit!

When AxKit receives it, it executes it as an XSP page. This forms a
connection with our application server, performs any logic that needs to
be done and the result is static XML which is NOT cached since most of
the pages on our site are dynamic. If our site was full of static
content, we might want to Cache the static XML page.

This XML page is now transformed into HTML using XPathScript. This is
where is gets interesting and also where I have had to hack
XPathScript.pm.


So for the stylesheets. 
As with the XML, there is 1 default stylesheet used for almost every
page. This defines the layout of the page. It might look like this..

<% 
  my $uri = $r->uri;
  my $page = get_page($uri} 
%>

<html>
  <head>
    <title><%$page->{shorttitle}%></title>
  </head>

<body>

<%= language('Welcome') %> <%= $user %>

<table>
  <tr>
    <td><img src="logo.gif"></td>
    <td><%= insert("/page/components/header.xml") %></td>
  </tr>
  <tr>
    <td><%= insert("/page/components/menu.xml") %></td>
    <td><%= insert("/page/$uri") %></td>
  </tr>
  <tr>
    <td><%= insert("/page/components/news.xml") %></td>
    <td><%= insert("/page/components/footer.xml") %></td>
  </tr>

</table>

</body>
</html>

----------

The interesting bit is the insert() method which I have added to
XPathScript.pm.

The insert method looks like this...

     sub insert {
         my ($node_name,$context_node) = @_;

         $node_name =~ s/(\.xml|\/)$//; #strip trailing / or .xml

         # get a local copy of ($t)ranslations ($p)
         my $p =
&Apache::AxKit::Language::XPathScript::Toys::clone($Apache::AxKit::Langu
age::XPathScript::trans);
         my $r = $Apache::AxKit::Language::XPathScript::request;

         my $n;
         # if node_name provided get a $node object
         if ($node_name) {
           #warn "Getting \$n  from node_name $node_name'\n";
           $node_name = ( $node_name =~ /^\/page/ ) ? $node_name :
"/page".$node_name;
           ($n) = (ref $context_node) ?
findnodes($node_name,$context_node) : findnodes($node_name);
         } else {
           # otherwise use context node as node
           $n = $context_node;
         }
         $node_name = ($n) ? $n->getName : $node_name;

         # look for stylesheet to import
         # then apply templates using imported translations
         if ($n) {
         #warn " - Node found, determining stylesheet\n";
         my ($node_path,$node_style);
         {
           my $n = $n;
             while ($n = $n->getParentNode) {
             my $node_name = $n->getName;
             $node_path = "$node_name/$node_path" unless $node_name eq
'page';
             }
         }
         $node_path .= $node_name;

         $node_style = $n->getAttribute('stylesheet');

         if ($node_style) {
             warn " - Loading Stylesheet: $node_style\n";
             my $url = "/stylesheets/$node_style";
             $p->{$node_name}->{testcode} = import_template($url);
         }

         my $applied = apply_templates($p,$n);

         warn " - Inserted Component '$node_name' ($node_style) \n";
         return $applied;
         } else {
         warn " - Missing Component '$node_name'\n";
         return "<empty_component/>";
         }
     }

----------
The parameter you pass to the insert() method is the name of the
component you want to place at that point on the page.
What it does is to take a clone ($p) of the current XPathScript
translations, $t. It then looks to see if there is a node in the xml
page with the name of the component you have requested. It checks to see
if that component has a stylesheet attribute. If it does, it loads the
stylesheet using the import_template function (which I think I also had
to adjust slightly). The loaded stylesheet is added to the cloned
translations. The effect of doing this is to create a new set of
translations based on the parents translation set but including the
component specific stylesheet WITHOUT affecting the parent or any other
components translations. Finally, the translations are applied to the
component and the resulting HTML or whatever is returned for inclusion
in the parent component.


This works in a nested fashion so you can have components within in
components, each with their own stylesheet.

=========
If a component's node does not exist within the XML, the component isn't
shown. The Stylesheet is not driving WHAT is shown, only HOW it is
shown. Whether or not a component is on a page is determined by whether
it was <included/> within the <config/> section of the xml file.
==========

A components individual stylesheet may be a simple set of translations
(er transformations? I forget :] )

eg 

<% my ($n) = @_);

<%
  $t->{para}->{pre} = '<p><b>';
  $t->{para}->{post} = '</b></p>';
%>

<%=apply_templates("*", $n)%>

OR, 
it could be a more detailed layout like this.

<% my ($n) = @_);

<%
  $t->{para}->{pre} = '<p><b>';
  $t->{para}->{post} = '</b></p>';
%>

<table>

<% foreach my $node ( $n->findnodes('/some/path') ) {
 <tr><td><%$apply_templates($node)</td></tr>
<% } %>

</table>

I also cache the final HTML page. It is keyed against a hash that is
formed from the resulting XML. If the XML that resulted from the
execution of the XSP stage hasnt changed since last time and the
stylesheet hasnt been modified then the HTML wont need to change so the
Stylesheets dont need to be applied again.

The whole page compilation time, including building the XML page,
executing the XSP, connecting to the Application server, getting the
session data and applying the stylesheets takes about 0.3-0.5 seconds.
However, our webservers cannot manage a particularly high sustained rate
of requests. I forget the figures but I think they max out at about 10
requests/second. I'm sure this is down to the building and execution of
the XSP page and the XPathScript transformations as they are pretty cpu
intensive. 

Visit www.bydeluxe.net to see it in action, it's not hugely exciting im
afraid since most of the dynamic stuff is only available to account
holders.

The only issues I have with this is that the XPathScript transformations
are not that fast, especially as there are multiple XPathScript
stylesheets applied for each page.

The fact that I've had to modify XPathScript.pm and have a custom
handler in front of AxKit means I'm a bit cagey about upgrading to the
latest version of AxKit.

PerForm can be a bit awkward to say the least - it doesnt provide a way
to generate lots of similarly named fields, ie input_1, input_2 etc and
it forces us to put all our callbacks outside of the root page tag -
which I believe is a closure issue.

Caching is problematic. If you want things like "You are now logged in"
on every page, then how can you avoid executing the XSP since you need
to access the database to check the users session.



If you made it this far, well done :)




-----Original Message-----
From: J�rg Walter [mailto:[EMAIL PROTECTED]] 
Sent: 04 February 2003 20:06
To: [EMAIL PROTECTED]
Subject: Re: AxKit for web applications


On Tuesday, 04. February 2003 20:47, Tom Howe wrote:

> Since every page request requires the users session to be pulled from 
> the database and the sessions can (occassionally) be over a meg its 
> important that the messaging system is quick and so far we havnt had 
> any problems. In fact the bottleneck in our system is definately 
> XPathScript rendering and the way we generate our XSP pages on the 
> fly. The load on our webservers is much higher than our application 
> servers.

Hah! Gotcha! I knew there must be others who generate their XSP
on-the-fly 
while knowing what they do. :-)
I am doing that, too, and plan to revise AxKit's XSP caching. Currently,
the 
generated code is not cached if it's not directly from a provider. My 
question is: Is your code cacheable, and what determines if it is
up-to-date? 
This can be a major time-saver as generating and eval'ing the code is
not 
neccessary on a cache hit.
I'd love to take your requirements into account when going in there.

-- 
CU
  Joerg

PGP Public Key at http://ich.bin.kein.hoschi.de/~trouble/public_key.asc
PGP Key fingerprint = D34F 57C4 99D8 8F16 E16E  7779 CDDC 41A4 4C48 6F94


---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]


---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to