Homepage von Carsten Mjartan

mod_rexx

Rexx als Modul für den Apache Webserver

Carsten Mjartan,
WiInf der Uni/GH Essen, Bereich Softwaretechnik



Inhalt

1. Aufgabenstellung

2. Die Apache Modul-API

2.1 Das Grundgerüst eines Apache Moduls

2.2 Handler

2.3 Die module Datenstruktur

2.4 Die Datenstruktur request_rec

2.5 Konfiguration eines Moduls

2.6 Ressourcenverwaltung

3. Die Rexx SAA API

3.1 Grundsätzliches

3.2 Rexx-Variablen

3.3 Subcommand-Handler

3.4 Handler für externe Funktionen

3.5 Die Ausführung von Rexx Code

3.6 Die Rexx-Variablen-Schnittstelle

3.7 System Exit Handler

3.8 Multithreading

4. mod_rexx

4.1 Programmablauf

4.2 Die Variablenkonversion

4.3 Umleitung der Scriptausgabe

4.4 Installation unter Linux

4.5 TODO

5. Referenz





Aufgabenstellung:

Apache ist ein im Quellcode frei erhältlicher WWW-Server, der weltweiter Marktführer ist. Für das Schreiben von CGI-Skripte gibt es die Möglichkeit, Programmiersprachen dynamisch von Apache zu laden und in einem Thread von Apache die CGI-Skripte ausführen zu lassen. Damit ist es nicht mehr notwendig, daß für jede einzelne CGI-Anforderung am WWW-Server jeweils ein (ressourcenaufwendiger) eigener Prozeß erzeugt werden muß. Es gibt beispielsweise für die Skriptsprache Perl ("mod-perl") eine im Internet im Quellcode verfügbare Implementierung.

Aufgaben dieser Gruppe umfassen das Einarbeiten in die mod-Architektur des Opensource WWW-Servers Apache, das Kennenlernen bestehender Implementierungen für verschiedene Skriptsprachen und den Entwurf und die Implementierung eines plattformunabhängigen "mod-rexx".





Sowohl der Apache Web-Server als auch die Scriptsprache Rexx besitzen dokumentierte Schnittstellen für die Programmiersprache C. Dies ermöglicht es, auf einfache Weise beide Konzepte in einem "mod_rexx"-Apache-Modul miteinander zu verbinden

Ich werde nun zunächst auf die Grundkonzepte der Apache API Architektur eingehen.



Die Apache Modul-API

Seit 1993 existiert mit CGI 1.0 eine plattformübergreifende Schnittstelle zur Entwicklung serverseitiger dynamischer Webseiten. CGI wird von fast jedem Webserver unterstützt und bietet dem Entwickler größtmögliche Flexibilität bei der Wahl der Plattform und der verwendeten Programmiersprache. Ein Nachteil dieser Architektur ist jedoch die Performance, die gerade bei großen Websites mit vielen Millionen Zugriffen pro Tag zum Problem wird. Bei jedem Aufruf einer dynamische Seite wird ein neuer Prozess gestartet, der Interpreter wird in den Speicher geladen, der wiederum das Script lädt und ausführt. Die Erweiterung des Standards, FastCGI, bei dem das Script nicht terminiert, sondern in einer Schleife auf neue Seitenaufrufe wartet, konnte sich bisher nicht durchsetzen.

Daher stellen die meisten Webserver weitere „proprietäre“ Schnittstellen zur Verfügung, z.B. ISAPI beim Internet Information Server und anderen Windows-basierten Servern, NSAPI beim Netscape Server und die Apache API (ohne eigene Abkürzung). Apache ermöglicht damit die Entwicklung von C-Modulen, die sich statisch zum Server dazulinken oder dynamisch (als shared object files unter Unix oder als Windows-DLLs) hinzuladen lassen. Innerhalb eines solchen Moduls kann man sich in fast jeden Verarbeitungsschritt des Webservers einklinken und neue Funktionen hinzufügen bzw. bestehendes Verhalten ändern. Gegen den Gebrauch der Server-APIs sprechen dagegen der höhere Lernaufwand und die größere Fehleranfälligkeit, die schlimmstenfalls den gesamten Webserver zum Absturz bringt.

Um einige der Nachteile zu umgehen, werden Interpreter wie Perl oder PHP oder (beim IIS) VBScript und JScript über die Server-APIs in die Webserver integriert und müssen deshalb zur Ausführung von Scripten nicht mehr extra geladen werden. Meist stellen die Interpreter während der Ausführung eine möglichst CGI-ähnliche Umgebung zur Verfügung, um Änderungsaufwand zu Vermeiden und den Entwicklern eine gewohnte Umgebung zu gewährleisten. Der mod_perl Interpreter für Apache bildet darüberhinaus - falls gewünscht - die volle Funktionalität der C-API ab und ermöglicht so die Entwicklung von „Perl-Modulen“.



Das Grundgerüst eines Apache-Moduls

Ich werde auf die am meisten verwendeten Mechanismen der Modulprogrammierung eingehen, das Thema ist allerdings sehr umfangreich. Für weitere Informationen kann ich die Apache API-Dokumentationen [1] sowie die (allerdings nicht vollständige) API-Referenz [3] empfehlen.

Hier erstmal ein kleines Beispiel: 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;

    /* Änderung des MIME-Typs des Antwort-Dokuments */
    r->content_type = "text/html";

    /* Senden der Response-Header */
    ap_send_http_header(r);
    if (r->header_only) return OK;

    /* Ausgabe des HTML-Inhalts */
    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);

    /* Request erfolgreich bearbeitet */
    return OK;
}


/* Liste der Content-Handler */

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
};



Zur Einbindung des Moduls benötigen Sie zunächst einmal den Apache Quellcode: http://httpd.apache.org/dist/apache_1.3.14.tar.gz


Unter Unix kompilieren Sie Apache mit den folgenden Befehlen mit mod_hello-Unterstützung:

    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 \
                [weitere Parameter]
    make
    make install

In der Konfigurationsdatei /home/myuser/apache/conf/httpd.conf müssen dann noch folgende Zeilen hinzugefügt werden:

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



Jetzt noch mit /home/myuser/apache/bin/apachectl start den Webserver starten und schon kann man sich das Ergebnis im Web-Browser ansehen:






Handler

Der Apache Webserver unterteilt die Bearbeitung eines Requests in mehrere Arbeitsschritte, die jeweils durch Funktionen eines Moduls „überladen“ werden können.

Die Schritte umfassen...

  • die Umsetzung des URL in den Dateinamen

  • das Parsen von Request Headern

  • userunabhängige Zugriffskontrolle
    (basierend z.B. auf IP-Adresse oder Hostname des Clients)

  • die User-Identifikation über Username und Passwort
    (z.B. anhand einer Datenbankabfrage)

  • die Kontrolle der Zugriffsberechtigung des Users für die angeforderte URL

  • die Zuordnung eines Mime-Typen
    (standardmäßig per Datei-Endung)

  • „Last-Minute“-Aktionen vor der Antwortgenerierung

  • die eigentliche Erstellung des Antwortdokuments
    (innerhalb sogenannter Content Handler)

  • das Request-Logging

  • den Request-Cleanup
    (u.a. Freigabe des während des Requests allokierten Speichers)

Zusätzlich existieren noch Handler für die Initialisierung und das Beenden des aktuellen Prozesses.



Eine Handler-Funktion erhält als Parameter den aktuellen Kontext in einer request_rec- oder einer server_rec-Struktur. Die Verarbeitung endet in der Rückgabe einer vordefinierten Konstante:

  • OK - der Request wurde verarbeitet

  • DECLINED - der Handler ist nicht zuständig: Apache gibt die Verarbeitung an den nächsten für diesen Verarbeitungsschritt zuständigen Handler eines anderen Moduls oder an die Standardroutine weiter.

  • HTTP_* - ein Fehler ist aufgetreten, zurückgegeben wird der HTTP-Statuscode (die Liste der Konstanten ist in httpd.h zu finden).

Die meisten Phasen werden nach dem Ende des ersten erfolgreichen Handlers abgeschlossen. Beim Cleanup und bei der Zugriffskontrolle werden alle für eine Phase registrierten Handler nacheinander abgearbeitet.

Es können mehrere Content-Handler eines Moduls registriert werden, indem mehr als ein Handler in die handler_rec-Liste eingetragen wird. In der Regel wird die Liste eher dazu verwendet, einem Handler mehrere Namen zu geben für die direkte Auswahl sowie für die Registrierung für einen MIME-Typ.

Code in mod_rexx.c:

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


Konfiguration in httpd.conf:
über MIME-Typ:

    AddType application/x-httpd-rexx .rexx

oder direkt:

    AddHandler rexx-handler .rexx

oder unabhängig von der Dateiendung:

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





Die module-Datenstruktur

Zur Registrierung der Handler müssen diese in eine Struktur vom Typ module eingetragen werden. Wenn für eine Phase kein Handler registriert werden soll, wird entsprechend ein NULL-Pointer eingesetzt.

Der Name der Struktur (<name>_module)ist wie der Dateiname des C-Modulquellcodes (mod_<name>.c) nicht frei wählbar, sonst kann Apache mit dem Modul nichts anfangen.

Mit neueren Apache-Versionen sind zusätzliche Phasen für Handler hinzugekommen, die an das Ende der module-Struktur angehängt wurden. Daher entspricht die Handler-Reihenfolge in der Struktur nicht der Reihenfolge der Ausführung.



Die Datenstruktur request_rec

Ein Handler bekommt als einzigen Parameter einen Pointer auf die request_rec-Struktur, in der Apache alle dem Request zugehörigen Informationen speichert. Sie enthält u.a. assioziative Arrays für die Request- und die Response-Header und das Environment, die URI und den Dateipfad.

Der Handler kann deren Inhalt auslesen und ändern.
So setzt man z.B. den MIME-Typ für die Ausgabe:

    r->content_type = „text/html“;


Für Zugriffe auf Arrays (struct array) und assoziative Arrays (struct table) stehen entsprechende API-Funktionen zur Verfügung.

Normalerweise entsteht ein request_rec, indem die Headerzeilen des Requests ausgelesen und in die ensprechenden Felder eingefügt werden



Konfiguration eines Moduls

Um das Verhalten eines Moduls zu steuern, sind die von Apache definierten Konfigurationsbefehle oft nicht ausreichend. Daher kann man den Befehlsumfang von Apache erweitern. Der Administrator kann das Modul mit den gleichen Mechanismen wie Apache selbst konfigurieren, indem er die vom Modul definierten Befehle in httpd.conf oder .htaccess-Dateien verwendet.

Dabei unterscheidet man serverbezogene und verzeichnisbezogene Einstellungen. Letztere werden innerhalb von Directory oder Location-Tags verwendet. Die Einstellungen müssen Sie in einer selbst definierten Datenstruktur speichern. Das Modul muss eine Funktion zur Verfügung stellen, die den notwendigen Speicher reserviert und die Felder mit Defaultwerten belegt.

Die Liste der Befehle inkl. Zeigern auf die zugehörigen Funktionen und weiteren Einstellungen wird über eine command_rec-Datenstruktur definiert.

Wenn während des Parsens von httpd.conf (in der Initialisierungsphase) oder einer .htaccess-Datei (zur Laufzeit) der Befehl auftaucht, wird die Funktion aufgerufen und die für das Verzeichnis/den Server gültige Defaultkonfiguration als void bzw. die Befehlsparameter als Zeichenketten übergeben.

Dies ist der für die Konfiguration zuständige Code aus mod_rexx.c:

...

/* Datenstruktur zur Speicherung der Modulkonfiguration */

typedef struct {
    int createEnvironment;
} rexx_dir_config;


/* Reservierung von Speicher für die Modulkonfiguration */
/* und Setzen von 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;
}


/* Befehlsfunktion fuer RexxCreateEnvironment */

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

    /* Parameter überprüfen und in Konfigurationsfeld eintragen */
    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 des Kommandos          */
      rexx_cmd_createEnvironment,            /* zugehörige Funktion         */
      NULL,                                  /* Zeiger auf ein der Funktion */
                                             /*   zu übergebendes Datenfeld */
      ACCESS_CONF,                           /* gültige Bereiche innerhalb  */
                                             /*           der Konfiguration */
      TAKE1,                                 /* TYP <=> Anz. d. Argumente   */
      "yes/no as parameter, default is yes"  /* Befehlsbeschreibung         */
    },
    { 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
};



Die Konfiguration erfolgt hier nur über Verzeichnisse. Die Funktionen für die verzeichnisorientierte und serverorientierte Konfiguration unterscheiden sich kaum.

Während des Requests erhält man über ap_get_module_config die für die URL gültige Konfiguration.

Normalerweise haben bei verschachtelten Einstellungen die Spezielleren Vorrang. Falls dies nicht erwünscht ist kann man eine Verknüpfungsfunktion (config merger) erstellen, die zum Zeitpunkt des Requests die allgemeine und die spezielle Konfigurationsdatenstruktur kombiniert.



Ressourcenverwaltung

Im letzten Listing ist Ihnen vielleicht aufgefallen, das zur Speicherallokation eine API-Funktion ap_palloc() verwendet wurde. Um das für C-Programme und speziell für Systemdienste große Problemfeld Memory-Leaks zu entschärfen, haben die Apache-Programmierer das Konzept der Speicherpools eingeführt.

Ein Speicherpool verwaltet eine Liste mit Referenzen auf den über ap_palloc() reservierten Speicher. I.d.R. gibt der Programmierer den Speicher nicht mehr selbst explizit frei. Ein Pool hat eine definierte Laufzeit, nach deren Ende alle Referenzen auf einmal freigegeben werden.

Es gibt verschiedene Pools die sich eben durch diese Laufzeit voneinander unterscheiden, z.B. existiert ein Serverpool für die Laufzeit des Servers, ein Request-Pool für die Dauer eines Requests, einer während der Konfigurationsphase des Moduls usw. Der Programmierer muss sich hierüber aber nur selten Gedanken machen, da er für jede Situation der passenden Pool entweder direkt als Parameter oder als Element der request_rec- oder server_rec-Struktur geliefert bekommt.

Für den Fall, das ein Modul kurzfristig viel Speicher benötigt, der auch wieder freigegeben werden sollte, hat der Programmierer die Möglichkeit, einen Subpool zu erstellen. Dieser hat dieselbe Laufzeit wie der Parent-Pool und kann nach Wunsch vorher geleert oder gelöscht werden.

Auf dieselbe Art kann mit Dateihandles gearbeitet werden, die über die ap_pfopen() geöffnet wurden. Hier existiert allerdings eine ap_pfclose()-Funktion, da die Anzahl der Dateihandles auf den meisten Systemen begrenzt ist.



Die Rexx SAA API:

Für Rexx-Sprachen existiert eine standardisierte API, zur Unterstützung von Rexx in eigenen (C-)Programmen. Rexx kann sowohl als Makrosprache verwendet werden als auch über eigene C-Funktionen erweitert werden.

Die verschiedenen Rexx-Implementierungen unterscheiden sich in kleinen Details dennoch voneinander. Ich habe für die Entwicklung von mod_rexx den kostenlosen, plattformübergreifend und im Quellcode verfügbaren Regina Rexx Interpreter [6] von Mark Hessling verwendet. Auf der Homepage findet sich ausserdem ein Wrapper, der statt rexxsaa.h in die einenen Programme eingebunden wird und Abweichungen der Schnittstelle anderer Interpreter kapselt.

Ich beschreibe auch hier nur die Grundlagen und einige Besonderheiten, die für das Verständnis von mod_rexx nötig sind, sonst müsste ich eigentlich den kompletten Abschnitt der Regina-Rexx Dokumentation zu diesem Thema abschreiben.



Grundsätzliches

Die Rexx-API unterteilt sich in 6 unterschiedliche Bereiche:

  • Subcommand-Handler behandeln Rexx-Kommandos zur Ausführung ausserhalb der Rexx-Umgebung, z.B. Shell Kommandos

  • Handler für externe Funktionen zur Erweiterung des Funktionsumfangs von Rexx

  • Die RexxStart-Funktion zur Ausführung von Rexx-Scripts aus dem Speicher oder aus einer Datei heraus.

  • Über die Variablen-Schnittstelle können Rexx-Variablen gelesen, geändert und gelöscht werden.

  • „System exits“ sind Hotspots, über die bestimmte Schlüsselfunktionen des Interpreters während der Ausführung variiert werden können.



Rexx-Variablen

Alle in Rexx verwendeten Variablen werden intern als Zeichenketten gespeichert. Dafür existiert ein eigener Datentyp RXSTRING:

    typedef struct {
        unsigned char strptr;           /* Zeiger auf den Inhalt der Zeichenkette */
        unsigned long strlength;        /* Länge der Zeichenkette in Bytes        */
    } RXSTRING;

    typedef RXSTRING *PRXSTRING;

Im Gegensatz zu C-Strings entscheidet der Wert von strlength über die Länge der Zeichenkette und nicht ein abschliessendes ASCII-0 Zeichen. Damit kann auch das ASCII-0-Zeichen in der Zeichenkette vorkommen.



Subcommand-Handler

Aus Rexx-Scripts können Kommandos an externe Umgebungen geschickt werden. Über das ADDRESS-Statement kann man die Umgebung auswählen:

    ADDRESS SYSTEM 'copy' A B               /* Kopiere die Datei, deren Name in  */
                                            /* Variable A steht in die Datei aus */
                                            /* Variable B                        */

Eine Handlerfunktion zur Verarbeitung solcher Kommandos hat folgenden Prototyp:

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

In flags gibt der Handler seinen Status zurück (RXSUBCOM_OK, RXSUBCOM_ERROR, RXSUBCOM_FAILURE). returnstring->strptr zeigt auf 256 Byte allokierten Speicher, in die der Rückgabewert kopiert werden kann. Der Zeiger kann aber auch falls nötig auf einen größeren Speicherbereich „umgebogen“ werden.

Über RexxRegisterSubcomExe("EnvName", &subcmd_handler, UserAreaPtr) registriert man den Handler, bzw. über RexxRegisterSubcomDll(), falls der Handler in einer DLL zu finden ist.

RexxDeregisterSubcom("EnvName", "DllName") entfernt den Handler wieder, wobei der zweite Parameter bei nicht-Dll-Handlern NULL sein sollte (Achtung: Wegen eines Fehlers in der aktuellen Regina API darf hier alles, nur nicht NULL stehen).

Über den UserAreaPtr-Parameter kann der Handler mit Informationen versorgt werden. Er zeigt auf einen 8-Byte großen Bereich, der von Rexx gespeichert wird und auf die innerhalb des Handlers über RexxQuerySubcom() zugegriffen werden kann.



Handler für externe Funktionen

Zur Erweiterung des Funktionsumfangs von Rexx können ebenfalls Handler installiert werden. Der Handler erhält als Parameter u.a. den Namen der aufgerufenen Funktion, die Funktionsparameter und wie beim Subcommand-Handler den Returnstring als einen Zeiger auf 256 Bytes reservierten Speicher.

    APIRET APIENTRY handler {
        PSZ name,                         /* Name der aufgerufenen Funktion        */
        ULONG argc,                       /* Die Anzahl der Parameter              */
        PRXSTRING argv,                   /* Stringarray mit argc Elementen        */
        PSZ queuename,                    /* data queue name                       */
        PRXSTRING returnstring            /* ="0" wenn returnstring.strptr=NULL    */
    );

Ansonsten werden die Handler genauso behandelt wie die Subcommand-Handler (über RexxRegisterFunctionExe(), RexxRegisterFunctionDll(), RexxDeregisterFunction() und RexxQueryFunction()).



Die Ausführung von Rexx Code

Die RexxStart()-Funktion ermöglicht den Start des Rexx-Interpreters zur Ausführung von Rexx Code:

    APIRET APIENTRY RexxStart (
        LONG ArgCount,            /* Anzahl der Parameter                          */
        PRXSTRING ArgList,        /* Liste von ArgCount Parametern                 */
        PSZ ProgramName,          /* Dateiname des Scripts oder Name eines Makros  */
        PRXSTRING InStore,        /* Speichert Script und Pseudocode, falls Script */
        PSZ EnvName,              /* im Speicher                                   */
        LONG CallType,            /* Interpreter-Modus(Command/Function/Subroutine)*/
        PRXSYSEXIT Exits,         /* Liste der Exit-Handler                        */
        PUSHORT ReturnCode,       /* Returnwert, falls numerisch, -32768<=x<=32767 */
        PRXSTRING Result          /* Returnstring                                  */
    ); 

Am interessantesten sind hier die Parameter ProgramName und InStore. Falls Instore ein NULL-Pointer ist, zeigt ProgramName auf den Dateinamen des Scripts, das ausgeführt werden soll.

Sonst zeigt InStore auf ein 2-elementiges RXSTRING-Array. Wenn beide InStore[0].strptr und InStore[1].strptr gleich NULL sind, wird ProgName als Name eines vorher geladenen Makros interpretiert.

Ist InStore[1].strptr hingegen nicht NULL, so zeigt InStore[1] auf ein bereits in Zwischencode umgewandeltes Script.

Wenn InStore[0] != NULL ist, wird das dort gespeicherte Script in Zwischencode umgewandelt und ausgeführt. Der Zeiger InStore[1] zeigt nach Ausführung von RexxStart auf diesen Zwischencode.

Der Speicher von InStore[1] muss selbst freigegeben werden. Bei Regina können Sie den Zwischencode nicht speichern und später wiederverwenden, da er nur für die Dauer des Prozesses Gültigkeit besitzt.



Die Rexx-Variablen-Schnittstelle

Rexx ermöglicht über die Funktion RexxVariablePool() den lesenden und schreibenden Zugriff auf alle Rexx-Variablen:

    APIRET APIENTRY ULONG RexxVariablePool(
        SHVBLOCK *Request;
    }

mit
    typedef struct shvnode {
        struct shvnode *shvnext;      /* Zeiger auf nächsten Block */
        RXSTRING shvname;             /* Variablenname             */
        RXSTRING shvvalue;            /* Variablenwert             */
        ULONG shvnamelen;             /* max. Länge von shvname    */
        ULONG shvvaluelien;           /* max. Länge von shvvalue   */
        ULONG shvcode;                /* Aktion                    */
        ULONG shvret;                 /* Rückgabewert              */
    } SHVBLOCK;

    typedef SHVBLOCK *PSHVBLOCK;

Der einzige Parameter zeigt auf eine einfach verkettete Liste von Aktionsblöcken. Je nach Inhalt von shvcode werden die Felder eines Blocks vor Ausführung von Ihnen oder während der Ausführung von Rexx belegt.

Rexx unterscheidet zwischen symbolischen und direkten Variablen. Symbolische Namen werden von Rexx durch Normalisierung in reelle Variablen umgewandelt: Die Zeichen werden in Großbuchstaben umgewandelt und das Ende (nach dem Variablenstamm) wird durch ihren Wert substituiert.

Folgende Aktionen sind möglich:

  • RXSHV_DROPV: Die Variable, deren reeller Name in shvname gespeichert ist, wird gelöscht, d.h. sie ist danach undefiniert.

  • RXSHV_EXIT: Hierüber kann ein Exit-Handler seinen Rückgabewert setzen.

  • RXSHV_FETCH: Der Wert der Variable, deren Name in shvname steht, wird in shvvalue gespeichert. Wenn shvvalue->strptr NULL ist, so wird genügend speicher allokiert, sonst wird der vorhandene Speicher bis zu shvvaluelen Bytes mit dem Wert gefüllt.

  • RXSHV_NEXTV: Durch mehrmalige Ausführung dieser Aktion können alle sichtbaren (= nicht verdeckten) Variablen der aktuellen Prozedur gelesen werden. Wenn keine Variablen mehr übrig sind, bekommt shvret den Wert RXSHV_LVAR. Andere Aktionen setzen die Liste wieder auf den Ausgangszustand zurück.

  • RXSHV_PRIV: Hiermit können spezielle Variablen wie z.B. die Kommandozeilenparameter oder die Interpreter-Version ausgelesen werden.

  • RXSHV_SET: Die reelle Variable in shvname wird auf den Wert in shvvalue gesetzt.

  • RXSHV_SYFET: Wie RXSHV_FETCH, nur mit dynamischer Variable in shvname

  • RXSHV_SYDRO: Wie RXSHV_DROPV, mit dynamische Variable in shvname

  • RXSHV_SYSET: Wie RXSHV_SET, mit dynamische Variable in shvname



System Exit Handler

System Exit Handler ermöglichen es dem Programmierer, in wichtige Bereiche der Rexx-Verarbeitung einzugreifen und das Verhalten des Rexx-Interpreters an diesen Stellen zu ändern:

  • RXFNC - External Function Exit Handler
    Dieser Handler wird vor der Ausführung jeder externen Funktion gestartet.

  • RXCMD - Subcommand Exit Handler
    Dieser Handler wird vor jedem Subcommand-Handler ausgeführt.

  • RXMSQ - External Data Queue Exit Handler
    Fängt lesende und schreibende Zugriffe auf eine Datenqueue bzw. einen Stack ab.

  • RXSIO - Standard I/O Exit Handler
    Kann die Ausgabe von Texten (via SAY) bzw. Fehlermeldungen ebenso wie die Standardeingabe (via PULL oder PARSE PULL) und die Trace-Ausgabe umleiten.

  • RXHLT - Halt Condition Exit Handler
    Wird nach Beendigung jedes Rexx-Befehls ausgeführt. Dieser Handler kann die Ausführungsgeschwindigkeit verringern.

  • RXTRC - Trace Status Exit Handler

  • RXINI - Initialization Exit Handler
    Wird vor Beginn der Scriptausführung gestartet, um Variablen anlegen zu können oder den Trace-Status zu setzen.

  • RXTER - Termination Exit Handler
    Das Gegenstück zu RXINI. wird nach dem letzten Rexx-Statement ausgeführt.

Jeder Handler basiert auf folgenden Prototyp:

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

Der Zeiger ParmBlock zeigt abhänglig von den Werten in ExitNumber und Subfunction auf unterschiedliche Datenstrukturen. So enthält der Exit Handler für RXSIOSAY die auszugebende Zeichenkette als RXSTRING.



Multithreading

Seit der Version 2.0 ist Regina Rexx "thread save", d.h. es können aus mehreren Threads heraus gleichzeitig Rexx-Scripts ausgeführt werden.

Wenn Sie Regina in eine Multithreading-Applikation einbinden, verwaltet Rexx für jeden Thread eine eigene Subcommand-, Function- und Exit-Handler-Liste. Daher kann jeder Applikationsthread mit Rexx (fast) so umgehen als wäre er der einzige Thread.

Es darf allerdings nicht mit globalen Variablen gearbeitet werden, um Informationen an die Handler zu übergeben, sondern es muss bei der Registrierung der UserAreaPtr-Parameter in Verbindung mit RexxQueryXXX innerhalb der Handler verwendet werden.





mod_rexx

mod_rexx hat nun die Aufgabe, die Kluft zwischen den Schnittstellen von Rexx und Apache zu überbrücken.

Mein Ziel war es, dem Script-Programmierer die von CGI-Scripten gewohnte Umgebung zu erhalten. Leider gibt es dennoch einige architekturbedingte Abweichungen:

  • Die Standardeingabe von Rexx lässt sich genauso wie auch die Standardausgabe nicht auf die Socket-Verbindung umbiegen. Daher kann der Request-Body eines POST-Requests nicht wie bei CGI über die Standardeingabe ausgelesen werden, sondern wird vor der Ausführung des Scripts eingelesen und im Rohformat in der Rexx-Variablen APACHE.POST_DATA abgelegt.

  • Die Windows-Version von Apache bearbeitet mehrere Requests innerhalb eines Prozesses über mehrere Threads gleichzeitig. Für die Dauer eines Request sollten sich aber die Scripts beim Zugriff auf die Umgebungsvariablen nicht in die Quere kommen. Alle Scripts, die diese Funktionalität benötigen müssen daher sequenziell ausgeführt werden.
    Der Administrator deshalb die Möglichkeit, ganz auf die Verwendung von Umgebungsvariablen zu verzichten: Alle Environment-Variablen werden auf die Rexx-Variablen unter APACHE!ENVIRONMENT.* abgebildet. Zur Arbeitserleichterung werden darüberhinaus alle mit GET oder POST übermittelten Variablen PHP-like dekodiert und in APACHE!QUERY.* und APACHE!FORM.* abgelegt.

Über die Konfigurationsdatei von Apache httpd.conf lässt sich die Verwendung von Umgebungsvariablen verzeichnisabhängig einschalten. Per Default sind sie abgeschaltet und sollten nur mit Vorsicht eingesetzt werden, da solche Scripts sich gegenseitig in der Ausführung blockieren.

Ein Rexx-Script beginnt wie gewohnt mit den Ausgabeheadern und einer Leerzeile:

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

    SAY 'Hello world!'

Auf GET- oder Post-Variablen zuzugreifen ist am einfachsten. Wenn beispielsweise ein Textfeld mit dem Namen MyText per HTTP-GET übergeben wird, so findet sich deren Wert in APACHE!QUERY.MYTEXT, die entsprechende HTTP-POST-Variable hätte den Namen APACHE!FORM.MYTEXT. Mehrwertige Variablen werden in ein Rexx-typisches Array umgewandelt:

Z.B. führt folgender 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>

zur Generierung von APACHE!FORM.XYZ.1 bis APACHE!FORM.XYZ.n.
Die Anzahl n wird in APACHE!FORM.XYZ.0 abgelegt.

Sie können über APACHE!QUERY_STRING und APACHE!POST_DATA auch auf die Rohdaten zugreifen, falls die Daten nicht http-urlencoded vorliegen.

Bei den Environment Variablen wird anders vorgegangen: Die Header werden in einem Array APACHE!ENVIRONMENT. abgelegt: Die Namen unter APCHE!ENVIRONMENT.I.NAME und die Werte unter APACHE!ENVIRONMENT.I.VALUE mit Index I=1..APACHE!ENVIRONMENT.COUNT.



Programmablauf

Initialsierungsphase:

Zunächst wird beim Start von Apache die Funktion rexx_initialize aufgerufen, die die Variable rexx_mutex vorbereitet. Die Variable ist für die Koordinierung des gegenseitigen Ausschlusses von Rexx-Threads mit Zugriff auf die Umgebungsvariablen notwendig.

Danach parst Apache die Konfigurationsdateien. Wenn eine neue Rexx-Konfiguration nötig wird, reserviert rexx_create_dir_config den notwendigen Speicher und deaktiviert die Verwendung von Environment-Variablen als Voreinstellung. Jedesmal wenn Apache auf den Befehl RexxCreateEnvironment stößt, wird rexx_cmd_createEnvironment aufgerufen, wo die Rexx-Konfiguration des Verzeichnisses, in dem der Befehl steht, aktualisiert wird.

Die Behandlung eines Requests:

Der einzige registrierte Handler für Rexx-Scripts ist der Content Handler rexx_handler. Dieser prüft zunächst, ob ein GET- oder POST-Request vorliegt und bricht eventuell die Verarbeitung mit DECLINED ab. Als nächstes wird kontrolliert, ob die Scriptdatei überhaupt vorhanden ist und ebenfalls entsprechend reagiert.

Alle Request-spezifischen Informationen werden dann in der Datenstruktur request_data abgelegt, deren Adresse bei der Registrierung mit an die Rexx-Handler übergeben wird. So hat z.B. der Rexx I/O-Handler indirekt Zugriff auf die request-rec-Struktur, um die Ausgabe an den Client weiterzuleiten. request_data speichert ausserdem den Request Body und ein assioziatives Array mit Rexx-Variablen für den Rexx INI-Handler.

Die Ausgabe bekommt den Typ "text/html". Falls nur die Header erfragt wurden, werden diese ausgegeben und die Verarbeitung beendet.

Mit ap_soft_timeout() teilen wir Apache mit, dass im Falle eines Verbindungsabbruchs durch den HTTP-Client alle weiteren Ausgaben ignoriert werden sollen. Rexx-Scripte werden damit immer bis zum Schluss ausgeführt.

Danach werden alle Rexx-Variablen generiert: Zuerst für die Request-Header, dann - falls vorhanden - die GET-Parameter. Falls ein POST-Request vorliegt wird der Body eingelesen und im der request_data-Struktur gespeichert. Wenn der Inhalt den MIME-Typ 'application/x-www-form-urlencoded' besitzt werden hier ebenfalls alle Variablen extrahiert.

Die Rexx-Handler für Initialisierung und Ausgabesteuerung werden registriert.

Bevor die Scriptausführung nun mit RexxStart() gestartet wird, kontrolliert das Modul noch, ob auf das Environment zugegriffen werden muss. Falls ja, so wird über ap_acquire_mutex() sichergestellt, das kein anderer Modulthread während der Scriptausführung in das Environment schreibt. Nach der Ausführung wird zur Freigabe dann ap_release_mutex() ausgeführt.

Danach werden die Handler wieder deregistriert. Die Registrierung und Deregistrierung ist bei jedem Request notwendig, da Informationen über den aktuellen Request nicht in globalen Variablen gespeichert werden können, sondern über RexxRegisterExitExe() an die Handler übergeben werden müssen.

Zum Schluss gibt rexx_handler() noch den Speicher des RexxStart()-Rückgabeergebnisses frei.



Die Variablenkonversion:

Die Bearbeitung der Variablen vollzieht sich in mehreren Schritten:

  • Rohdaten lesen: bei einem POST-Request wird der Nachrichtenkörper vollständig eingelesen. Dies geschieht in der Funktion read_request_body(). Der Query_String findet sich bereits in request_rec->args.

  • Variablen dekodieren: Das Datenfeld wird geparst und die Feldnamen und -werte in eine verkettete Liste geschrieben. GET- und POST-Variablen liegen i.d.R. im selben Format vor. Die Feldnamen werden in Großbuchstaben umgewandelt und an den Variablenstamm "APACHE!FORM." bzw. "APACHE!QUERY." gehängt. (append_form_table() und rvl_append_var() / rvl_append_arrvar())
    Die APACHE!ENVIRONMENT-Variablen liegen bereits in Apache-Array vor und werden ebenfalls mit neuen Namen in eine Liste geschrieben (append_header_var()).
    Danach werden alle Listen zu einer großen Liste vereinigt.

  • Übertragen nach Rexx: Nach dem Aufruf von RexxStart(), aber vor der Ausführung des ersten Rexx-Kommandos führt Rexx die INI-Exit-Handler aus. rxini_exit_handler() besorgt sich über RexxQueryExit() zuerst den Request-Kontext und damit den Zeiger auf die Variablenliste.
    Über die Rexx-API-Funktion RexxVariablePool() wird die Liste an Rexx übergeben.



Umleitung der Script-Eingabe/-Ausgabe:

Der für die Ein- und Ausgabe registrierte Exit-Handler, rxsio_exit_handler() ist einerseits für die Ausgabe von Fehlermeldungen als auch für die ganz normale Textausgabe zuständig. Außerdem ermöglicht er das zeilenweise Einlesen eines Request-Bodys via PULL / PARSE PULL.

Der Handler holt sich zuerst die Request-Informationen, die zur Ausgabe notwendig sind.

Wenn Rexx als Subfunction-Parameter die Konstante RXSIOTRC übergibt, so ist der übergebene Text eine Fehlermeldung. Wenn noch keine Header übertragen wurden, wird dies nachgeholt - sonst wäre die Meldung nicht zu sehen und die HTTP-Response an den Client nicht gültig. Dann werden ein paar HTML-Tags geschickt, ebenfalls um sicher zu gehen, das die Fehlerausgabe auch sichtbar ist. Die Meldung wird zur Hervorhebung fettgedruckt dargestellt.

Die Konstante RXSIOSAY wird im Falle einer normalen Textausgabe übergeben. Nun wird zuerst kontrolliert, ob die Header bereits gesendet wurden. Falls ja, wird der Zeichenketteninhalt einfach ausgegeben und mit einem Newline-Zeichen abgeschlossen.

Headerzeilen werden erstmal am Doppelpunkt gesplittet und beide Teile in die request_rec->headers_out Tabelle eingetragen. Bei einer leeren Zeile werden alle Header per ap_send_http_header() gesendet.

Beim Lesen von Zeilen ist zu beachten, dass das Script mit einer Fehlermeldung abbricht, wenn über die letzte Zeile hinaus gelesen wird. Der in Rexx-CGI übliche Weg über die Funktion CHARIN() funktioniert nicht, da hier keine Eingriffsmöglichkeiten über die API bestehen!



Installation unter Linux:

Installation von Regina Rexx ab Version 2.0:

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

Installation von Apache 1.3.14 mit 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]

Anpassung der Apache Konfiguration: folgende Zeile muss am Ende von /usr/local/apache/conf/httpd.conf eingefügt werden:

    AddHandler rexx-handler .rexx

Referenz:

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

[2] Apache 1.3.14 Quellcode: http://httpd.apache.org/dist/apache_1.3.14.tar.gz

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

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

[5] Regina Rexx 2.2 Dokumentation: http://www.lightlink.com/hessling/Regina/

[6] Regina Rexx 2.0 / 2.2 Quellcode: http://www.lightlink.com/hessling/Regina/

[7] PHP 4.0.2 Quellcode: http://www.php.net

[8] mod_perl 1.24 Quellcode: http://perl.apache.org