Benutzer-Werkzeuge

Webseiten-Werkzeuge


edv:prg:apache:apache-module

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:

  • "c" - kompilieren
  • "i" - installiern
  • "a" - aktivieren

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):

  • request_rec - Diese Struktur representiert den jeweiligen (bzw. aktuellen) http-Request.
    Die Struktur geht über alle Handler durch, die von den Modulen implementiert sind.
  • server_rec - Diese Struktur speichert Informationen über jeden logischen (sprich virtuellen) Server.
    Jeder logische Server hat normalerweise eine eigene Instanz dieser Struktur. Die Struktur wird beim Start des Servers erzeugt und bleibt permanent bestehen, solange der Server läuft.
  • conn_rec - Diese Struktur representiert eine TCP-Connection.
    Sie wird erzeugt, wenn Apache einen Request vom Client entgegen nimmt und wird zerstört, wenn die Connection schließt.
    Es ist üblich, dass mehrere "request_rec" auf dieselbe "conn_rec" verweisen, weil sie in der Folge über dieselbe TCP-Connection abgesetzt werden.
  • process_rec - Diese Struktur representiert den Prozess.

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

  • request pool
    Lifetime: http-Request
    Zugriff: request->pool
  • process pool (für lang "lebende" Ressourcen)
    Lifetime: Server Prozess
    Zugriff: process->pool
  • connection pool
    Lifetime: TCP-Connection
    Zugriff: connection->pool
  • configuration pool
    Lifetime: bis zum nächsten Neulesen der Konfiguration
    Zugriff: process->pconf

:!: 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:

  • r->handler (char*): Beinhaltet den Namen des Handlers, den der Server gerade fragt, den aktuellen Request zu bearbeiten.
  • r->method (char*): Beinhaltet den Namen des HTTP-Requests, z.B. GET oder POST.
  • r->filename (char*): Beinhaltet den "translated filename", den der Client angefordert hat.
  • r->args (char*): Beinhaltet den Argumenten-String aus dem Request, falls vorhanden.
  • r->headers_in (apr_table_t*): Beinhaltet alle Headers, die vom Client gesendet wurden.
  • r->connection (conn_rec*): Diese Record beinhaltet Information über die aktuelle Connection.
  • r->user (char*): Falls URI eine Authentifizierung verlangt, hier steht der "username".
  • r->useragent_ip (char*): Die IP-Adresse des Clients.
  • r->pool (apr_pool_t*): Der "memory pool" des aktuellen Requests.

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:

  • Allgemein:
    • DECLINED: Der Handler ist für diesen Request nicht zuständig.
    • OK: Der Request wurde erfolgreich bearbeitet.
    • DONE: Der Request wurde bearbeitet und der Server soll diesen Thread schließen, ohne ihn weiter zu bearbeiten.
  • HTTP-spezifisch:
    • HTTP_OK (200): Der Request war OK.
    • HTTP_MOVED_PERMANENTLY (301): Die Ressource wurde "verschoben" zu einer anderen URL.
    • HTTP_UNAUTHORIZED (401): Der Client wurde für duese Seite nicht authorisiert.
    • HTTP_FORBIDDEN (403): Keine Berechtigung / Zugriff verweigert.
    • HTTP_NOT_FOUND (404): Spricht für sich - "File not found".
    • HTTP_INTERNAL_SERVER_ERROR (500): Ebenfalls selbsterklärend - "Internal server error".

:!: 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):

  • post_read_request-Phase markiert den Übergang vom Protokoll zur Behandlung des Requests. Das request_rec-Objekt ist zwar gültig, jedoch viele Felder sind noch nicht gesetzt.
    • post_read_request - Allgemeiner Hook, läuft sofort beim Erstellen des request_rec-Objekts.
  • request preparation-Phase:
    • Sub-Phase: Auflösung des Requests ins Filesystem und/oder logisches URL-Spaces entsprechend Definition des Servers.
      • translate_name - Mapping der Request-URL in das Filesystem.
      • map_to_storage - Wendet die "per-directory"-Konfiguration an.
    • Sub-Phase: Erster Hook, bei dem die "per-directory"-Konfiguration zur Verfügung steht.
      • header_parser - Prüfung des "HTTP-Request"-Headers. Ein allgemeiner Hook nachdem die Konfiguration komplett zur Verfügung steht, aber vor dem Begin von spezifischen Phasen.
    • Security-Phase: Hier wird bestimmt, wozu der User berechtigt ist.
      • access_checker - Kontrolle, ob der Zugriff zulässig für den Remote-Host.
      • check_user_id - Authentifizierung des "remote Users" (falls anwendbar).
      • auth_checker - Kontrolle, ob der remote-User für den Request berechtigt ist.
    • Sub-Phase: Die letzte Phase vor dem Content Generator.
      • type_checker - Anwendung der Konfigurationsregeln, die bestimmen die Handler- und Response-Headers.
      • fixup - Allgemeiner Hook am Ende der Vorbereitung des Requests, aber bevor die Handler aufgerufen werden.
  • handler-Phase (Content Generator):
    • insert_filter - Fügt Content-Filter hinzu.
    • handler - Bearbeitung des Requests und Generierung der Response.
  • logger-Phase (die finale Phase):
    • logger - Protokollierung der Transaktion.

:!: 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

  • ap_rputs()
    ap_rputs(const char *string, request_rec *r);

    Sendet einen String zum Client:

    ap_rputs("Hello, world!", r);
  • ap_rprintf()
    int ap_rprintf(request_rec *r, const char *fmt, ... );

    Diese Funktion funktioniert ähnlich wie printf(), mit dem Unterschied, dass sie das Ergebnis zum Client sendet:

    ap_rprintf(r, "Hello, %s!", r->useragent_ip);
  • ap_set_content_type()
    ap_set_content_type(request_rec *r, const char *type);

    Setzt den Content-Type für den Output:

    ap_set_content_type(r, "text/plain"); /* force a raw text output */

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:

  • void* apr_palloc(apr_pool_t *p, apr_size_t size): Zuteilung von "size" Bytes in dem "pool".
  • void* apr_pcalloc(apr_pool_t *p, apr_size_t size): Zuteilung von "size" Bytes in dem "pool" und Setzen aller Bytes auf 0.
  • char* apr_pstrdup(apr_pool_t *p, const char *s): Dupliziert den String "s". Das ist besonders praktisch fürs Kopieren von konstanten Werte um sie zu editieren.
  • char* apr_psprintf(apr_pool_t *p, const char *fmt, … ): Ähnlich wie "sprintf()", mit dem Unterschied, dass der Server liefert die entsprechend zugeteilte Zielvariable.

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.

  • read gibt die Adresse und Größe der Daten im Bucket zurück.
    Wenn sich die Daten nicht im Speicher befinden, werden sie eingelesen, und der Bucket ändert den Typ, sodass er sich auf den neuen Speicherort der Daten beziehen kann. Wenn alle Daten nicht in den Bucket passen, wird ein neuer Bucket in die Brigade eingefügt, um den Rest zu platzieren.
  • split teilt die Daten in einem Bucket in zwei Bereiche auf.
    Nach der Aufteilung bezieht sich der ursprüngliche Bucket auf den ersten Teil der Daten, und der neue Bucket, der in die Brigade nach dem ursprünglichen Bucket eingefügt wird, auf den zweiten Teil der Daten.
    Referenz-Zähler werden entsprechend gepflegt.
  • setaside stellt sicher, dass die Daten im Bucket eine ausreichend lange Lebensdauer haben.
    Manchmal ist es praktisch, einen Bucket zu erstellen, der sich auf Daten auf dem Stack bezieht, in der Erwartung, dass er konsumiert wird (Ausgabe an das Netzwerk), bevor der Stack abgewickelt wird. Wenn sich diese Erwartung unerfüllt bleibt, wird die "setaside"-Funktion aufgerufen, um die Daten an einen sicheren Ort zu verschieben.
  • copy erstellt ein Duplikat der Bucket-Struktur, solange es möglich ist, um mehrere Referenzen auf eine einzige Kopie der Daten zu haben.
    Es können nicht alle Bucket-Typen kopiert werden.
  • destroy behandelt Referenz-Zähler der Ressourcen, die von einem Bucket verwendet werden, und gibt sie bei Bedarf frei.

:!: 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

edv/prg/apache/apache-module.txt · Zuletzt geändert: 2020/01/11 01:23 von 127.0.0.1

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki