Programming With Stencil


Hello World

Let's start off with a version of the classic "Hello World" program.

helloworld.C:
// Copyright (c) 2004 David Muse
// See the COPYING file for more information

#include <stencil/inputoutput.h>
#include <stencil/request.h>
#include <stencil/response.h>

int main(int argc, char **argv) {

        inputoutput     io(NULL);
        request         req(&io);
        response        resp(&io);

        // send an http header
        resp.textPlain();

        // write hello world
        io.output("hello world!\n");

        exit(0);
}

The inputoutput class provides methods for writing data to the browser. The request class provides methods for accessing data that was sent in the request from the browser; cookies, form entry variables, etc. The response class provides methods for sending HTTP headers to the browser. Most stencil programs will use an instance of each. This one doesn't actually use the request class, but most programs will.

This simple program sends a text/plain HTTP header, followed by "hello world!"

Stencil provides a script that returns the compile flags necessary to compile stencil programs. This program can be compiled as follows:

gcc `stencil-config --cflags` -o helloworld.cgi helloworld.C `stencil-config --libs`

CGI's and Apache Modules

Stencil programs can be compiled into CGI's or Apache modules. Below is the same hello world program with the necessary code to allow it to be compiled as a CGI or Apache module.

cgiapache.C:
// Copyright (c) 2004 David Muse
// See the COPYING file for more information

#include <stencil/inputoutput.h>
#include <stencil/request.h>
#include <stencil/response.h>

extern "C" {

#ifdef APACHEMODULE
        #define NAME helloworld
        #include <stencil/apachemodule.h>
#else
        #include <stencil/cgimodule.h>
#endif

MAIN {

        inputoutput     io(APISTRUCT);
        request         req(&io);
        response        resp(&io);

        // send an http header
        resp.textPlain();

        // write hello world
        io.output("hello world!\n");

        EXIT(0);
}

}

This program can be compiled into a CGI as follows:

gcc `stencil-config --cflags` -o helloworld.cgi helloworld.C `stencil-config --libs`

It can be compiled as an Apache module as follows:

gcc `stencil-config --apache-cflags` -shared -o mod_helloworld.so helloworld.C `stencil-config --apache-libs`

Installing a CGI is pretty simple, you just copy the .cgi file into the appropriate directory.

Installing an Apache module is more complex. Apache must be configured to use the module. To configure Apache to use the module, edit the httpd.conf file and add a directive like the following:

LoadModule helloworld_module    lib/apache/mod_helloworld.so

If you're using Apache 1.3, you'll also have to add this directive:

AddModule mod_helloworld.c

These directives assume that mod_helloworld.so is installed below the ServerRoot in lib/apache. If it's installed somewhere else, you should substitute lib/apache with the appropriate pathname.

To configure apache to use a module instead of a CGI, use a combination of the Location and SetHandler directives. For example, to handle the URL /helloworld.cgi with mod_helloworld.so, use the following directives.

<Location /helloworld.cgi>
SetHandler helloworld
</Location>

In the example, NAME was defined as helloworld. So helloworld is used with the SetHandler directive.


Browser Jogging and Timeouts

If a CGI takes a while to run and stops sending data to the brower for a while, the browser may close it's connection to the server prematurely. On the other hand, if a CGI has a bug and gets hung up, it may just run and run forever, even after the user clicks stop in his browser.

Stencil provides a jogtimeout class that can send a whitespace character to the browser periodically and shut the CGI down if it gets hung.

jogtimeout.C:
// Copyright (c) 2004 David Muse
// See the COPYING file for more information

#include <stencil/inputoutput.h>
#include <stencil/request.h>
#include <stencil/response.h>
#include <stencil/jogtimeout.h>

#include <stencil/cgimodule.h>

MAIN {

        inputoutput     io(APISTRUCT);
        request         req(&io);
        response        resp(&io);
        jogtimeout      jt(&io);

        // jog the browser every 2 seconds, timeout after 20 seconds
        jt.initJogTimeout(2,20);

        // send an http header
        resp.textPlain();

        // write hello world
        io.output("hello world!\n");

        EXIT(0);
}

Environment Variables

When a web server runs a CGI, it sets several environment variables first. The request class provides methods for accessing and manipulating these variables.

envvars.C:
// Copyright (c) 2004 David Muse
// See the COPYING file for more information

#include <stencil/inputoutput.h>
#include <stencil/request.h>
#include <stencil/response.h>

#include <stencil/cgimodule.h>

MAIN {

        inputoutput     io(APISTRUCT);
        request         req(&io);
        response        resp(&io);

        // send an http header
        resp.textPlain();


        // environment variables...

        // display an individual environment variable
        io.output("DOCUMENT_ROOT=");
        io.output(req.environmentVariable("DOCUMENT_ROOT"));
        io.output("<br><br>\n");

        // another way to display an individual environment variable
        io.output("DOCUMENT_ROOT=");
        req.writeEnvironmentVariable("DOCUMENT_ROOT");
        io.output("<br><br>\n");

        // list all environment variables
        io.output("All Environment Variables:<br>\n");
        const char * const *vars=req.environmentVariables();
        const char * const *vals=req.environmentValues();
        for (int index=0; index<req.environmentVariableCount(); index++) {
                printf("        %s=%s<br>\n",vars[index],vals[index]);
        }
        io.output("<br>\n");

        // add an environment variable
        req.newEnvironmentVariable("NEW_VARIABLE","newvalue");
        io.output("New Environment Variable: ");
        req.writeEnvironmentVariable("NEW_VARIABLE");
        io.output("<br>\n");

        // change an environment variable
        req.changeEnvironmentVariable("NEW_VARIABLE","newervalue");
        io.output("Changed Environment Variable: ");
        req.writeEnvironmentVariable("NEW_VARIABLE");
        io.output("<br>\n");

        EXIT(0);
}

Form Entries

Some web pages have forms in them. When the form is submitted, variables in the form are passed to the cgi that the form links to. The request class provides methods for accessing and manipulating form entry variables.

formentries.C:
// Copyright (c) 2004 David Muse
// See the COPYING file for more information

#include <stencil/inputoutput.h>
#include <stencil/request.h>
#include <stencil/response.h>

#include <stencil/cgimodule.h>

MAIN {

        inputoutput     io(APISTRUCT);
        request         req(&io);
        response        resp(&io);

        // send an http header
        resp.textPlain();

        // form entries...

        // display an individual form entry
        io.output("variable1=");
        io.output(req.formEntry("variable1"));
        io.output("<br><br>\n");

        // another way to display an individual form entry
        io.output("variable1=");
        req.writeFormEntry("variable1");
        io.output("<br><br>\n");

        // list all form entries
        io.output("All Form Entries:<br>\n");
        const char * const *vars=req.formEntryVariables();
        const char * const *vals=req.formEntryValues();
        for (int index=0; index<req.formEntryCount(); index++) {
                printf("        %s=%s<br>\n",vars[index],vals[index]);
        }
        io.output("<br>\n");

        // display form entries as a get string
        stringbuffer    getstring;
        req.formEntriesAsGetString(&getstring,NULL);

        stringbuffer    getstringwithexceptions;
        char    *gsexceptions[]={"variable1",NULL};
        req.formEntriesAsGetString(&getstringwithexceptions,gsexceptions);

        io.output("All Form Entries As A Get String: ")->output(getstring.getString())->output("<br><br>\n");

        io.output("All Form Entries (except variable1) As A Get String: ")->output(getstringwithexceptions.getString())->output("<br><br>\n");



        // display form entries as hidden variables
        stringbuffer    hiddenvars;
        req.formEntriesAsHiddenVariables(&hiddenvars,NULL);

        stringbuffer    hiddenvarswithexceptions;
        char    *hvexceptions[]={"variable1",NULL};
        req.formEntriesAsHiddenVariables(&hiddenvarswithexceptions,hvexceptions);

        io.output("All Form Entries As Hidden Variables:<br>\n");
        io.output(hiddenvars.getString())->output("<br><br>\n");

        io.output("All Form Entries (except variable1) As Hidden Variables:<br>\n");
        io.output(hiddenvars.getString())->output("<br><br>\n");

        // add a form entry
        req.newFormEntry("newentry","newvalue");
        io.output("New Form Entry: ");
        req.writeFormEntry("newentry");
        io.output("<br>\n");

        // change a form entry
        req.changeFormEntry("newentry","newervalue");
        io.output("Changed Form Entry: ");
        req.writeFormEntry("newentry");
        io.output("<br>\n");

        EXIT(0);
}

Cookies

Some web pages set cookies. The request class provides methods for accessing and manipulating cookies.

cookies.C:
// Copyright (c) 2004 David Muse
// See the COPYING file for more information

#include <stencil/inputoutput.h>
#include <stencil/request.h>
#include <stencil/response.h>

#include <stencil/cgimodule.h>

MAIN {

        inputoutput     io(APISTRUCT);
        request         req(&io);
        response        resp(&io);

        // send an http header
        resp.textPlain();

        // cookies...

        // display an individual cookie
        io.output("cookie1=");
        io.output(req.cookie("cookie1"));
        io.output("<br><br>\n");

        // another way to display an individual cookie
        io.output("cookie1=");
        req.writeCookie("cookie1");
        io.output("<br><br>\n");

        // list all cookies
        io.output("All Cookies:<br>\n");
        const char * const *vars=req.cookieVariables();
        const char * const *vals=req.cookieValues();
        for (int index=0; index<req.cookieCount(); index++) {
                printf("        %s=%s<br>\n",vars[index],vals[index]);
        }
        io.output("<br>\n");

        // add a cookie
        req.newCookie("newentry","newvalue");
        io.output("New Cookie: ");
        req.writeCookie("newentry");
        io.output("<br>\n");

        // change a cookie
        req.changeCookie("newentry","newervalue");
        io.output("Changed Cookie: ");
        req.writeCookie("newentry");
        io.output("<br>\n");

        EXIT(0);
}

File Entries

Some web pages with forms have file entries. The browser can pass entire files to the cgi that the form links to. The request class provides methods for accessing and manipulating file entry variables.

fileentries.C:
// Copyright (c) 2004 David Muse
// See the COPYING file for more information

#include <stencil/inputoutput.h>
#include <stencil/request.h>
#include <stencil/response.h>

#include <stencil/cgimodule.h>

MAIN {

        inputoutput     io(APISTRUCT);
        request         req(&io);
        response        resp(&io);

        // send an http header
        resp.textPlain();


        // file entries...

        // display an individual file entry
        io.output("Specific File Entry:\n");
        io.output("file1=");
        io.output(req.fileEntryFilename("file1"));
        io.output("\n");
        io.output("temp file=");
        io.output(req.fileEntryTempFilename("file1"));
        io.output("\n");
        io.output("mime type=");
        io.output(req.fileEntryMimeType("file1"));
        io.output("\n");

        // list all file entries
        io.output("All File Entries:\n");
        for (int index=0; index<req.fileCount(); index++) {
                const char      *name=req.fileNames()[index];
                io.output("file1=");
                io.output(req.fileEntryFilename(name));
                io.output("\n");
                io.output("temp file=");
                io.output(req.fileEntryTempFilename(name));
                io.output("\n");
                io.output("mime type=");
                io.output(req.fileEntryMimeType(name));
                io.output("\n");
        }

        // add a file entry
        req.newFileEntry("newfile","filename","tempfilename","text/html");
        io.output("New File Entry:\n");
        io.output("newfile=");
        io.output(req.fileEntryFilename("newfile"));
        io.output("\n");
        io.output("temp file=");
        io.output(req.fileEntryTempFilename("newfile"));
        io.output("\n");
        io.output("mime type=");
        io.output(req.fileEntryMimeType("newfile"));
        io.output("\n");

        EXIT(0);
}

Responses

Most often, a cgi will send a text/html or text/plain HTTP header, followed by a page of HTML or text. However, a cgi might also set the value of a cookie, redirect the browser to another page, control whether a page is cached, etc. The response class provides methods for sending all kinds of different HTTP headers.

In this example, the cgi sets a cookie that will expire when the browser is closed.

sessioncookie.C:
// Copyright (c) 2004 David Muse
// See the COPYING file for more information

#include <stencil/response.h>

#include <stencil/cgimodule.h>

MAIN {

        inputoutput     io(APISTRUCT);
        response        resp(&io);

        resp.contentType("text","html",NULL);
        resp.setCookie("password","abcd1234",NULL,NULL,NULL,false);
        resp.cr();
        resp.cr();

        io.output("Cookie set!\n");

        EXIT(0);
}

In this example, the cgi sets a cookie that will expire on Sunday, March 1, 2020.

persistentcookie.C:
// Copyright (c) 2004 David Muse
// See the COPYING file for more information

#include <stencil/response.h>
#include <unistd.h>

#include <stencil/cgimodule.h>

MAIN {

        inputoutput     io(APISTRUCT);
        response        resp(&io);

        resp.contentType("text","html",NULL);
        resp.setCookie("password","abcd1234",NULL,NULL,
                        "Sun, 01-Mar-2020 00:00:00 GMT",false);
        resp.cr();
        resp.cr();

        io.output("Cookie set!\n");

        EXIT(0);
}

In this example, the cgi simply forwards the browser to http://www.mysite.com/errors/error.html

httpforward.C:
// Copyright (c) 2004 David Muse
// See the COPYING file for more information

#include <stencil/response.h>

#include <stencil/cgimodule.h>

MAIN {

        inputoutput     io(APISTRUCT);
        response        resp(&io);

        resp.location("html","www.mysite.com",NULL,"errors","error.html");
        resp.cr();
        resp.cr();

        EXIT(0);
}

In this example, caching is turned off so the browser won't cache the page.

httpnocache.C:
// Copyright (c) 2004 David Muse
// See the COPYING file for more information

#include <stencil/response.h>

#include <stencil/cgimodule.h>

MAIN {

        inputoutput     io(APISTRUCT);
        response        resp(&io);

        resp.contentType("text","html",NULL);
        resp.noCache(NULL);
        resp.cr();
        resp.cr();

        printf("This page won't be cached\n");

        EXIT(0);
}

Multipart Responses

Sometimes a cgi sends multiple responses to the browser. This is most commonly used to send an image that refreshes itself periodically. Most webcam software does this. The response class provides methods for sending multipart responses.

The following program sends a sequence of 10 images 5 times.

multipart.C:
// Copyright (c) 2004 David Muse
// See the COPYING file for more information

#include <stencil/response.h>
#include <unistd.h>

#include <stencil/cgimodule.h>

MAIN {

        inputoutput     io(APISTRUCT);
        response        resp(&io);

        // send an http header
        resp.multiPartContentType("x-mixed-replace",NULL,resp.boundaryString());

        // loop, sending the series of images 5 times
        for (uint16_t i=0; i<5; i++) {
                for (uint16_t j=0; j<10; j++) {

                        // send the a boundary string
                        resp.multiPartBoundary(NULL);

                        if (i>0) {
                                sleep(1);
                        }

                        resp.contentType("image","gif",NULL);
                        resp.cr();

                        // send the image
                        stringbuffer    imagename;
                        imagename.append("image")->append(j);
                        imagename.append(".gif");
                        file            imagefile;
                        imagefile.open(imagename.getString(),O_RDONLY);
                        io.output(&imagefile);
                        imagefile.close();
                }
        }

        // send the final boundary string
        resp.multiPartEnd(NULL);

        EXIT(0);
}

URL Parsing

When the request class is initialized, it parses the URL that invoked the program. The URL is split up into the following components:

Here is how the URL is parsed:

http://www.mysite.com/application/program.cgi/skin/module/page.html

The SCRIPT_NAME environment variable is parsed into application and program components. The PATH_INFO environment variable is parsed into skin, module and page components.

The different components of the URL are accessible using methods of the request class.

After parsing, the application and module components may contain several slashes. The program, skin and page components will contain no slashes.

For example, the following URL's break down as follows:

http://www.mysite.com/addressbook/addr.cgi/default/addressbook/login.html http://www.mysite.com/addressbook/delivery/addr.cgi/default/main/addressbook/login.html http://www.mysite.com/addressbook/delivery/content/addr.cgi/default/content/main/addressbook/login.html

And so on.

The request class provides methods for accessing each of the parts of the URL:

urlparts.C:

Separation of Application Code and Interface

Stencil allows you to define an applicaton's interface using a set of HTML pages and code the application in C++. It should be possible to develop an entire application without embedding any HTML in the code. Fundamentally this is accomplished by building a set of web pages and writing a program that delivers each page by parsing it and substituting in information as necessary.

When running an application, the program to run is specified in the SCRIPT_NAME portion of the URL and the page to deliver is specified in the PATH_INFO portion.

For example, in this URL:

http://www.mysite.com/addressbook/addr.cgi/default/addressbook/login.html

The program at $DOCUMENT_ROOT/addressbook/addr.cgi delivers the page at $DOCUMENT_ROOT/default/addressbook/login.html

A program must define methods for handling pages, segments of pages and variables defined in pages. The templateengine class allows you to associate methods with pages and segments of pages and allow you to associate values with variables.


Pages

The templateengine class allows you to attach a method to a module and page of HTML. Whenever the program is told to processes that module and page, the attached method is called to handle it.

The pagehandler struct allows you to associate modules and pages with methods to handle them.

The struct is defined as follows:

struct pagehandler {
        const char      *module;
        const char      *page;
        pagehandler_t   handler;
        void            *data;
};

The struct may be used to associate a set of modules and pages with methods to handle them as follows:

pagehandler     ph[]={
        {"addressbook","login.html",loginhtml,NULL},
        {"addressbook","addressbook.html",addressbookhtml,NULL},
        {NULL,NULL,NULL,NULL}
};

The templateengine::handlePage() method compares the module and page passed into it to the entries in the array of pagehandlers and calls the associated method.

In this example, if the PATH_INFO is /default/addressbook/login.html, then the loginhtml method is called. If the PATH_INFO is /default/addressbook/addressbook.html then the addressbookhtml method is called.

Here is an example, an addressbook application. This application serves 3 pages: a login page, a page with a list of addresses and an error page. Note that this example is incomplete. It illustrates how an program can process pages, but is not yet a functional addressbook.

addressbook.C:
// Copyright (c) 2004 David Muse
// See the COPYING file for more information

#include <stencil/templateengine.h>
#include <stencil/request.h>
#include <stencil/response.h>
#include <rudiments/charstring.h>
#include <rudiments/stringbuffer.h>


// handler for login.html
bool loginhtml(inputoutput *io, request *req, response *resp,
                        stringbuffer *container, const char *filename,
                        void *data) {

        // send an http header
        resp->textHtml();

        // parse page contents
        return templateengine::parseFile(io,req,resp,NULL,req->pagePath(),
                                                                NULL,NULL,NULL);
}


// handler for addressbook.html
bool addressbookhtml(inputoutput *io, request *req, response *resp,
                        stringbuffer *container, const char *filename,
                        void *data) {

        // validate login
        // normally you'd look this data up in a database, but for this
        // example, it's hardcoded into the program
        if (charstring::compare(req->formEntry("login"),"myuser") ||
                charstring::compare(req->formEntry("password"),"mypassword")) {
                return false;
        }

        // send an http header
        resp->textHtml();

        // parse page contents
        return templateengine::parseFile(io,req,resp,NULL,req->pagePath(),
                                                                NULL,NULL,NULL);
}


#include <stencil/cgimodule.h>

MAIN {

        inputoutput     io(APISTRUCT);
        request         req(&io);
        response        resp(&io);

        // associate pages with page handling functions
        pagehandler     ph[]={
                {"addressbook","login.html",loginhtml,NULL},
                {"addressbook","addressbook.html",addressbookhtml,NULL},
                {NULL,NULL,NULL,NULL}
        };

        // handle the page
        if (templateengine::handlePage(&io,&req,&resp,ph,req.module(),
                                                req.page(),req.pagePath())) {
                EXIT(0);
        }

        // if an error occurred, display the error page
        stringbuffer    filename;
        filename.append(req.skinPath())->append("/addressbook/error.html");
        resp.textHtml();
        templateengine::parseFile(&io,&req,&resp,NULL,
                                        filename.getString(),NULL,NULL,NULL);
        EXIT(0);
}

Here are the 3 HTML pages:

login.html:
<html>
<head>
        <title>Login</title>
</head>
<body>
        <table>
        <tr>
        <td>Login:</td>
        <td><input type="text" name="login"></td>
        </tr>
        <tr>
        <td>Password:</td>
        <td><input type="password" name="password"></td>
        </tr>
        </table>
</body>
</html>
addressbook.html:
<html>
<head>
        <title>Address Book</title>
</head>
<body>
        Address Book for $(login):<br><br>
        <table>
        <tr>
        <th>Name</th><th>Address</th><th>City</th>
        <th>State</th><th>Phone Number</th>
        </tr>
        <!-- start addressentry -->
        <tr>
        <td>$(name)</td><td>$(address)</td><td>$(city)</td>
        <td>$(state)</td><td>$(phonenumber)</td>
        <tr>
        <!-- end addressentry -->
        </table>
</body>
</html>
error.html:
<html>
<head>
        <title>Error</title>
</head>
<body>
        An error has occurred.
</body>
</html>

Segments

The templateengine class allows you to attach a method to a segment of HTML. When parsing the page, if the parser runs into the segment, it calls the attached method to parse it. Segments are defined by start/end HTML comments. For example, the comments in the following chunk of HMTL define the "addressentry" segment:

<table>
<tr>
<th>Name</th><th>Address</th><th>City</th>
<th>State</th><th>Phone Number</th>
</tr>
<!-- start addressentry -->
<tr>
<td>$(name)</td><td>$(address)</td><td>$(city)</td>
<td>$(state)</td><td>$(phonenumber)</td>
<tr>
<!-- end addressentry -->
</table>

The segmenthandler struct allows you to associate segments of HTML with methods to handle them.

The struct is defined as follows:

struct segmenthandler {
        stringbuffer            *container;
        const char              *name;
        segmenthandler_t        handler;
        void                    *data;
};

The struct may be used to associate segments with methods to handle them as follows:

segmenthandler  sh[]={
        {NULL,"addressentry",addressentry,NULL},
        {NULL,"loop",loop,NULL},
        {NULL,NULL,NULL,NULL}
};

When the templateengine::parseFile() and templateengine::parseSegment() methods encounter segments, they compare the names of the segments passed into them to the entries in the array of segmenthandlers and call the associated method.

In this example, if the "addressentry" segment is encountered, the addressentry method is called. If the "loop" segment is encountered, the loop method is called.

Here, the addressbook application is extended. When serving the list of addresses, it processes the "addressentry" segment of the page. Note that this example is still incomplete. It illustrates how an program can process pages and segments, but is not yet a functional addressbook.

addressbook.C:
// Copyright (c) 2004 David Muse
// See the COPYING file for more information

#include <stencil/templateengine.h>
#include <stencil/request.h>
#include <stencil/response.h>
#include <rudiments/charstring.h>
#include <rudiments/stringbuffer.h>


// handler for login.html
bool loginhtml(inputoutput *io, request *req, response *resp,
                        stringbuffer *container, const char *filename,
                        void *data) {

        // send an http header
        resp->textHtml();

        // parse page contents
        return templateengine::parseFile(io,req,resp,NULL,req->pagePath(),
                                                                NULL,NULL,NULL);
}


// handler for addressentry segment
bool addressentry(inputoutput *io, request *req, response *resp,
                        stringbuffer *container, const char *name,
                        const char *segment, uint64_t length,
                        void *data) {

        if (!templateengine::parseSegment(io,req,resp,container,
                                        segment,length,NULL,NULL,NULL)) {
                return false;
        }
        return true;
}


// handler for addressbook.html
bool addressbookhtml(inputoutput *io, request *req, response *resp,
                        stringbuffer *container, const char *filename,
                        void *data) {

        // validate login
        // normally you'd look this data up in a database, but for this
        // example, it's hardcoded into the program
        if (charstring::compare(req->formEntry("login"),"myuser") ||
                charstring::compare(req->formEntry("password"),"mypassword")) {
                return false;
        }

        // send an http header
        resp->textHtml();

        // handle the segments in the page
        segmenthandler  sh[]={
                {NULL,"addressentry",addressentry,NULL},
                {NULL,NULL,NULL,NULL}
        };

        // parse page contents
        return templateengine::parseFile(io,req,resp,NULL,req->pagePath(),
                                                                sh,NULL,NULL);
}


#include <stencil/cgimodule.h>

MAIN {

        inputoutput     io(APISTRUCT);
        request         req(&io);
        response        resp(&io);

        // associate pages with page handling functions
        pagehandler     ph[]={
                {"addressbook","login.html",loginhtml,NULL},
                {"addressbook","addressbook.html",addressbookhtml,NULL},
                {NULL,NULL,NULL,NULL}
        };

        // handle the page
        if (templateengine::handlePage(&io,&req,&resp,ph,req.module(),
                                                req.page(),req.pagePath())) {
                EXIT(0);
        }

        // if an error occurred, display the error page
        stringbuffer    filename;
        filename.append(req.skinPath())->append("/addressbook/error.html");
        resp.textHtml();
        templateengine::parseFile(&io,&req,&resp,NULL,
                                        filename.getString(),NULL,NULL,NULL);
        EXIT(0);
}

Substitutions

The templateengine class allows you to define sets of variable/value pairs and perform substitutions when parsing pages or segments. Variables are denoted by the $(variable) syntax.

There are 2 ways that sets of variable names and values may be defined. The first is using the variable struct. The variable struct allows you to associate variable names and values directly.

The struct is defined as follows:

struct variable {
        const char    *var;
        const char    *val;
};

The struct may be used to define a set of variables/values as follows:

variable variablearray[]={
        {"name","Dave"},
        {"address","1234 Dave's Street"},
        {"city","Cumming"},
        {"state","Georgia"},
        {"phonenumber","555-1234"},
        {NULL,NULL}
};

It may also be used to substitute in individual form entries or cookie values, as follows:

variable variablearray[]={
        {"sesionid",req->cookie("sessionid")},
        {"name",req->formEntry("name")},
        {"address",req->formEntry("address")},
        {"city",req->formEntry("city")},
        {"state",req->formEntry("state")},
        {"phonenumber",req->formEntry("phonenumber")},
        {NULL,NULL}
};

Another way that sets of variable names and values may be defined is to use the variables struct. The variables struct allows you to associate arrays of variable names with arrays of values. For example, you can use the variables struct when fetching data from a database, you may be able to get the column names in one array and each row of data in another array.

The struct is defined as follows:

struct variables {
        const char * const *var;
        const char * const *val;
};

The struct may be used to define sets of variables/values as follows:

const char * const *columnmames=getColumnNames();
const char * const *row=getRow();

variables variablesarray[]={
        {columnnames,row},
        {NULL,NULL}
};

The request class provides the following methods for defining sets of variable/value pairs from form entries, environment variables, cookies, components of the parsed URL, etc.

These methods may be used in conjunction with the variables struct as follows:

To substitute in only cookies and form entries:

variables variablesarray[]={
        {req->cookieVariables(),req->cookieValues()},
        {req->formEntryVariables(),req->formEntryValues()},
        {NULL,NULL}
};

To substitute in cookies, form entries and environment variables:

variables variablesarray[]={
	{req->cookieVariables(),req->cookieValues()},
	{req->formEntryVariables(),req->formEntryValues()},
	{req->environmentVariables(),req->environmentValues()},
        {NULL,NULL}
};

To substitute in all variables:

variables variablesarray[]={
	{req->allVariables(),req->allValues()},
        {NULL,NULL}
};

Here, the addressbook application is complete. It processes pages and segments and substitutes values into each.

addressbook.C:
// Copyright (c) 2004 David Muse
// See the COPYING file for more information

#include <stencil/templateengine.h>
#include <stencil/request.h>
#include <stencil/response.h>
#include <rudiments/charstring.h>
#include <rudiments/stringbuffer.h>


// handler for login.html
bool loginhtml(inputoutput *io, request *req, response *resp,
                        stringbuffer *container, const char *filename,
                        void *data) {

        // send an http header
        resp->textHtml();

        // parse page contents
        return templateengine::parseFile(io,req,resp,NULL,req->pagePath(),
                                                                NULL,NULL,NULL);
}


// handler for addressentry segment
bool addressentry(inputoutput *io, request *req, response *resp,
                        stringbuffer *container, const char *name,
                        const char *segment, uint64_t length,
                        void *data) {

        struct addressdata {
                const char      *name;
                const char      *address;
                const char      *city;
                const char      *state;
                const char      *phonenumber;
        };

        // build a list of names and address data
        // normally you'd look this data up in a database, but for this
        // example, it's hardcoded into the program
        addressdata     ad[]={
                {"David","1234 Dave's St.","Cumming","GA","555-1234"},
                {"Mark","2345 Mark's St.","Baton Rouge","LA","555-2345"},
                {"Nick","3456 Nick's Rd.","Some City","NJ","555-3456"},
                {"Mike","4567 Mike's St.","Houston","TX","555-4567"}
        };

        // for each address in the list, parse the segment,
        // substituting in the name and address data
        for (int i=0; i<4; i++) {

                variable        var[]={
                        {"name",ad[i].name},
                        {"address",ad[i].address},
                        {"city",ad[i].city},
                        {"state",ad[i].state},
                        {"phonenumber",ad[i].phonenumber},
                        {NULL,NULL}
                };

                if (!templateengine::parseSegment(io,req,resp,container,
                                                segment,length,NULL,NULL,var)) {
                        return false;
                }
        }
        return true;
}


