Advanced Usage¶
A lot of tasks can be achieved just using the basic features detailed above. However there are more advanced features that can make life even easier
Blocks¶
Blocks are a powerful dynamic content generation tool. WebDyne can render arbitrary blocks of text or HTML within a page, which makes generation of dynamic content generally more readable than similar output generated within Perl code. An example:
<html>
<head>
<title>Blocks</title>
</head>
<body>
<p>
<form>
2 + 2 = <textfield name="sum">
<p><submit>
</form>
<p>
<perl method="check">
<!-- Each block below is only rendered if specifically requested by the Perl code -->
<block name="pass">
Yes, +{sum} is the correct answer ! Brilliant ..
</block>
<block name="fail">
I am sorry .. +{sum} is not correct .. Please try again !
</block>
<block name="silly">
Danger, does not compute ! .. "+{sum}" is not a number !
</block>
<p>
Thanks for playing !
</perl>
</body>
</html>
__PERL__
sub check {
my $self=shift();
if ((my $ans=$_{'sum'}) == 4) {
$self->render_block('pass')
}
elsif ($ans=~/^[0-9.]+$/) {
$self->render_block('fail')
}
elsif ($ans) {
$self->render_block('silly')
}
# Blocks aren't displayed until whole section rendered
#
return $self->render();
}
There can be more than one block with the same name - any block with the target name will be rendered:
<html>
<head><title>Hello World</title></head>
<body>
<p>
<form>
Enter your name:
<p><textfield name="name">
<p><submit>
</form>
<perl method="hello">
<!-- The following block is only rendered if we get a name - see the perl
code -->
<block name="greeting">
Hello +{name}, pleased to meet you !
<p>
</block>
<!-- This text is always rendered - it is not part of a block -->
The time here is !{! localtime() !}
<!-- This block has the same name as the first one, so will be rendered
whenever that one is -->
<block name="greeting">
<p>
It has been a pleasure to serve you, +{name} !
</block>
</perl>
</body>
</html>
__PERL__
sub hello {
my $self=shift();
# Only render greeting blocks if name given. Both blocks
# will be rendered, as the both have the name "greeting"
#
if ($_{'name'}) {
$self->render_block('greeting');
}
$self->render();
}
Like any other text or HTML between <perl> tags, blocks can take parameters to substitute into the text:
<html>
<head><title>Hello World</title></head>
<body>
<p>
<form>
Enter your name: <textfield name="name">
<submit>
</form>
<perl method="hello">
<!-- This block will be rendered multiple times, the output changing depending
on the variables values supplied as parameters -->
<block name="greeting">
${i} .. Hello +{name}, pleased to meet you !
<p>
</block>
The time here is <? localtime() ?>
</perl>
</body>
</html>
__PERL__
sub hello {
my $self=shift();
# Only render greeting blocks if name given. Both blocks
# will be rendered, as the both have the name "greeting"
#
if ($_{'name'}) {
for(my $i=0; $i<3; $i++) {
$self->render_block('greeting', i=>$i );
}
}
$self->render();
}
Blocks have a non-intuitive feature - they still display even if they are outside of the <perl> tags that made the call to render them. e.g. the following is OK:
<html>
<head><title>Hello World</title></head>
<body>
<!-- Perl block with no content -->
<perl method="hello">
</perl>
<p>
<!-- This block is not enclosed within the <perl> tags, but will still render -->
<block name="hello">
Hello World
</block>
<p>
<!-- So will this one -->
<block name="hello">
Again
</block>
</body>
</html>
__PERL__
sub hello {
my $self=shift();
$self->render_block('hello');
}
You can mix the two styles:
<html>
<head><title>Hello World</title></head>
<body>
<perl method="hello">
<!-- This block is rendered -->
<block name="hello">
Hello World
</block>
</perl>
<p>
<!-- So is this one, even though it is outside the <perl>..</perl> block -->
<block name="hello">
Again
</block>
</body>
</html>
__PERL__
sub hello {
my $self=shift();
$self->render_block('hello');
$self->render();
}
You can use the <block> tag display attribute to hide or show a block, or use a CGI parameter to determine visibility (e.g for a status update or warning):
<start_html>
<!-- Form to get block toggle status. Update hidden param based on toggle button -->
<form>
<submit name="button" value="Toggle">
<hidden name="toggle" value="!{! $_{'toggle'} ? 0 : 1 !}">
</form>
<!-- This block will only be displayed if the toggle value is true -->
<block name="toggle1" display="!{! $_{'toggle'} !}">
Toggle On (+{toggle})
</block>
<p>
<!-- This block will always display -->
<block name="hello" display=1>
Hello World
</block>
<!-- This block will never display unless called from a perl handler -->
<block name="hello" display=0>
Goodbye world
</block>
File inclusion¶
You can include other file fragments at compile time using the include tag:
<html>
<head><title>Hello World</title></head>
<body>
<p>
The protocols file on this machine:
<pre>
<include file="/etc/protocols">
</pre>
</body>
</html>
If the file name is not an absolute path name is will be loaded relative
to the directory of the parent file. For example if file "bar.psp"
incorporates the tag <include file="foo.psp"> it will be expected that
"foo.psp
" is in the same directory as "bar.psp
".
Important
The include tag pulls in the target file at compile time. Changes to the
included file after the WebDyne page is run the first time (resulting in
compilation) are not reflected in subsequent output unless the nocache
attribute is set. Thus the include tag should not be seen as a shortcut
to a pseudo Content Management System. For example <include
file="latest_news.txt"> will probably not behave in the way you expect.
The first time you run it the latest news is displayed. However updating
the "latest_news.txt" file will not result in changes to the output (it
will be stale).
If you do use the nocache
attribute the included page will be loaded
and parsed every time, significantly slowing down page display. There
are betters ways to build a CMS with WebDyne - use the include tag
sparingly !
You can include just the head or body section of a HTML or PSP file by using the head or body attributes. Here is the reference file (file to be included). It does not have to be a .psp file - a standard HTML file can be supplied :
And here is the generating file (the file that includes sections from the reference file).
<html>
<head>
<include head file="./include2.psp">
</head>
<body>
<include body file="./include2.psp">
You can also include block sections from .psp
files. If this is the
reference file (the file to be included) containing two blocks. This is
a renderable .psp
file in it's own right. The blocks use the display
attribute to demonstrate that they will produce output, but it's not
required:
<start_html>
<p>
<block name="block1" display>
This is block 1
</block>
<p>
<block name="block2" display>
This is block 2
</block>
And here is the file that brings in the blocks from the reference file and incorporates them into the output:
<start_html>
This is my master file
<p>
Here is some text pulled from the "include4.psp" file:
<p>
<include file="include4.psp" block="block1">
<p>
And another different block from the same file with caching disabled:
<p>
<include file="include4.psp" block="block2" nocache>
Static Sections¶
Sometimes you want to generate dynamic output in a page once only (e.g. a last modified date, a sidebar menu etc.) Using WebDyne this can be done with Perl or CGI code flagged with the "static" attribute. Any dynamic tag so flagged will be rendered at compile time, and the resulting output will become part of the compiled page - it will not change on subsequent page views, or have to be re-run each time the page is loaded. An example:
<html>
<head><title>Hello World</title></head>
<body>
<p>
Hello World
<hr>
<!-- Note the static attribute -->
<perl method="mtime" static="1">
<em>Last Modified: </em>${mtime}
</perl>
</body>
</html>
__PERL__
sub mtime {
my $self=shift();
my $r=$self->request();
my $srce_pn=$r->filename();
my $srce_mtime=(stat($srce_pn))[9];
my $srce_localmtime=localtime $srce_mtime;
return $self->render( mtime=>$srce_localmtime )
}
In fact the above page will render very quickly because it has no dynamic content at all once the <perl> content is flagged as static. The WebDyne engine will recognise this and store the page as a static HTML file in its cache. Whenever it is called WebDyne will use the Apache lookup_file() function to return the page as if it was just serving up static content.
You can check this by looking at the content of the WebDyne cache directory (usually /var/webdyne/cache). Any file with a ".html" extension represents the static version of a page.
Of course you can still mix static and dynamic Perl sections:
<html>
<head><title>Hello World</title></head>
<body>
<p>
Hello World
<p>
<!-- A normal dynamic section - code is run each time page is loaded -->
<perl method="localtime">
Current time: ${time}
</perl>
<hr>
<!-- Note the static attribute - code is run only once at compile time -->
<perl method="mtime" static="1">
<em>Last Modified: </em>${mtime}
</perl>
</body>
</html>
__PERL__
sub localtime {
shift()->render(time=>scalar localtime);
}
sub mtime {
my $self=shift();
my $r=$self->request();
my $srce_pn=$r->filename();
my $srce_mtime=(stat($srce_pn))[9];
my $srce_localmtime=localtime $srce_mtime;
return $self->render( mtime=>$srce_localmtime )
}
If you want the whole pages to be static, then flagging everything with the "static" attribute can be cumbersome. There is a special meta tag which flags the entire page as static:
<html>
<head>
<!-- Special meta tag -->
<meta name="WebDyne" content="static=1">
<title>Hello World</title>
</head>
<body>
<p>
Hello World
<hr>
<!-- A normal dynamic section, but because of the meta tag it will be frozen
at compile time -->
<perl method="localtime">
Current time: ${time}
</perl>
<!-- Note the static attribute. It is redundant now the whole page is flagged
as static - it could be removed safely. -->
<p>
<perl method="mtime" static="1">
<em>Last Modified: </em>${mtime}
</perl>
</body>
</html>
__PERL__
sub localtime {
shift()->render(time=>scalar localtime);
}
sub mtime {
my $self=shift();
my $r=$self->request();
my $srce_pn=$r->filename();
my $srce_mtime=(stat($srce_pn))[9];
my $srce_localmtime=localtime $srce_mtime;
return $self->render( mtime=>$srce_localmtime )
}
If you don't like the idea of setting the static flag in meta data, then "using" the special package "WebDyne::Static" will have exactly the same effect:
<html>
<head>
<title>Hello World</title>
</head>
<body>
<p>
Hello World
<hr>
<perl method="localtime">
Current time: ${time}
</perl>
<p>
<perl method="mtime">
<em>Last Modified: </em>${mtime}
</perl>
</body>
</html>
__PERL__
# Makes the whole page static
#
use WebDyne::Static;
sub localtime {
shift()->render(time=>scalar localtime);
}
sub mtime {
my $self=shift();
my $r=$self->request();
my $srce_pn=$r->filename();
my $srce_mtime=(stat($srce_pn))[9];
my $srce_localmtime=localtime $srce_mtime;
return $self->render( mtime=>$srce_localmtime )
}
If the static tag seems trivial consider the example that displayed country codes:
<html>
<head><title>Hello World</title></head>
<body>
<p>
<!-- Generate all country names for picklist -->
<form>
Your Country ?
<perl method="countries">
<popup_menu values="${countries_ar}" default="Australia">
</perl>
</form>
</body>
</html>
__PERL__
use Locale::Country;
sub countries {
my $self=shift();
my @countries = sort { $a cmp $b } all_country_names();
$self->render( countries_ar=>\@countries );
}
Every time the above example is viewed the Country Name list is
generated dynamically via the Locale::Country module. This is a waste of
resources because the list changes very infrequently. We can keep the
code neat but gain a lot of speed by adding the static
tag attribute:
<html>
<head><title>Hello World</title></head>
<body>
<p>
<!-- Generate all country names for picklist -->
<form>
Your Country ?
<perl method="countries" static="1">
<!-- Note the addition of the static attribute -->
<popup_menu values="${countries_ar}">
</perl>
</form>
</body>
</html>
__PERL__
use Locale::Country;
sub countries {
my $self=shift();
my @countries = sort {$a cmp $b} all_country_names();
$self->render( countries_ar=>\@countries );
}
By simply adding the "static" attribute output on a sample machine resulted in a 4x speedup in page loads. Judicious use of the static tag in places with slow changing data can markedly increase efficiency of the WebDyne engine.
Caching¶
WebDyne has the ability to cache the compiled version of a dynamic page according to specs you set via the API. When coupled with pages/blocks that are flagged as static this presents some powerful possibilities.
Important
Caching will only work if $WEBDYNE_CACHE_DN
is defined and set to a
directory that the web server has write access to. If caching does not
work check that $WEBDYNE_CACHE_DN
is defined and permissions set
correctly for your web server.
There are many potential examples, but consider this one: you have a page that generates output by making a complex query to a database, which takes a lot of CPU and disk IO resources to generate. You need to update the page reasonably frequently (e.g. a weather forecast, near real time sales stats), but can't afford to have the query run every time someone view the page.
WebDyne allows you to configure the page to cache the output for a period of time (say 5 minutes) before re-running the query. In this way users sees near real-time data without imposing a high load on the database/Web server.
WebDyne knows to enable the caching code by looking for a meta tag, or
by loading the WebDyne::Cache
module in a __PERL__ block.
The cache code can command WebDyne to recompile a page based on any arbitrary criteria it desires. As an example the following code will recompile the page every 10 seconds. If viewed in between refresh intervals WebDyne will serve up the cached HTML result using Apache r$->lookup_file() or the FCGI equivalent, which is very fast.
Try it by running the following example and clicking refresh a few times over a 20 second interval
<html>
<head>
<title>Caching</title>
<!-- Set static and cache meta parameters -->
<meta name="WebDyne" content="cache=&cache;static=1">
</head>
<body>
<p>
This page will update once every 10 seconds.
<p>
Hello World !{! localtime() !}
</body>
</html>
__PERL__
# The following would work in place of the meta tags
#
#use WebDyne::Static;
#use WebDyne::Cache (\&cache);
sub cache {
my $self=shift();
# Get cache file mtime (modified time)
#
my $mtime=${ $self->cache_mtime() };
# If older than 10 seconds force recompile
#
if ((time()-$mtime) > 10) {
$self->cache_compile(1)
};
# Done
#
return \undef;
}
WebDyne uses the return value of the nominated cache routine to determine what UID (unique ID) to assign to the page. In the above example we returned \undef, which signifies that the UID will remain unchanged.
You can start to get more advanced in your handling of cached pages by returning a different UID based on some arbitrary criteria. To extend our example above: say we have a page that generated sales figures for a given month. The SQL code to do this takes a long time, and we do not want to hit the database every time someone loads up the page. However we cannot just cache the output, as it will vary depending on the month the user chooses. We can tell the cache code to generate a different UID based on the month selected, then cache the resulting output.
The following example simulates such a scenario:
<!-- Start to cheat by using start/end_html tags to save space -->
<start_html>
<form method="GET">
Get sales results for: <popup_menu name="month" values="@{qw(January February March)}">
<submit>
</form>
<perl method="results">
Sales results for +{month}: $${results}
</perl>
<hr>
This page generated: !{! localtime() !}
<end_html>
__PERL__
use WebDyne::Static;
use WebDyne::Cache (\&cache);
my %results=(
January => 20,
February => 30,
March => 40
);
sub cache {
# Return UID based on month
#
my $uid=undef;
if (my $month=$_{'month'}) {
# Make sure month is valid
#
$uid=$month if defined $results{$month}
}
return \$uid;
}
sub results {
my $self=shift();
if (my $month=$_{'month'}) {
# Could be a really long complex SQL query ...
#
my $results=$results{$month};
# And display
#
return $self->render(results => $results);
}
else {
return \undef;
}
}
Important
Take care when using user-supplied input to generate the page UID. There is no inbuilt code in WebDyne to limit the number of UID's associated with a page. Unless we check it, a malicious user could potentially DOS the server by supplying endless random "months" to the above page with a script, causing WebDyne to create a new file for each UID - perhaps eventually filling the disk partition that holds the cache directory. That is why we check the month is valid in the code above.
JSON¶
WebDyne has a <json> tag that can be used to present JSON data objects to Javascript libraries in an output page. Here is a very simple example:
<start_html title="Sample JSON Chart" script="https://cdn.jsdelivr.net/npm/chart.js">
<h2>Monthly Sales Chart</h2>
<canvas id="myChart"></canvas>
<json handler="chart_data" id="chartData">
<script>
// Parse JSON from the script tag
const data = JSON.parse(document.getElementById("chartData").textContent);
const ctx = document.getElementById('myChart').getContext('2d');
new Chart(ctx, {
type: 'bar', // You can also use 'line', 'pie', etc.
data: {
labels: data.labels,
datasets: [{
label: 'Sales',
data: data.values,
}]
}
});
</script>
__PERL__
sub chart_data {
my %data=(
labels => [qw(Jan Feb Mar Apr)],
values => [(120, 150, 180, 100)]
);
return \%data
}
If you run it and review the source HTML you will see the JSON data rendered into the page as <script></script> block of type application/json with an id of "chartData". Any data returned by the perl routine nominated by the json tag is presented as JSON within that tag block, and available to Javascript libraries within the page. JSON data is kept in canononical order by default, which can be adjusted with the WEBDYNE_JSON_CANONICAL variable if not desired/needed for a very small speed-up.
Dump¶
The <dump> tag is a informational element which can be included in a
page for diagnostic or debugging purposes. It will show various variable
and state values for the page. By default if a <dump> flag is embedded
in a page diagnostic information is not shown unless the force
attribute is specified or the $WEBDYNE_DUMP_FLAG
is set, the latter
allowing the <dump> tag to be embedded into all pages on a site but
not activated unless debugging enabled.
Various diagnostic elements can be displayed - see the <dump> tag section for information on what they are. In this example all components are enabled and display is forced:
Dump display/hide can be controlled by form parameters or run URI query strings. In the example below ticking the checkbox or simply appending "?dump_enable=1" to the URL will display the dump information: