Thalassa CMS logo

Thalassa CMS

The dullcgi framework

Contents:

Overview

The dullcgi framework consists of some (but not all) modules from the thalcgi.cgi program, accompanied with the dedicated module named dullcgi.o, and is intended to ease creation of additional (custom) CGI programs for Thalassa-based web sites. Basically it enables the user to create a CGI program, which uses its own configuration file (in ini format) as well as the macroprocessor.

In the present version, dullcgi only allows to handle GET requests; also it doesn't provide access to the session information and user accounts. This will likely change in future versions.

Dullcgi is a framework, not a library in the classic sense. It provides its own main function, and expects the user to provide a function named dullcgi_main and two globally visible constants of the type const char *, named dullcgi_config_path and dullcgi_program_name.

Facilities provided by the framework are mainly accessed through an object of the DullCgi class; the object is created by the framework, and its address is passed to the user-supplied function as its only parameter.

The framework is built as a static library archive file (one with the ``.a'' suffix), in which all necessary object files are placed, not only those from Thalassa own codebase (the $thalassa/cms/ directory), but also all the necessary libraries from the $thalassa/lib/* subdirectories. The archive is named simply dullcgi.a, without the usual lib prefix, to make it clear it is not a library despite the file is in the static library format.

Getting started

It might be a good idea to take the $thalassa/examples/testcgi directory's content as a kind of starting point. Well, the user module in this example is perhaps the simplest thing possible, here is it:

#include <dullcgi.hpp>
int dullcgi_main(DullCgi *cgi)
{
    cgi->SendPage("default");
    return 0;
}
const char *dullcgi_config_path = "test.ini";
const char *dullcgi_program_name = "TEST";

As you might guess, the dullcgi_config_path constant's value sets the name for the configuration file the framework must read. The dullcgi_program_name constant is a more or less arbitrary name for your CGI program; it is only used as a part of the default error page, which is displayed only in case the framework couldn't read the configuration file.

The dullcgi_main function in this (perhaps the simplest possible) version causes the framework to unconditionally send to the client the HTML page configured by the [page default] section within the configuration file. Please note the function returns 0; the value it returns is then returned by the framework from its main function, so it becomes the exit code for the whole CGI program. You should only return a non-zero value in case everything went so wrong that you don't want even an error message to be sent back to the requesting client; well, hardly you'll ever need it.

A really simple configuration example is provided in the dumb.ini file (yes, it's not the default version), here is the full contents of the file:

[page default]

template = <?xml version="1.0" encoding="us-ascii"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+    <title>Simple test CGI program with dumb configuration</title>
+  </head>
+  <body>
+    <h1>It WORKS!</h1>
+  </body>
+</html>
+

The simplest version of the CGI program doesn't detect any errors on its own, only the errors detected by the framework are processed, so it is more or less acceptable to have no custom error page. The default page in this example is configured in the simplest possible way — its full text is given, with no calls to macros. If you decide to follow this way in your own configuration, please keep in mind that the percent char “%” is used by the macroprocessor to recognize macro calls, so in case you need the “%” char in your text, the char must be doubled.

To build your CGI program, first make sure the dullcgi.a file is ready for use; it builds by default if you just order your Thalassa sources to build with a simple ``make'' command. Then, a command like

   THAL=path/to/your/thalassa/directory \
      g++ -Wall -g -I${THAL}/cms -I${THAL}/lib mycgi.cpp \
      ${THAL}/cms/dullcgi.a -o mycgi.cgi

will do the thing.

You might want to authomate the things a bit, using a Makefile. A reasonable simplistic example of such file is provided in the same directory.

Configuration file

The first thing the dullcgi framework does after the CGI program start is reading and parsing the configuration file. In case this fails, the framework emits its default (hardcoded) error page and quits; the user-supplied function doesn't even get the control.

Normally, the configuration file, which has the ini format, should contain:

The very minimum here is a single [page NNN] section, as it is shown in the getting started section. Parameters recognized within each of the configuration sections are documented below.

Besides the configuration sections and parameters recognized by the dullcgi framework, the configuration file may contain any information specific for a particular CGI program. The DullCgi class provides a method to access such custom parameters.

The present version of dullcgi recognizes exactly one parameter, named template, for each of the page-defining sections ([page NNN], [errorpage] and [redirectpage]). The parameter's value is passed through the macroprocessor to make the full page text.

In the [errorpage] template parameter's value, three additional macros are recognized:

In the [redirectpage] template parameter's value, the framework recognizes one additional macro, named url, which expands to the target URL for the redirection being processed.

The [html] section of the configuration file, introduced to contain text snippets and simple templates, works exactly the same way as [html] sections for both static content generator and the thalcgi.cgi program (use the link for details).

Macros

The dullcgi framework makes all common macros available in the configuration file. Besides that, the following macros are made available by the framework and work exactly the same way as for the thalcgi.cgi program:

Some context-specific macros may also be in effect within certain configuration parameters.

Besides that, the user-supplied (that is, your) module can introduce additional macros using the DullCgi class methods.

Using the DullCgi class object

The function dullcgi_main, which your module must provide, is called by the framework with the address of DullCgi class object as its argument. All features of the framework are mostly accessed through this object.

Before we go on with the object's methods documentation, please note the classes provided by the Scriptpp library are heavily used by these methods. Be sure to take a look at the brief overview of the library (follow the link for it) before reading the DullCgi class interface documentation, or else you're at risk not to understand anything.

To be really short, let's remind that ScriptVariable is a class representing strings, ScriptVector represents a (resizeable) vector of strings. The Scriptpp library also provides implementation for the macroprocessor used by Thalassa CMS; the macroprocessor itself is represented by the ScriptMacroprocessor class (or its descendant), and every macro is implemented by a subclass of the (abstract) class ScriptMacroprocessorMacro.

It is also useful to keep in mind that ScriptVariable provides a converting constructor for const char*, so for all arguments of type const ScriptVariable& you can use simple string literals (in doublequotes).

Accessing the configuration file

It is highly likely you'll want your configuration file to hold some parameters specific to your very particular CGI program. From within the dullcgi_main function, as well as from any other (your) function you passed the DullCgi object pointer to, you can access the configuration file contents with the following method:

    ScriptVariable GetConfigValue(const ScriptVariable &sectgrp,
                                  const ScriptVariable &sect,
                                  const ScriptVariable &param,
                                  const ScriptVariable &specifier,
                                  bool expand_macros) const;

The first four parameters determine what information you'd like to access, while the last one (expand_macros) tells the framework whether should the information extracted from the config file get passed through the macroprocessor before it will be returned to you.

To understand the first four parameters, suppose you have something like

  [foo bar]
  manager:friday = John
  manager = Alice

in your configuration file. Then, to get that name John, you should use code like this:

  ScriptVariable manager_name =
      cgi->GetConfigValue("foo", "bar", "manager", "friday", true);

(use manager_name.c_str() after that to get the C string). Please note that the parameter value given with no specifier (Alice) serves as a kind of default, so it will be returned for any value of the specifier argument other than "friday". You can use 0 (which produces “invalid” ScriptVariable) if you just need the default value.

In case the section you're trying to access is not a part of a group, that is, its name consists of only one word, then the second argument for the method must be left zero, too. So, if you've got smth. like

  [abra]
  cadabra = blah-blah

then use

      cgi->GetConfigValue("abra", 0, "cadabra", 0, true);

to get that "blah-blah" value.

Sometimes these section groups can be used to define a kind of an array, or a list, or whatever else you actually don't know the number of elements nor their names in advance; actually, Thalassa CMS heavily uses this capability, e.g., to create ini-based lists. If you do something like that in your configuration file, you might want to be able to figure out which sections there are in a given section group. This is done by the

    int GetSectionNames(const ScriptVariable &group_id,
                        ScriptVector &names) const;

method. Suppose your configuration file contains the following:

  [item foo]
  enable = yes

  [item bar]
  enable = yes

  [item bazz]
  enable = yes

(it is critical to have at least one non-empty parameter in each of the sections). Then, you can act like this:

      ScriptVector names;
      int count;
      count = cgi->GetSectionNames("item", names);

After that, the count variable will contain the number 3, and the name object will hold three strings: "foo", "bar" and "bazz".

Controlling the macroprocessor

The framework creates the macroprocessor object on its own, filling it with the predefined macros. You can control the macroprocessor to some extent. To work with it, you will perhaps need to add

  #include <scriptpp/scrmacro.hpp>

to your module.

To add your own macro, specific to your CGI program, either derive a class from ScriptMacroprocessorMacro, or use one of the predefined classes, such as ScriptMacroConst or ScriptMacroScrVar. Make an object with operator new and pass the pointer to the framework using the method

    void AddMacro(ScriptMacroprocessorMacro *p);

Please note the ownership over the object is considered to be transferred here, that is, once you called this method, the macro object from now on belongs to the macroprocessor and will be deleted by its destructor. That's why it must be created with new and in no other way.

You can set the vector of positionals for the macroprocessor using metod

    void SetPositionals(const ScriptVector &v);

Sometimes it may be necessary to add some macros locally, perform some transformations, then forget the macros. This may be achieved by creating a temporary (local) clone of the macroprocessor. The DullCgi class provides the following two methods for that:

    ScriptMacroprocessor *MakeMacroprocessorClone() const;
    static void DisposeMacroprocessorClone(ScriptMacroprocessor *p);

Please keep in mind that objects of macros you pass to the local clone will belong to the clone and will be deleted once you dispose the clone object.

Sending the response to the client

Once you're done with all preparations and other things your CGI might need to complete, you must tell the framework what actually to send to the client. To do so you must call exactly one of the following methods:

    void SendPage(const ScriptVariable &pgid);
    void SendErrorPage(int code, const ScriptVariable &cmt);
    void SendRedirect(const ScriptVariable &url);

The SendPage method is used in case everything is Ok, so your program should just send a web page to the client. The page must be configured in the configuration file using a page section, like this:

  [page mypage]
  template = ...

To instruct the framework that this page is the one to be sent, simply use a statement like this:

    cgi->SendPage("mypage");

The text taken from the respective template parameter will be passed through the macroprocessor, and the result will be sent to the client with the “success” code (200).

In case something went wrong, you might want to call the SendErrorPage instead of SendPage, like this:

    cgi->SendErrorPage(404, "page not found");

or even

    cgi->SendErrorPage(418, "i'm a teapot");

(to have an idea what's the matter with teapots here, please read this Wikipedia article).

The page sent to the client will be generated using the [errorpage] section of the configuration file.

Please note that, although the framework really tries to set the error code in the Status field of the response, in most cases the HTTP server has its own idea what to send to the client in that field.

The last possibility is to request the client to redirect the user to some other place. It is done like this:

    cgi->SendRedirect("http://www.example.com/place/to/go");

Together with the redirect response, a “backup” page is usually sent, which informs the user what's going on and provides a link to click in case something went wrong with the browser's ablility to redirect authomatically. The dullcgi framework generates a page for this purpose using the [redirectpage] section.

© Andrey V. Stolyarov, 2023, 2024