// handler for addressbook.html
bool addressbookhtml(inputoutput *io, request *req, response *resp,
                        stringbuffer *container, const char *filename,
                        void *data) {

        // validate login
        // normally you'd look this data up in a database, but for this
        // example, it's hardcoded into the program
        if (charstring::compare(req->formEntry("login"),"myuser") ||
                charstring::compare(req->formEntry("password"),"mypassword")) {
                return false;
        }

        // send an http header
        resp->textHtml();

        // use built-in substitution variables (form entries, cookies, etc.)
        variables vars[]={
                {req->allVariables(),req->allValues()},
                {NULL,NULL}
        };

        // handle the segments in the page
        segmenthandler  sh[]={
                {NULL,"addressentry",addressentry,NULL},
                {NULL,NULL,NULL,NULL}
        };

        // parse page contents
        return templateengine::parseFile(io,req,resp,NULL,req->pagePath(),
                                                                sh,vars,NULL);
}


#include <stencil/cgimodule.h>

MAIN {

        inputoutput     io(APISTRUCT);
        request         req(&io);
        response        resp(&io);

        // associate pages with page handling functions
        pagehandler     ph[]={
                {"addressbook","login.html",loginhtml,NULL},
                {"addressbook","addressbook.html",addressbookhtml,NULL},
                {NULL,NULL,NULL,NULL}
        };

        // handle the page
        if (templateengine::handlePage(&io,&req,&resp,ph,req.module(),
                                                req.page(),req.pagePath())) {
                EXIT(0);
        }

        // if an error occurred, display the error page
        stringbuffer    filename;
        filename.append(req.skinPath())->append("/addressbook/error.html");
        resp.textHtml();
        templateengine::parseFile(&io,&req,&resp,NULL,
                                        filename.getString(),NULL,NULL,NULL);
        EXIT(0);
}

Skins

Applications written using Stencil are skinnable. An application's interface is defined by a set of HTML pages. Multiple sets of HTML pages may be built for a given application. The same program may be directed to deliver pages from one set or the other.

For example, lets say we have an addressbook application with 3 pages associated with it: a login page, a page that displays the names of each person in the address book and a page that displays an individual person's record.

Our application may need to have a default skin with lots of images on it and a low-bandwidth version with no images.

Our applicaton could consist of the following files:

$DOCUMENT_ROOT/addressbook/addr.cgi

$DOCUMENT_ROOT/addressbook/default/login/login.html
$DOCUMENT_ROOT/addressbook/default/addressbook/list.html
$DOCUMENT_ROOT/addressbook/default/addressbook/detail.html

$DOCUMENT_ROOT/addressbook/lowbandwidth/login/login.html
$DOCUMENT_ROOT/addressbook/lowbandwidth/addressbook/list.html
$DOCUMENT_ROOT/addressbook/lowbandwidth/addressbook/detail.html

Stencil derives the skin from first directory of the PATH_INFO.

To access the login page using the default skin, we would use the following url: http://www.mysite.com/addressbook/addr.cgi/default/login/login.html

To access the login page using the low bandwidth skin, we would use the following url: http://www.mysite.com/addressbook/addr.cgi/lowbandwidth/login/login.html

Stencil assumes that the PATH_INFO is relative to the application. In this example, it will look for the default and lowbandwidth directories under $DOCUMENT_ROOT/addressbook.

HTML pages under a given skin can link to other cgi's and pages using relative paths and the $(application), $(program), $(skin), $(module) and $(page) variables to prevent having to modify them if the application is restructured.

For instance, if the list page links back to the login page, it could use a URL like this:

http://$(SERVER_NAME)/$(application)/addr.cgi/$(skin)/login/login.html

In this case, if the server name is changed or addr.cgi and skin directory are moved into a new directory, everything will still "just work".


Skin Variables

