Inhaltsverzeichnis

Apache-Module

Welche Module im Apache statisch verlinkt sind, findet man auf der Kommandozeile mit "apache -l" heraus:

apache -l

Auf einer Windows-Maschine mit installiertem Apache 2.2.4 ergibt das beispielsweise folgende Anzeige:

Compiled in modules:
  core.c
  mod_win32.c
  mpm_winnt.c
  http_core.c
  mod_so.c

(Je nach installierter Version kann der Befehl auch "httpd -l" oder "apache2 -l" lauten.)

In dem oben gezeigten Beispiel für den selbst kompilierten Apache:

userid@rhel:/users/userid/apache/httpd-2.4.27/bin> ./httpd -l
Compiled in modules:
  core.c
  mod_so.c
  http_core.c
  event.c
userid@rhel:/users/userid/apache/httpd-2.4.27/bin>

Fertige Module kompilieren

Falls einige Module nur als Quelltext vorliegen, müssen sie zuerst kompiliert werden. Apache stellt dafür in seinem "bin"-Verzeichnis das Tool apxs zur Verfügung. Dieses Tool erlaubt es, in einem Rutsch das Modul zu kompilieren, installieren und in der "conf/httpd.conf" zu aktivieren.

Hier ist ein Beispiel.
Als ich Apache (httpd-2.4.27) kompiliert hatte, wurde dabei das Mudul mod_cgi nicht mit kompiliert. Das kann man per Hand nachholen:

userid@rhel:/users/userid/apache/httpd-2.4.27/modules/generators> ~/apache/httpd-2.4.27/bin/apxs -cia mod_cgi.c

Die Parameter "-cia" vom "apxs" stehen für:

Oder einzeln bzw. Schritt-für-Schritt:

userid@rhel:/users/userid/apache/httpd-2.4.27/modules/generators> ~/apache/httpd-2.4.27/bin/apxs -c mod_cgi.c
userid@rhel:/users/userid/apache/httpd-2.4.27/modules/generators> ~/apache/httpd-2.4.27/bin/apxs -i mod_cgi.la
userid@rhel:/users/userid/apache/httpd-2.4.27/modules/generators> ~/apache/httpd-2.4.27/bin/apxs -a mod_cgi.so

Die letzte Zeile bewirkt, dass die Konfiguration ("httpd.conf") um ensprechende Angaben zum Modul erweitert wird:

LoadModule cgi_module modules/mod_cgi.so

Zur Kontrolle bietet Apache zwei Skripte im cgi-bin-Verzeichnis - "printenv" (in Perl) und "test-cgi" (Shell-Script). Bitte beachten, dass bei den CGI-Skripten das x-Recht für "alle" gesetzt sein soll, damit diese ausfürbar sind.
Im Web-Browser auf die URL http://apache-adresse/cgi-bin/printenv bzw. http://apache-adresse/cgi-bin/test-cgi gehen.

:!: ACHTUNG:
Diese beide Skripte sind nur als Beispiel gedacht dürfen nicht in einer produktiven Umgebung betrieben werden - Sicherheitsrisiko!

Module in C++

Ein in C++ geschriebenes Modul zu kompilieren und installieren, geht etwas anders:

userid@rhel:/users/userid/apache/httpd-2.4.27/modules/beispiel/cpp> c++ -g -O2 -Wall -fPIC -I~/apache/httpd-2.4.27/include mod_beispiel.cpp
userid@rhel:/users/userid/apache/httpd-2.4.27/modules/beispiel/cpp> c++ -shared -o mod_beispiel.so mod_beispiel.o
userid@rhel:/users/userid/apache/httpd-2.4.27/modules/beispiel/cpp> cp mod_beispiel.so ~/apache/httpd-2.4.27/modules/

:!: Nicht vergessen, die Modul-Symbole mit extern "C" zu deklarieren, um C-Linkage zu erzwingen.

Module schreiben (in C)

Vereinfachte Übersicht über Aufbau von Apache-Modulen anhand des Beispiel-Moduls "holiday".

Wichtige Strukturen (definiert in httpd.h):

Mit diesen Strukturen sind entsprechende APR-Pools verknüpft:

:!: Achtung! Die Pools sind nicht Thread-sicher!

Beispiel für den Zugriff:

int my_func(request_rec* r)
{
	// Zugriff auf Request-Pool:
	some_call(r->pool);
 
	// Zugriff auf Prozess-Pool:
	some_call(r->server->process->pool);
 
	// Zugriff auf Connection-Pool:
	some_call(r->connection->pool);
}

Häufig verwendete Elemente der request_rec-Struktur:

Die komplette Liste aller Felder der request_rec-Struktur findet man im httpd.h-Header (oder unter http://ci.apache.org/projects/httpd/trunk/doxygen/structrequest__rec.html).

Hier (beispielsweise) eine Vorlage für ein Modul (namens "modulname"). Grundgerüst:

mod_modulname.c

#include "httpd.h"
#include "http_config.h"
#include "http_protocol.h"
 
// Forward-Deklaration der Modul-Datenstruktur
module AP_MODULE_DECLARE_DATA modulname_module;
 
 
/*                                                        */
/* Hier befinden sich alle internen Funktionen des Moduls */
/*                                                        */
 
// Datenstruktur des Moduls (Felder, die irrelevant sind, mit NULL initialisieren)
module AP_MODULE_DECLARE_DATA modulname_module =
{
	STANDARD20_MODULE_STUFF, // Makro für Apache2
	modulname_dir_config,    // Konfiguration pro Verzeichnis
	modulname_dir_merge,     // Merger der Verzeichniskonfigurationen
	modulname_server_config, // Konfiguration pro Server
	modulname_server_merge,  // Merger der Serverkonfigurationen
	modulname_commands,      // Befehlstabelle (Direktiven)
	modulname_register_hooks // Hooks (Aufruf der Modulfunktionen)
};

Die Struktur module hat folgenden Aufbau:

typedef struct module_struct module;
struct module_struct
{
	int version; // API version, *not* module version;
	int minor_version;
	int module_index;
	const char *name;
	void *dynamic_load_handle;
	struct module_struct *next;
	unsigned long magic;
	void (*rewrite_args) (process_rec *process);
 
	void *(*create_dir_config) (apr_pool_t *p, char *dir);
	void *(*merge_dir_config) (apr_pool_t *p, void *base_conf, void *new_conf);
	void *(*create_server_config) (apr_pool_t *p, server_rec *s);
	void *(*merge_server_config) (apr_pool_t *p, void *base_conf, void *new_conf);
	const command_rec *cmds;
	void (*register_hooks) (apr_pool_t *p);
};

Die Konstante STANDARD20_MODULE_STUFF belegt die ersten 8 Felder der Struktur und wird standardmäßig wie folgt definiert:

#define STANDARD20_MODULE_STUFF MODULE_MAGIC_NUMBER_MAJOR, \
				MODULE_MAGIC_NUMBER_MINOR, \
				-1, \
				__FILE__, \
				NULL, \
				NULL, \
				MODULE_MAGIC_COOKIE, \
				NULL

:!: Die Deklaration durch das Makro "AP_MODULE_DECLARE_DATA" kann durch das Makro "AP_DECLARE_MODULE" ersetzt werden. Dabei muss der Modulname ohne Zusatz "_module" angegeben werden:

AP_DECLARE_MODULE(modulname) =
{
	STANDARD20_MODULE_STUFF,
	modulname_dir_config,
	modulname_dir_merge,
	modulname_server_config,
	modulname_server_merge,
	modulname_commands,
	modulname_register_hooks
};

(Info unter: https://httpd.apache.org/docs/2.4/developer/new_api_2_4.html#api_changes)

Das unterste Feld dieser Struktur zeigt auf die CallBack-Funktion, die für das Modul erforderliche Hooks registriert:

static void modulname_register_hooks(apr_pool_t *pool)
{
	// Hier werden für die Hook-Bearbeitung zuständige CallBack-Funktionen registriert
	// Als Beispiel, der Handler wird durch die Funktion "modulname_handler" realisiert:
	ap_hook_handler(modulname_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

:!: Außer ap_hook_handler() gibt es auch weitere Hooks. FIXME

:!: APR_HOOK_MIDDLE steht für den relativen Zeitpunkt, wann (ungefähr) der relevante Hook aufgerufen wird. Es gibt folgende Definitionen:

/* Hook orderings */
#define APR_HOOK_REALLY_FIRST	(-10)	// run this hook first, before ANYTHING
#define APR_HOOK_FIRST		0	// run this hook first
#define APR_HOOK_MIDDLE		10	// run this hook somewhere
#define APR_HOOK_LAST		20	// run this hook after every other hook which is defined
#define APR_HOOK_REALLY_LAST	30	// run this hook last, after EVERYTHING

Der Pointer "apr_pool_t *pool" kann für die Pre-Initialisierung von irgendwelchen Struktur genutzt werden, bevor irgendein Request durch den hier gebundenen Handler "modulname_handler" bearbeitet wird.

Die CallBack-Funktion des Handlers, in der die eigentliche Funktionalität des Moduls gekapselt ist:

static int modulname_handler(request_rec *r)
{
	/*                         */
	/* Hier arbeitet das Modul */
	/*                         */
 
	return DECLINED; // Falls nicht zuständig
}

Weitere Rückgabewerte:

:!: Hier sind Beispiele, wie man die Konfiguration(en) ausliest und behandelt: http://httpd.apache.org/docs/2.4/developer/modguide.html#context

Request parsen

Alles, was wir zum Parsen von GET- und POST-Daten benötigen, sind vier einfache Zeilen:

eine_funktion(request_rec *r)
{
	apr_table_t *GET;
	apr_array_header_t *POST;
 
	ap_args_to_table(r, &GET);
	ap_parse_form_data(r, NULL, &POST, -1, 8192);
 
	// Weiterer Code...
}

Zum Beispiel, wir wollen prüfen, ob ein GET-Parameter namens "digest" übergeben wurde und, falls dieser den Wert "md5" hat, dies entsprechend behandeln, sonst einen default-Wert zuweisen:

eine_funktion(request_rec *r)
{
	// Hier Code inklusive Parsen...
	// ...
 
	// Den Key "digest" aus dem Query-String holen:
	const char *digestType = apr_table_get(GET, "digest");
 
	// Falls "md5", entsprechende Behandlung:
	if (!strcasecmp(digestType, "md5")) {
		// Weiterer Code...
	}
 
	// Falls nicht vorhanden, auf den default-Wert setzen:
	if (!digestType) digestType = "sha1";
 
	// Weiterer Code...
 
}

Der Code für einen POST-Request ist ein bißchen komplexer, hier ein Beispiel: http://httpd.apache.org/docs/2.4/developer/modguide.html#get_post

An gleicher Stelle gibt es 2 weitere Beispiele: "Printing out every HTTP header received" (bzw. das Lesen von Felder aus dem HTTP-Header) und "Reading the request body into memory".

Request-Behandlung

Module können in den Bearbeitungszyklus auf folgenden Wege (Hooks) eingreifen (nach Phasen gruppiert):

:!: Bitte die URLs nicht mit Dateisystempfaden verwechseln! Obwohl sie übereinstimmen können (und defaultmäßig auch tun das), ist dies nur eine Frage der Konvention.
Die Begriffe <Directory> und <Files> beziehen sich auf das Filesystem, die <Location> dagegen auf die URLs.

Häufig genutzte Funktionen

Speicherverwaltung

Man sollte vermeiden, Speicher mit den malloc() bzw. free() zu zuteilen bzw. freizugeben. Das gleiche gilt fürs Öffnen/Schließen von Files mittels fopen() bzw. fclose(), für Sockets und auch Mutexe.
Für Ressource-Management sind die APR-Pools zuständig. Das ist zwar eine andere Vorgehensweise als Constructor/Destructor oder Garbage Collection Model, die komplett im Hintergrund agieren, verwaltet aber weitgehend selbständig die Zuteilung und Freigabe von Ressourcen.

Beispiel:

// Konventionelle Methode:
mytype* myvar = malloc(sizeof(mytype));
/* Prüfung, ob "myvar != NULL" */
 
/* Weiterer Code */
 
free(myvar);
 
// ----------------------------------------
 
// APR Methode:
mytype* myvar = apr_palloc(pool, sizeof(mytype));
 
/* Weiterer Code, keine explizite Freigabe */

Es ist auch möglich, beide Methoden zu kombinieren. Dabei werden Ressourcen auf konventionelle Weise zugeteilt und dann "in die Hände" von APR übergeben.

Hier ein Beispiel:

// Speicher:
mytype* myvar = malloc(sizeof(mytype));
apr_pool_cleanup_register(pool, myvar, free, apr_pool_cleanup_null);
 
// File:
FILE* f = fopen(filename, "r");
apr_pool_cleanup_register(pool, f, fclose, apr_pool_cleanup_null);

:!: Dabei bleibt die Möglichkeit, auf diese Weise für die "externe" (durch APR) Verwaltung registrierte Ressourcen nativ freizugeben, trotzdem bestehen.

// Irgendwas zuteilen:
my_type* my_res = my_res_alloc(args);
// In der APR-Speicherverwaltung registrieren:
apr_pool_cleanup_register(pool, my_res, my_res_free, apr_pool_cleanup_null);
 
/* Weiterer Code, in dem my_res verwendet wird */
 
// Nachdem wir "my_res" nicht mehr brauchen:
rv = my_res_free(my_res);
// Aus der APR-Speicherverwaltung herausnehmen:
apr_pool_cleanup_kill(pool, my_res, my_res_free);
// Danach folgt die Fehlerbehandlung:
if (rv != APR_SUCCESS)
{
	/* Fehlerbehandlung */
}

Die beiden letzten Aktionen (Freigabe und Unregistrierung) kann man auch in einem Call realisieren:

apr_pool_cleanup_run(pool, my_res, my_res_free);

Häufig verwendete Funktionen:

Hier ein Beispiel:

static int example_handler(request_rec *r)
{
	const char *original = "Dieser String ist nicht editierbar";
	char *copy;
	int *integers;
 
	// Platz für 10x "int" zuteilen und deren Werte auf 0 setzen.
	integers = apr_pcalloc(r->pool, sizeof(int) * 10); 
 
	// Eine Kopie von dem Inhalt der Variable "original" erzeugen - die Kopie ist editierbar.
	copy = apr_pstrdup(r->pool, original);
 
	return OK;
}

Info zu den "Bucket brigades"

(Quelle: https://ci.apache.org/projects/httpd/trunk/doxygen/group__APR__Util__Bucket__Brigades.html#ga9a30babfeb6e290db124d8f9b69e49e4, hier meine Übersetzung.)

Vereinfachte Beschreibung:
Bucket brigades stellen einen komplexen Datenstrom dar, der ohne unnötiges Kopieren durch mehrere Schichten im IO-System behandelt werden kann.

Vollständigere Beschreibung:
Eine "bucket brigade" ist eine doppelt verkettete Liste (Ring) von "buckets", so dass wir nicht auf das Einfügen vorne und das Entfernen hinten beschränkt sind. Buckets werden nur als Mitglieder einer Brigade herumgereicht, wobei auch "Singleton-Buckets" für kurze Zeiträume auftreten können. "Buckets" speichern Daten verschiedener Typen. Sie können sich auf Daten im Speicher oder einen Teil einer Datei oder eines MMAP-Bereichs oder auf die Ausgabe eines Prozesses usw. beziehen.

"Buckets" haben auch einige typabhängige Zugriffsfunktionen: read, split, copy, setaside und destroy.

:!: Alle o.g. Funktionen verfügen über Wrapper-Makros (apr_bucket_read(), apr_bucket_destroy(), usw.), und diese Makros sollten verwendet werden, anstatt die Funktionszeiger direkt zu verwenden.

Um in eine "bucket brigade" zu schreiben, werden zuerst "iovec" gemacht, damit wir nicht zu kleine Daten-Portionen schreiben.
Momentan ignorieren wir das "Komprimieren" der Buckets in so wenige Buckets wie möglich, aber wenn wir wirklich eine gute Performance haben wollen, dann müssen wir die Buckets zusammenfassen, bevor wir sie zu einem "iovec" konvertieren (oder möglicherweise während wir sie zu einem "iovec" konvertieren).

:!: Ein weiterer Artikel, der das Konzept von "bucket brigades" erläutert: http://www.apachetutor.org/dev/brigades


Stand: 01.08.2018

EOF