mod_rexx

Rexx as a module for the Apache web server

Carsten Mjartan,
WiInf of the Uni/GH Essen, software technology



Contents

1. Exercise

2. The Apache module API

2.1 A minimal Apache module

2.2 Handler

2.3 The data structure module

2.4 The data structure request_rec

2.5 Configuration of a module

2.6 Resource administration

3. The Rexx SAA API

3.1 Basics

3.2 Rexx variables

3.3 Subcommand-Handlers

3.4 Handlers for external functions

3.5 The execution of Rexx code

3.6 The Rexx variable-interface

3.7 System Exit Handlers

3.8 Multithreading

4. mod_rexx

4.1 Program flow

4.2 Variable conversion

4.3 Redirecting the script output

4.4 Installation under linux

4.5 TODO

5. Reference





Exercise

Apache is a WWW-Server freely available in the source code that is worldwide market leader. For the letter of CGI skripts, there is the possibility to load program language dynamically by Apache and to run the scripts in the Apache thread. Therewith it is no longer necessary that for each single page request a (resources costly) process has to be produced. For scripting language Perl there is a freely available mod_perl implementation in source code on the web.

Tasks of this group enclose the working in into the mod-architecture of the open source WWW-Server Apache, the becoming acquainted with of existing implementations for different scripting languages and the design and implementation of a platform independent "mod-rexx".





Both the Apache Web-Server and the scripting language Rexx possess documented interfaces for the program language C. This enables it to interconnect both drafts in a "mod_rexx"-Apache module on simple manner.

I will go in now first of all on the reason drafts of the Apache API architecture.



The Apache module API

Since 1993 with CGI 1.0 a plattform independent interface to the development of server-side dynamic web pages exists. CGI is supported by almost each web server and offers the most flexibility in the choice of the platform and the related programming language to the developer. A disadvantage of this architecture is however the performance that becomes a problem in large Websites with many million accesses per day. In each proclamation of a dynamic page, a new process is started, is loaded that Interpreter into the storage, that loads and carries out again the Script. The expansion of the standard, FastCGI, where the script doesn't terminate but runs in a loop and waits for new requests, was not able to prevail.

Therefore most Webserver offer proprietary interfaces, for example ISAPI in the Internet information Server and other Windows based servers, NSAPI in the Netscape Server and the Apache API. Apache enables therefore the development of C-modules, which are statically linked to the Server or loaded dynamically (as a shared object file under Unix or as a Windows-DLLs). Within such a module, one can interfere at each request processing step of the web server and add new functionality and/or change existing behavior. Speaking against the use of the Server-APIs is on the other hand the higher learn expense and the larger mistake susceptibility, that leads in worst case to the complete crash of the web server.

In order to go around some of the disadvantages, Interpreters like Perl or PHP or (in the IIS) VBScript and JScript are integrated into the server runtime environment and do not need to be loaded for each script execution any more. Usually the interpreters provide a CGI-similar environment to help avoiding change expense to give the developer an accustomed environment. The mod_perl interpreter for Apache maps - if desired - the full functionality of the C-API to Perl and therefore enables the development of „Perl-Modulen“.



A minimal Apache module

I will go in on the most related mechanisms of the module programming, the subject is indeed very extensive. For further information, I recommend reading the Apache API documentation [1] as well as the (incomplete) Apache API reference [3].

First of all a little example: mod_hello.C

#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_protocol.h"
#include "http_log.h"


static int hello_handler(request_rec *r)
{
    if (r->method_number != M_GET) return DECLINED;

    /* Change the MIME-type of the response */
    r->content_type = "text/html";

    /* Send the response header */
    ap_send_http_header(r);
    if (r->header_only) return OK;

    /* Output the HTML code */
    ap_rputs("<html>"                        ,r);
    ap_rputs("<head>"                        ,r);
    ap_rputs("    <title>Hello World</title>",r);
    ap_rputs("</head>"                       ,r);
    ap_rputs("<body>"                        ,r);
    ap_rputs("Hallo Welt!"                   ,r);
    ap_rputs("</body>\n</html>"              ,r);

    /* successfully ended */
    return OK;
}


/* List of content handlers */

handler_rec hello_handlers[] = {
    { "hello-handler", hello_handler },
    { NULL }
};


module MODULE_VAR_EXPORT hello_module =
{
    STANDARD_MODULE_STUFF,
    NULL,                           /* module initializer */
    NULL,                           /* per-directory config creator */
    NULL,                           /* dir config merger */
    NULL,                           /* server config creator */
    NULL,                           /* server config merger */
    NULL,                           /* command table */
    hello_handlers,                 /* [9] list of handlers */
    NULL,                           /* [2] filename-to-URI translation */
    NULL,                           /* [5] check/validate user_id */
    NULL,                           /* [6] check user_id is valid *here* */
    NULL,                           /* [4] check access by host address */
    NULL,                           /* [7] MIME type checker/setter */
    NULL,                           /* [8] fixups */
    NULL,                           /* [10] logger */
#if MODULE_MAGIC_NUMBER >= 19970103
    NULL,                           /* [3] header parser */
#endif
#if MODULE_MAGIC_NUMBER >= 19970719
    NULL,                           /* process initializer */
#endif
#if MODULE_MAGIC_NUMBER >= 19970728
    NULL,                           /* process exit/cleanup */
#endif
#if MODULE_MAGIC_NUMBER >= 19970902
    NULL                            /* [1] post read_request handling */
#endif
};



To compile the module into Apache, the Apache source code is required for first: http://httpd.apache.org/dist/apache_1.3.14.tar.gz


Under Unix Apache is compiled with the following commands for mod_hello-support:

    tar xzf apache_1.3.14.tar.gz
    cd apache_1.3.14
    ./configure --prefix=/home/myuser/apache \
                --add-module=/path/to/mod_hello.c \
                [further parameters]
    make
    make install

In the configuration file /home/myuser/apache/conf/httpd.conf, the following lines must be added:

    <Location /hello/world>
        SetHandler hello-handler
    </Location>



Now /home/myuser/apache/bin/apachectl start starts the web server and you can already view the result in the web browser:






Handlers

The Apache web server subdivides the request processing into several functioning steps, that can be overloaded by module functions“.

The steps include...

In addition yet Handler exist for the initialisation and the shutdown of the current process.



A handler function receives the current context as a parameter in a request_rec- or a server_rec -structure. The processing ends by returning a predefined constant:

Most phases are terminated after the end of the first successful handlers. At the cleanup and in the access inspection, all handlers registered for a phase will be processed.

Several content handler can be registered in one module by inserting more than one handler into the handler_rec data structure. Normally the list is used to assign several names to one handler for the direct selection as well as for the registration of a MIME-type.

Code in mod_rexx. c:

    static const handler_rec rexx_handlers [] = {
        {"application/x-httpd-rexx",   rexx_handler},
        {"rexx-handler",               rexx_handler},
        {NULL}
    };


Configuration in httpd. conf:
by MIME-type:

    AddType application/x-httpd-rexx .rexx

or directly:

    AddHandler rexx-handler .rexx

or independent of the file ending:

    <Location /rexx>
        SetHandler rexx-handler
    </Location>





The data structure module

To register the handlers, they must be inserted into a structure of the type module. If for a phase no Handler is supposed to be registered, a NULL-Pointer is inserted.

The name of the structure (<name>_module) as well as the file name of the C-module source code (mod_<name>.c) is not freely eligible, otherwise Apache cannot use the module.

With newer Apache versions, additional phases for handler have been added at the end of the module structure. Therefore the handler-sequence in the structure does not correspond to the sequence of the handler execution.



The data structure request_rec

A handler gets a pointer on the request_rec structure as the only parameter, in which Apache stores all information accompanying the request. It contains among other things associative Arrays for the request- and the response-headers and the environment, the URI and the file path.

The handler can select and change contents of request_rec..
So one sets the response MIME type to text/html in this manner:

    r->content_type = „text/html“;


For accesses on Arrays (struct array) and associative arrays (struct table), corresponding API-functions are available.

Normally the request_rec is created by Apache by reading the header lines of the requests and inserting them into the appropriate fields.



Configuration of a module

In order to define the behavior of a module, the configuration commands defined by Apache are often not sufficient. For that reason you can create new commands. The administrator can configure the module using the same mechanisms as Apache offers itself in that he uses the commands defined by the module in httpd. conf or .htaccess-files.

You have to differentiate between server based and directory based configuration directives. Latter have to be used within Directory- or Location-tags. The attributes which have to be changed have to be stored in an data structure. The module contains a function that reserves the necessary storage and stores the default configuration in it.

The command list, including pointers to the accompanying functions and further attitudes are defined inside a command_rec-data structure.

If during the parsing of httpd. conf (in the initialisation phase) or .htaccess-file (at runtime) the command emerges, the function is called and the gets the default configuration of the directory as a void-parameter and the parameter(s) as (char *).

This is the code responsible for the configuration of mod_rexx. c:

...

/* Data structure to the storage of the module configuration */

typedef struct {
    int createEnvironment;
} rexx_dir_config;


/* Reservation of storage for the module configuration */
/* and setting of defaults                              */

static char *rexx_create_dir_config(pool *p, char *path)
{
    rexx_dir_config *cfg = 
                    (rexx_dir_config *) ap_palloc(p, sizeof(rexx_dir_config));
    cfg->createEnvironment = 1;
    return (void *) cfg;
}


/* Command function for RexxCreateEnvironment */

static const char *rexx_cmd_createEnvironment(cmd_parms *parms, void *mconfig,
                                              char *yesno)
{
    rexx_dir_config *cfg = (rexx_dir_config *) mconfig;

    /* check parameter validity and update configuration */
    if ( (!strcasecmp(yesno, "yes")) || (!strcasecmp(yesno, "on")) ) {
        cfg->createEnvironment = 1;
    } else if ( (!strcasecmp(yesno, "no")) || (!strcasecmp(yesno, "off")) ) {
        cfg->createEnvironment = 0;
    } else {
        return "parameter yes,no,on or off allowed";
    }
    return NULL;
}



static const command_rec rexx_commands[] =
{
      "RexxCreateEnvironment",               /* name of the command         */
      rexx_cmd_createEnvironment,            /* accompanying function       */
      NULL,                                  /* pointer to a data field to  */
                                             /* be delivered to the function*/
      ACCESS_CONF,                           /* valid areas within the      */
                                             /*               configuration */
      TAKE1,                                 /* type <=> numb. of arguments */
      "yes/no as parameter, default is yes"  /* command description         */
    },
    { NULL }
};

...

static int rexx_handler(request_rec *r)
{
    ...
    rexx_dir_config *config;                 /* per dir configuration    */
    ...
    
    config = (rexx_dir_config *) 
                       ap_get_module_config(r->per_dir_config, &rexx_module);

    if (config->createEnvironment) {
        ...
    }
    ...
}

...

/* module-structure */

module MODULE_VAR_EXPORT rexx_module =
{
    STANDARD_MODULE_STUFF,
    rexx_initialize,                /* module initializer */
    rexx_create_dir_config,         /* per-directory config creator */
    NULL,                           /* dir config merger */
    NULL,                           /* server config creator */
    NULL,                           /* server config merger */
    rexx_commands,                  /* command table */
    rexx_handlers,                  /* [9] list of handlers */
    NULL,                           /* [2] filename-to-URI translation */
    NULL,                           /* [5] check/validate user_id */
    NULL,                           /* [6] check user_id is valid *here* */
    NULL,                           /* [4] check access by host address */
    NULL,                           /* [7] MIME type checker/setter */
    NULL,                           /* [8] fixups */
    NULL,                           /* [10] logger */
#if MODULE_MAGIC_NUMBER >= 19970103
    NULL,                           /* [3] header parser */
#endif
#if MODULE_MAGIC_NUMBER >= 19970719
    NULL,                           /* process initializer */
#endif
#if MODULE_MAGIC_NUMBER >= 19970728
    NULL,                           /* process exit/cleanup */
#endif
#if MODULE_MAGIC_NUMBER >= 19970902
    NULL                            /* [1] post read_request handling */
#endif
};



The configuration takes place here only via directories. The functions for the directory oriented type and server oriented configuration differ scarcely.

During the requests, one can use the ap_get_module_config() function to get the configuration valid for the URL.

Normally in nested commands the more special have precedence. If this isn't desirable one implement a config merger function that combines the two different configuration data structures into une at request time.



Resource administration

In the last listing perhaps you noticed, that the API function ap_palloc() was used to allocate memory. In order to defuse that for C-programs and specially for system services large problem field memory leaks, the Apache-programmers introduced the draft of the resource pools.

A pool administers a list of reference on the storage reserved by ap_palloc() . The programmer does no longer release the storage explicitely. A pool has a defined lifetime after whose end the references are released all at once.

There are different pools: for example there exists a server pool for the lifetime of the web server, one during the configuration phase of the module, a request pool where memory is released after the last phase of request handling, etc.. The programmer must make himself hereon however only rarely thought, because for each situation the suitable pool is delivered either directly as a parameter or as an element of the request_rec- or server_rec-structure.

For the case, that a module should for a short time require much storage that should be released directly after use, the programmer has the possibility to generate a subpool using ap_make_sub_pool(). This has the same lifetime as the parent-pool and can be emptied or deleted previously by ap_release_pool() or ap_destroy_pool().

Apache deals with file handles the same way if a file is opened via ap_pfopen (). There also exists a ap_pfclose() function because the amount of the file handles is limited to most operating systems.



The Rexx SAA API:

For Rexx languages there exists a standardized API, to add Rexx support to (C-)programs. Rexx can be used both as a macro language and can also be expanded by C-functions.

The different Rexx implementations nevertheless differ in little detail from one another. I used the Rexx interpreter Regina [6] of Mark Hessling for the development of mod_rexx, because it is cost-free, platform independent and available in source code. On the Homepage you can also get a API wrapper that replaces rexxsaa.h, that hides the small interpreter differences to the programmer.

I also describe only the basics and some peculiarities, that are necessary for the understanding of mod_rexx, otherwise I actually had to copy the complete section of the rain-Rexx documentation to this subject.



Basics

The Rexx API subdivides itself into 6 different areas:



Rexx variables

All Rexx variables are stored internally as a string. For that a characteristic Datentyp RXSTRING exists:

    typedef struct {
        unsigned char strptr;           /* Pointer to the contents of the string */
        unsigned long strlength;        /* Length of the string in bytes         */
    } RXSTRING;

    typedef RXSTRING *PRXSTRING;

In contrast to C-Strings, the value of strlength determines the length of the string and not a terminating ASCII-0 char. Therewith also the ASCII-0 char can occur inside the string.



Subcommand handlers

In Rexx scripts, commands can be sent to external environments. By the ADDRESS-Statement, you can select the environment:

    ADDRESS SYSTEM 'copy' A B               /* The file  whose name in is found in      */
                                            /* variable A is copied into the file named */
                                            /* by variable B                            */

A handler function has following prototype:

    APIRET APIENTRY handler (
        PRXSTRING command,
        PUSHORT flags,
        PRXSTRING returnstring
    );

In flags the handler returns its status (RXSUBCOM_OK, RXSUBCOM_ERROR, RXSUBCOM_FAILURE). returnstring->strptr points on 256 byte of preallocated memory, into which the return values can be copied. The pointer however can also be redirected to a larger memory block.

By RexxRegisterSubcomExe("EnvName", &subcmd_handler, UserAreaPtr) you register the handler. If the handler has to be loaded from a DLL you use RexxRegisterSubcomDll().

RexxDeregisterSubcom("EnvName", "DllName") removes the Handler again, whereby the second parameter in not-Dll-Handlern ZERO its should (Attention: Because of a mistake in the current rains API here all may not stand, only ZERO).

The UserAreaPtr parameter can be used to transmit thread specific context data to the the handler. It points on a 8-byte large area that is stored by Rexx and which can be queried from within the handlers by RexxQuerySubcom().



Handlers for external functions

To increase the functionality Rexx there you can also install external function handlers. The handler receives the name of the called function, the function parameters as a parameter among other things and like a subcommand handler it gets a pointer to 256 bytes for the return string.

    APIRET APIENTRY handler {
        PSZ name,                         /* Name of the called function             */
        ULONG argc,                       /* the number of parameters                */
        PRXSTRING argv,                   /* string array of argc elements           */
        PSZ queuename,                    /* data queue name                         */
        PRXSTRING returnstring            /* ="0"  if returnstring->strptr = NULL    */
    );

Otherwise the functions are handled just as like the subcommand handlers (by RexxRegisterFunctionExe(), RexxRegisterFunctionDll(), RexxDeregisterFunction() and RexxQueryFunction()).



The execution of Rexx code

The RexxStart() funktion is used to execute Rexx code:

    APIRET APIENTRY RexxStart (
        LONG ArgCount,            /* Number of parameters                                */
        PRXSTRING ArgList,        /* List of ArgCount parameters                         */
        PSZ ProgramName,          /* File name of the script or name of a rexx macro     */
        PRXSTRING InStore,        /* script and Pseudocode, if script is in memory       */
        PSZ EnvName,              /* name of the default external environment            */
        LONG CallType,            /* Interpreter mode (command/function/Subroutine)      */   
        PRXSYSEXIT Exits,         /* List of exit handlers                               */
        PUSHORT ReturnCode,       /* Returncode, if numerically and -32768<=x<=32767     */
        PRXSTRING Result          /* Returnstring                                        */
    ); 

The most interesting parameters here are ProgramName and InStore. If Instore is a NULL pointer, ProgramName is the filename of the script that is to be executed.

Otherwise InStore points to a RXSTRING-Array with two elements. If both InStore[0].strptr and InStore[1].strptr are NULL, ProgName is a name of a macro, that was previously loaded by RexxAddMacro() or RexxLoadMacroSpace().

If InStore[1].strptr is not NULL, InStore [1] points to pretokenized code.

If InStore[0] != NULL,it contains a script which is transformed into tokens and is then stored in InStore[1] before execution.

It's your task to release the memory allocated for InStore [1]. You cannot store the pseudocode and reuse it because it is only valid for the lifetime of the current process.



The Rexx variable-interface

The API function RexxVariablePool() allows read and write access on all Rexx variables:

    APIRET APIENTRY ULONG RexxVariablePool (
        SHVBLOCK *Request;
    }

with
    typedef struct shvnode {
        struct shvnode *shvnext;      /* Pointer to next block      */
        RXSTRING shvname;             /* variable name              */
        RXSTRING shvvalue;            /* variable value             */
        ULONG shvnamelen;             /* max.  length of shvname    */
        ULONG shvvaluelien;           /* max.  length of shvvalue   */
        ULONG shvcode;                /* action                     */
        ULONG shvret;                 /* return value               */
    } SHVBLOCK;

    typedef SHVBLOCK *PSHVBLOCK;

The only parameter shows on a simple chain of action blocks. According to contents of shvcode, the fields of a block are have to be set by you before execution ore they are set by Rexx during the execution.

Rexx distinguishes between symbolic and direct variable. Symbolic names are transformed to real variables by normalization: the chars are transformed into upper case and the tail is substituted by its value.

Following actions are possible:



System exit handlers

System exit handlers allow the programmer to interfere into important areas of the Rexx processing and to change the behavior of the Rexx interpreter at these places:

Each handler function is based on the following prototype:

    LONG APIENTRY exit_handler (
        LONG ExitNumber,
        LONG Subfunction,
        PEXIT ParmBlock
    );

ParmBlock points to different data structures dependant on the values shows in ExitNumber and Subfunction. E.g. the exit handler RXSIOSAY contains a sing RXSTRING for the text to be written.



Multithreading

Since the version 2.0 Regina Rexx is thread save, i.e. several Rexx scripts can be executed simultaneously with RexxStart.

If you link Regina into a multithreading application, every application thread administers its own subcommand-, function- exit-handlers. Therefore each thread can handle Rexx (almost) as it would be the only thread.

You must not use global variables in order to deliver information to the handler, but rather you must use the UserAreaPtr-parameter at the handler registration and RexxQueryXXX from within the handler function.





mod_rexx

mod_rexx has now the task of bridging between the interfaces of Rexx and Apache.

It was my goal to provide an environment mostly equal to CGI to the script programmer. Unfortunately there are nevertheless some architectural deviations:

By editing the configuration file of Apache httpd.conf, the use of environment variables can be turned on for every directory. Per Default, they are disabled. They should be enabled with care because such scripts block themselves mutually in the execution.

A Rexx script begins output with the response headers and an empty line:

    SAY 'Content-type: text/plain'
    SAY ''

    SAY 'Hello world!'

It's very easy to access GET- or POST-variables from Rexx. If for example a text field with the name MyText is delivered by HTTP-GET, it's value is stored in APACHE!QUERY.MYTEXT, the corresponding HTTP-POST-variable would be in APACHE!FORM.MYTEXT. Multivalued varables are converted into a typical Rexx array:

The following HTML code

    <form action="form.rexx" method=post>
        <input type=checkbox name="xyz." value="1">1<br>
        <input type=checkbox name="xyz." value="2">1<br>
        <input type=checkbox name="xyz." value="3">1<br>
    </form>

is converted to the Rexx variables APACHE!FORM.XYZ.1 to APACHE!FORM.XYZ.n, where the number of elements (n) is stored in APACHE!FORM.XYZ.0.

There also exist the two variables APACHE!QUERY_STRING and APACHE.!POST_DATA for raw access, if the data is not http-urlencoded.

The environment variables are handled another way: the headers are discarded in an Array APACHE!ENVIRONMENT: the names under APCHE!ENVIRONMENT.I.NAME and the values under APACHE!ENVIRONMENT.I.VALUE with index I=1..APACHE!ENVIRONMENT.COUNT.



Program flow

Initialisation phase:

First of all at the start of Apache, the function rexx_initialize is called, which prepares the global variable rexx_mutex. The variable is needed for the coordination of the mutual exclusion of Rexx scripts that need the environment.

After that Apache parses the configuration files. If a new Rexx directory configuration becomes necessary, rexx_create_you_config reserves the necessary memory space and deactivates the use of environment variables per default. Each time Apache meets the command RexxCreateEnvironment, rexx_cmd_createEnvironment() is called and the Rexx configuration of the directory in which the command appeared is updated.

The treatment of a request:

The only registered handler for Rexx scripts is the content handler rexx_handler(). First, the routine checks whether a GET- or POST-request occurred, otherwise it interrupts returning DECLINED. If the script file doesn't exist, it also exits with NOT_FOUND.

All request-specific information is then copied into the data structure request_data Discarded, whose address is delivered in the registration with at the Rexx handler. So for example the Rexx I/O-Handler has indirectly access on that request-rec-Structure to forward around the edition at the Client. request_dates stores moreover the Request Body and an assioziatives Array with Rexx variable for the Rexx INI-Handler.

The output gets the default MIME type "text/html". If only the headers were questioned, these are issued and the processing ends.

With ap_soft_timeout() we tell Apache to continue processing of the handler - and of the script - in case of a client disconnect. All further output will then be ignored.

After that all Rexx variables are generated: first for the request headers, then - if available - the GET-parameters. In case of a POST request the body will be loaded and stored in the request_data-structure. If the request body has the MIME-type 'application/x-www-form-urlencoded' all variables will also be extracted.

The Rexx handlers for initialisation and output control are registered.

Before the script is executed with RexxStart(), the directory configuration is read to decide whether the environment variables are to be set. If so, ap_acquire_mutex() guarantees that no other module thread writes into the environment while the script is processed. After RexxStart() ap_release_mutex() releases that seal.

After that the Rexx handlers are deregistered. The registration and deregistration is necessary for each request, because information on the current request cannot be stored in global variables, but rather must be transmitted by RexxRegisterExitExe().

At last, rexx_handler() gives yet the storage the RexxStart()-Return result freely.



The variable conversion:

The preparation of the variables takes place in several steps:



Redirecting script output

The exit handler registered for for I/O, rxsio_exit_handler(),is responsible for the output of error messages and for normal text output via SAY. He also manages the line-by-line input of a possibly existing request body via PULL / PARSE PULL.

The handler queries for the request information that is necessary to determine the client connection.

If Rexx delivers the constant RXSIOTRC as the subfunction parameter, the handler submits an error message. If yet no headers were transmitted, this is made up. Then a couple of HTML tags are sent to ensure that the error message is visible (especially Netscape is very sensitive). The message is displayed in boldface.

The constant RXSIOSAY is delivered for normal text output. The function checks whether the headers were already sent. If yes, the string contents are simply submitted to the client and is terminated with a newline character.

Header lines are splittet into two parts separated by ':' and added to the request_rec->headers_out table. In case of an empty line, all headers are sent with ap_send_http_header().

The standard input redirection is problematic, because reading beyond the last line of the input leads to a syntax error. The typical way - using the function CHARIN() - is not functioning, because the API doesn't support redirection here.



Installation under Linux:

Installation of Regina Rexx version 2.0 or 2.2:

    tar xzf Regina-2.0.tar.gz
    cd Regina-2.0
    ./configure
    make
    make install                                            [must be run as administrator]

Installation of Apache 1.3.14 with mod_rexx support:

    tar xzf apache_1.3.14.tar.gz
    tar xzf mod_rexx-1.0.tar.gz
    cd apache_1.3.14
    ./configure --prefix=/usr/local/apache \
                --add-module=../mod_rexx-1.0/src/mod_rexx.c \
                ...
    make
    make install                                            [must be run as administrator]

Customization of the Apache configuration: the following line has to be added to /usr/local/apache/conf/httpd.conf:

    AddHandler rexx-handler .rexx



Reference:

[1] Apache Server documentation: http://httpd.apache.org/_URL

[2] Apache 1.3.14 source code: http://httpd.apache.org/dist/apache_1.3.14.tar.gz_URL

[3] Apache API prototype dictionary: http://dev.apache.org/apidoc/_URL

[4] Lincoln stone, Doug MacEachern, „Writing Apache module with Perl and C““, O' Reilly 1999

[5] Rains Rexx 2.2 documentation: http://www.lightlink.com/hessling/Regina/_URL

[6] Rains Rexx 2.0 / 2.2 source code: http://www.lightlink.com/hessling/Regina/_URL

[7] PHP 4.0.2 source code: http://www.php.net_URL

[8] mod_perl 1.24 source code: http://perl.apache.org_URL