Sometimes a particular set of values need to accompany a particular skin. For example, if skins are used for co-branding (ie. an app has a different skin for each customer's site that links to it), then you might look up values in a different database instance for each customer. The database instance, username and password need to be skin-specific.

When the request class parses the URL, it also looks in each subdirectory of the PATH_INFO for skin variable files. Skin variable files are XML files, containing simple name/value pairs.

skinvars.C:

Each skin variable file may define variables. Each more deeply nested skin variable file may define new variables or override previously defined ones. Each skin variable file must be named after the directory or file that it applies to and must end in the .var suffix.

For instance, our applicaton could consist of the following files:

$DOCUMENT_ROOT/addressbook/addr.cgi

$DOCUMENT_ROOT/addressbook/lowbandwidth.var
$DOCUMENT_ROOT/addressbook/lowbandwidth/login.var
$DOCUMENT_ROOT/addressbook/lowbandwidth/login/login.html
$DOCUMENT_ROOT/addressbook/lowbandwidth/login/login.html.var
$DOCUMENT_ROOT/addressbook/lowbandwidth/addressbook.var
$DOCUMENT_ROOT/addressbook/lowbandwidth/addressbook/list.html
$DOCUMENT_ROOT/addressbook/lowbandwidth/addressbook/list.html.var
$DOCUMENT_ROOT/addressbook/lowbandwidth/addressbook/detail.html
$DOCUMENT_ROOT/addressbook/lowbandwidth/addressbook/detail.html.var

The variables defined in lowbandwidth.var are visible to all pages under the lowbandwidth directory.

The variables defined in the lowbandwidth/login.var are visible to all pages under the lowbandwidth/login directory and may override variables defined in lowbandwidth.var.

The variables defined in the lowbandwidth/login/login.html.var are visible to lowbandwidth/login/login.html and may override variables defined in lowbandwidth/login.var and lowbandwidth.var.

Since skin variable files are stored in direcories that contain files which may be served to the public, if they contain any sensitive information, you'll probably want to configure the web server not to serve them. You can configure Apache not to serve them using the following directive:

<FilesMatch "\.var">
        Deny from all
</FilesMatch>

Security

There are several special skin variables that may be used to control whether access to a particular page is allowed or not. These variables are:

The denied-methods and allowed-methods variables may each contain a reqular expression defining form submission methods (GET, POST, etc.) that are denied or allowed when using a form to link to this page. When processing a page, a cgi may call request::methodAllowed() to determine whether the previous page used a valid form submission method or not.

The denied-ips and allowed-ips variables may each contain a reqular expression defining IP addresses that are denied or allowed access to the page. When processing a page, a cgi may call request::ipAllowed() to determine whether the browser is located at a valid ip or not.

The denied-referers and allowed-referers variables may each contain a reqular expression defining referring pages that are denied or allowed to link to the page. When processing a page, a cgi may call request::refererAllowed() to determine whether the previous page is allowed to link to this page or not.

In each case, the regular expression for denied cases is evaluated first, then the expression for allowed cases is evaluated. This is useful for defining a broad set of denied cases and then making a few exceptions.

For example, to deny the entire 192.168.2.0 subnet access to a page except for 192.168.2.10 and 192.168.2.11, you could define the following skin variables:

<skin>

<!-- database instance, username and password -->
<var name="denied-ips">^192\.168\.2</var>
<var name="allowed-ips">(^192\.168\.2\.10$|^192\.168\.2\.11$)</var>

</skin>

Load Balancing

If you have a cluster of web servers, you may want to distribute load among them. There are lots of hardware solutions for doing this, most of which make a cluster of web servers pretend to be a single server by intercepting and rewriting packets.

These systems are effective, but are often expensive or challenging to administer.

Stencil provides a "poor man's" solution. Much like the request class looks for skin variables, the loadbal class looks in each subdirectory of the PATH_INFO for load balancing files. Load balancing files are simple lists of hosts and metrics.

host1   5
host2   2
host3   1
host4   3

The metric is a representation of the power of the machine relative to the other machines in the list.

In this scenario, host1 is 5 times as powerful as host3, host2 is twice as powerful as host3 and host4 is 3 times as powerful as host3. The order of the hostnames in the file is irrelevant.

Each more deeply nested load balancing file overrides the previously defined list of hosts. Each load balancing file must be named after the directory or file that it applies to and must end in the .ldb suffix.

For instance, our applicaton could consist of the following files:

$DOCUMENT_ROOT/addressbook/addr.cgi

$DOCUMENT_ROOT/addressbook/lowbandwidth.ldb
$DOCUMENT_ROOT/addressbook/lowbandwidth/login.ldb
$DOCUMENT_ROOT/addressbook/lowbandwidth/login/login.html
$DOCUMENT_ROOT/addressbook/lowbandwidth/login/login.html.ldb
$DOCUMENT_ROOT/addressbook/lowbandwidth/addressbook.ldb
$DOCUMENT_ROOT/addressbook/lowbandwidth/addressbook/list.html
$DOCUMENT_ROOT/addressbook/lowbandwidth/addressbook/list.html.ldb
$DOCUMENT_ROOT/addressbook/lowbandwidth/addressbook/detail.html
$DOCUMENT_ROOT/addressbook/lowbandwidth/addressbook/detail.html.ldb

The list of hosts defined in lowbandwidth.ldb is visible to all pages under the lowbandwidth directory.

The list of hosts defined in the lowbandwidth/login.ldb is visible to all pages under the lowbandwidth/login directory and overrides the list of hosts defined in lowbandwidth.ldb.

The list of hosts defined in the lowbandwidth/login/login.html.ldb is visible to lowbandwidth/login/login.html and overrides the list of hosts defined in lowbandwidth/login.ldb and lowbandwidth.ldb.

The loadbal class provides a nextHost() method which returns the name of a weighted random selection of the hosts in the above loaded file, weighted by the metric in the file. This method may be used to get a hostname which then may be substituted into the URL's in a page. The links in that page will then refer to another host in the cluster. When the user clicks on a link, it will be served by that host.

Since .ldb files are stored in direcories that contain files which may be served to the public, you may want to configure the web server not to serve them. You can configure Apache not to serve them using the following directive:

<FilesMatch "\.ldb">
        Deny from all
</FilesMatch>