Introduction to Web Application Development

Historically, web application programming began with the introduction of the CGI interface (Common Gateway Interface). It was invented in 1993 at the NCSA (National Center for Supercomputing Applications) and enabled web servers to execute external programs to dynamicaly generate responses to client requests by an HTTP server's CGI script.

Client request -> HTTPD -> CGI program generates content -> HTTPD respose -> Client

To address the shortcomings of CGI, Open Market developed FastCGI and first introduced it in their webserver product in the mid-1990s. So, we got application deamons instead of one time processes.

CGI     := HTTPD fork/execute/pipe mechanism to execute one-time CGI processes.
FCGI    := HTTPD launches an application daemon and communicates via the FastCGI interface.
SCGI    := CGI interface with the Maxdaemon HTTPD similar to FCGI.
DCGI    := The application is directly integrated into the HTTPD daemon.
ModPerl := Applications running with Apache's mod_perl module. 

In those days CGI.pm and ModPerl where very popular for application programming, because they implemented an API to the client side of the CGI interface. Today, numerous frameworks are out there, which supports application programming, as well as web site content and code managment.

Essentualy, CGI processes see client requests via environment variables and data sent via an HTTP server. They generate a response depending on, for example, the request headers, the path info, the node path, the node's file system and MIME types, the query string, as well as other data. In case the client is a browser, the response of a CGI script sent to the client via the HTTP server is a dynamicaly generated HTML document.

CGI programming is a bit tricky because the CGI protocol is stateless and the program data is sent back and forth between the client and the server. So, you need to retain the process state via CGI variables, a unique client identification on the server and maybe cookies on the client side.

A special location is addressed to by an URL (Uniform Resource Locator), which is a special case of a URI (Uniform Resource Identifier). FQDN is the full qualified domain name of a TCP/IP network host.

Uniform Resource Locator
────────────────────────
URL := <scheme>://<domain name>:<port>/<file path><path info>?<query string>

scheme │FQDN         │port│file path    │path info  │query string         │hash tag
───────+─────────────+────+─────────────+───────────+─────────────────────+───────
https://www.local.net:10000/user/www.scgi/Page.phtml?Category=/&Page=Index#Maxscape

A URL is a unique address that is used to identify a distinct resource on a network. The file path points to a file, link or directory. You may better name it a node path, but lets assume that a link or directory can also bee seen as a form of a file. A file system is a hierachicaly ordered tree of nodes that can be used to represent the information structure of a web site.

In case the file path is a CGI program, the HTTP server executes resp. communicates with it. The CGI query string, besides other data, can be used to pass parameters from the client via the HTTP server server to the CGI process.

When a web server receives a request, the URL and associateted data are passed to the CGI process which generates a response that is in return send back to the client. A Perl CGI 'Hello world!' CGI script is a one liner and may look like:

#!/usr/bin/env perl
print "Content-type: text/html\r\n\r\nHello world!";

The response generated by the CGI script consists of the response headers and the content, such as an HTML document. The requested URL and the request headers are accessible within the CGI script via environment variables. Depending on the request method (GET, POST, ..) data needs to be read from the CGI's input stream (STDIN), too.

So, a simple Perl CGI script may look like this. Note, how easy it is to embed variables in a string with Perl:

Simple CGI script:
──────────────────
#!/usr/bin/env perl

my $CRLF = "\015\012";
my $ClientHost     = $ENV{HTTP_HOST};
my $QueryString    = $ENV{QUERY_STRING};
my $ResponseHeader = "Content-type: text/html\r\n\r\n";

my $Text = "$ClientHost has sent a request: The query string is '$QueryString'.";

print $ResponseHeader . $Text;

The HTTPD response to 'https://maxdaemon.local.net/cgi-bin/CGI-Script.cgi?Hallo' is:
────────────────────────────────────────────────────────────────────────────────────
HTTP/1.1 200 OK
server: Maxdaemon/1.0.0
connection: close
content-type: text/html
date: Sat, 26 Oct 2024 13:49:07 GMT
content-length: 73

maxdaemon.local.net:443 has sent a request with the query string 'Hallo'.

The CGI query string is usually used to control the CGI's program flow. It starts with a question mark and consists of key-value pairs that are divided by an ampersand.

CGI query string example:
─────────────────────────
query string := ?Object=Node&Action=display&Language=en

URL with query string example:
──────────────────────────────
https://www.google.com/search?q=how+does+the+cgi+interface+work?"

You can look at the query string as an extension of the <file path> and <path info> part of the location of a special resource or service on the Internet. So, a simple CGI script using the Maxdaemon HTTPD may look like:

CGI script '/maxdaemon/cgi-bin/ObjectAction.cgi':
─────────────────────────────────────────────────
#!/usr/bin/env perl
###############################################################################
package main;
use strict;
sub BEGIN {
  unshift (@INC, "$ENV{MAXDAEMON_HOME}/lib"); eval ("use Maxdaemon::Globals;");
  $@ && (die "Fatal error in eval ("use Maxdaemon::Globals;"): " . $@ . "\n");
  Maxdaemon::Globals::checkProjectEnvironment(0);
}
###############################################################################
use Maxdaemon::CGI; # Import the Maxdaemon::CGI module.
###############################################################################

my $CGI = Maxdaemon::CGI->new ();

my $ReqestSheme     = $CGI->RequestScheme();
my $ScriptName      = $CGI->ScriptName();
my $ServerNamePort  = $CGI->ServerNamePort();

my $Object          = $CGI->Query ('Object');
my $Action          = $CGI->Query ('Action');

my $Text = '';
if (!$Object || !$Action) {
  $Text = "Enter an object and an action in the browser locator.";
} else {
  $Text =<< "EOT";
URL    = $ReqestSheme://$ServerNamePort$ScriptName?Object=$Object&Action=$Action.
Object = $Object.
Action = $Action.
EOT
}
my $ResponseHeaders = "Content-type: text/html\r\n\r\n";

print $ResponseHeaders . $Text;
1;
###############################################################################

If 'Object' and 'Action' are defined, a Browser displays:
─────────────────────────────────────────────────────────
URL    = http://maxdaemon.local.net:80/cgi-bin/ObjectAction.cgi?Object=Foo&Action=Bar.
Object = Foo.
Action = Bar.

Well, development, operation and maintainance of big web sites by storing the site's content on the server's file system is a task on its own. To overcome problems when only depicting a web site's information structure onto the HTTP server's file system, below the document root, Maxscape uses a rational database, named Content Database.

The entry point to retrieve data is an hierachical tree of nodes used to structure a web site's content, templates and code. Each node has a Category and a Page name and stores the node's data like node description, status, template, elements, a.s.o. You'll find a more compehensive discussion of the Content Database concept later.

To address a node of the content tree, the query string variables 'Category' and 'Page' are used.

'Category' and 'Page' addresses a location of the content node tree:
─────────────────────────────────────────────────────────────────────
https://maxscape.com?Category=/Profile/Applications&Page=Index

Content Database node tree:
───────────────────────────
── Root Category
   +─ Index
   ...
   +─ Profile
      +─ Index 
      +─ Applications
         +─ Index
         ...
      ...
   +─ Projects
      ...

Two variables are required to address a node of the node tree instead of one path, because then you can distinguish, before any database query, if the node is a category or a page. Unlike a directory, a node of type category may contain content or code elements that can be inherited to the categories's sub tree.

Perl is a wonderful language! You know, I wrote my first program in Pascal and entered it via punch cards into an IBM machine with a card reader that printed out a paper listing with the results of my program, that I had developed by hand on paper. Instead of trying to force me to write good code and imposing lots of things I definitly don't need for my special application, Perl's slogan "There's More Than One Way To Do It" relaxes programming a lot. And of course are there several alternatives to a task in the real world. Well, a web server is all about text processing, be it a message sent to a user or a piece of code executing a program.

A web site consists of pages and Perl's string interpolation possibilities and regular expressions are simply ingenious. The Perl bracketed expressions '@{[ ]}' even allow to easyly embed function calls within a text, what makes Perl a native interpreter for parsing (HTML) documents with embedded variables and function calls without any additional parser.

Function call embedded in a string using a Perl bracketed expression:
─────────────────────────────────────────────────────────────────────
#!/usr/bin/env perl

package Object;

sub new {
    my $c = shift;
    my $s = {};
    bless $s, $c;
    return $s;
}
sub Function {
    my $s = shift;
    return "@_ Thank's I am fine!";
}

package main;

my $ResponseHeaders = "Content-type: text/html\r\n\r\n";
my $Object = Object->new;

my $Text = "Embedded function call: @{[ $Object->Function ('How are you?') ]}\n";

print $ResponseHeaders . $Text;

After the string interpolation $Text contains:
──────────────────────────────────────────────
Embedded function call example: How are you? Thank's I am fine! 

By the way, the output of the print statements in the above examples is sent to the HTTPD via a pipe, in case of a CGI script or a TCP/IP resp. Unix domain socket in case of FastCGI or SCGI process daemons.

Obviously, working with the native CGI interface and explicitly reading files from a server's file system isn't very comfortable. So, numerous web server frameworks have been developed to ease application development. With Maxscape an SCGI script looks like:

SCGI script '/maxscape/server/WebServer/docs/www.scgi':
───────────────────────────────────────────────────────
...
use Maxdaemon::Daemon::HTTPD;
use Maxdaemon::Daemon::SCGI_Client;
use Maxdaemon::Daemon::Configuration;
use Maxdaemon::Process;

use Maxscape::Message;
use Maxscape::Message::Generic;
use Maxscape::HTTP::Request;
use Maxscape::HTTP::Response;
use Maxscape::Process;
use Maxscape::ServerConfiguration;
use Maxscape::ServerApplication;
use Maxscape::ProcessApplication;

my $RequestCounter = 0;

my $SCGI_Interface = Maxdaemon::Daemon::SCGI_Client->new;
$SCGI_Interface->cloneProcess ();

$Process = Maxscape::Process->new (ProcessType => 'SCGI');
$Process->initialise();

my ($Request; $Response, $Client);

while ($Client = $SCGI_Interface->accept)
{
  $Request  = Maxscape::HTTP::Request->new;
  $Response = $Process->execute ($Request, $Client);

  print $Response->print ($Client);

  $Process->postProcess;
  $Process->cleanup;
  $RequestCounter++;
}

Applications are executed within the $Process object, so the script is pretty generic and you might never need to touch it. Frameworks with an integrated HTTP server, like Mojolicious or Node.js provide a run-time main loop, but Maxscape, thought it has a simple internal HTTPD, don't, because it is used on the client side of the CGI interface and designed to work with an external HTTPD. With Maxdaemon, Maxscape can be integrated into the HTTP daemon and run via Maxdaemon's DCGI interface, if you like.

Here is a template main element with embedded Perl variables and object member function calls, using the run-time object $Page, which is explainted later. Note, that category, template and page elements are stored in the application server's content database and can arbirarily be intermixed. Applications can use the Maxscape library API, and don't need to fiddle with the database and SQL statement.

<--
Example of a template element, stored in the Content Database.
-->
<html>
  <head>
    @{[ $Page->ElementContent (Name => 'Category.Meta') ]}
    @{[ $Page->ElementContent (Name => 'Page.Meta') ]}

    <title>@{[ $Page->ElementContent (Name => 'Title') ]}</title>

    <style>
      @{[ $Page->ElementContent (Name => 'Category.CSS') ]}
      @{[ $Page->TemplateContent (Name => 'Template.CSS.Float', Type => 'parsed') ]}
      @{[ $Page->ElementContent (Name => 'Page.CSS.Float') ]}
      @{[ $Page->TemplateContent (Name => 'Template.CSS.PulldownMenu', Type => 'parsed') ]}
    </style>

    <script type="text/javascript">
      @{[ $Page->ElementContent (Name => 'Category.Javascript') ]}
      @{[ $Page->ElementContent (Name => 'Page.Javascript') ]}
    </script>
  </head>

  <body>
    <header>
      @{[ ($Page->ElementContent (Name => 'Category.Page.Header') ?
           $Page->ElementContent (Name => 'Page.Header')
           : $Page->TemplateContent (Name => 'Template.Page.Header'))
      ]}
    </header>

    <main>
      @{[ $Page->simpleElementLoop () ]}
    </main>

    <footer>
      @{[ $Page->ElementContent (Name => 'Page.Trailor') ]}
    </footer>
  </body>
</html>
Next ∙ Previous ∙ Top ∙ Join ∙ Contact © Maxscape