[ zurück ]

Verarbeitung der Klient-Eingaben


Die Leistungsfähigkeit der ersten Servererweiterung IsapiHallo.DLL ist zugegebenermaßen recht gering. Der Hauptmangel ist das Fehlen jeglicher Reaktion auf etwaige Klienteingaben. Dieses Manko soll mit der nächsten DLL ausgeglichen werden. Dazu ist erst einmal ein intensiverer Blick auf die Datenstruktur ECB notwendig.

Der Extension-Control-Bloc ECB

Der Extension-Control-Bloc (ECB) wird bei jeder Anforderung im Adreßraum des Servers neu angelegt und zur Vermittlung der Ein- und Ausgaben benutzt. Auch bei vielen gleichzeitigen Anforderungen greift jeder Thread auf "seinen" ECB zu. Die Tabelle listet alle Elemente auf, die pro Ausfertigung des Datenblockes vorhanden sind:

StrukturelementBeschreibung
cbSize (IN) Die Größe der Record-Struktur ECB.
dwVersion (IN) Die Versionsnummer dieser Struktur: Versionsinformation des ISAPI-Standards, auf die der entsprechende ECB aufbaut; das HIWORD repräsentiert die major Versionsnummer, das LOWORD die minor Versionsnummer, die unterstützt wird.
ConnID (IN) Eine eindeutige Identifikations-Nummer für die aktuelle Anforderung, die vom HTTP Server zugewiesen wird. Sie darf nicht verändert werden.
dwHttpStatusCode (OUT) Der Status der aktuellen Transaktion, wenn die Anforderung fertig ist.
lpszLogData (OUT) Puffer der Größe HSE_LOG_BUFFER_LEN (derzeit 80 Zeichen). Er wird mit einem null-terminierten Log-Informations-String der aktuellen Transaktion gefüllt. Diese Log-Information wird im HTTP-Server-Log eingetragen. Das ist ein gemeinsames Log-File, das HTTP-Server und ISAPI-Anwendungen zu administrativen Zwecken dient.
lpszMethod (IN) Die Methode, mit der die Anforderung getätigt wurde.
lpszQueryString (IN) Ein null-terminierter String, der die Abfrage-Information des Klient enthält.

lpszPathInfo (IN)

Ein null-terminierter String, der zusätzliche Pfadinformationen enthält, die vom Klient gekommen sind.
lpszPathTranslated (IN) Ein null-terminierter String, der den übersetzten Pfad enthält.
cbTotalBytes (IN) Die Anzahl der Bytes, die vom Klient gesendet wurden. Einen Sonderfall stellt der Wert 0xffffffff dar: Es liegen dann mehr als 4 GigaByte Daten vor. In diesem Fall ist die Funktion ReadClient so oft aufzurufen, bis keine Daten mehr anstehen.
cbAvailable (IN) Die Anzahl der verfügbaren Bytes, die sich im Puffer befinden; Auf diesen Puffer zeigt der Zeiger lpbData. Ist der Wert von cbTotalbytes gleich dem Wert cbAvailable, dann zeigt der Zeiger lpbData auf einen Puffer, der die gesamten gesendeten Daten enthält. Ansonsten enthält cbTotalbytes die Anzahl der gesendeten Bytes. Die ISAPI-Anwendung benötigt die Callback-Funktion ReadClient, um den Rest der Daten zu lesen (beginnend bei einem Offset von cbAvailable).
lpbData (IN) Zeiger auf den Puffer der Größe cbAvailable, welcher die gesendeten Daten des Klient enthält. Es werden maximal die ersten 48 KBytes geliefert.
pszContenttype (IN) Ein null-terminierter String, der den Content-Typ der Daten enthält, die der Klient gesendet hat.

Zuerst interessiert der Inhalt des Feldes lpszMethod. Davon hängt ab, wo die weiteren Eingabedaten des Klient zu finden sind. Bei der Methode GET liefert der Browser seine Daten an den sogenannten QueryString lpszQueryString. Bei der Aufrufmethode POST gelangen die vom Klient gesendeten Daten in den Puffer lpbData. Zumindest die ersten 48 KByte davon, aber dieses Maß dürfte selten überschritten werden. Die Daten im Puffer sind im Allgemeinen kodiert. lpszContentType gibt Auskunft darüber, nach welchem Verfahren diese Kodierung stattgefunden hat. Formulardaten liegen in der Regel als "application/x-www-form-urlencoded" vor.

Alle diese Datenfelder können Sie bequem von der ISAPI-DLL aus dem ECB-Block auslesen lassen und weiterbenutzen. Achten Sie aber bitte von vornherein exakt auf die Datentypen. Je nach Compilereinstellung bekommen Sie speziell unter Delphi 4/5 sonst Typkonflikte angezeigt.

Das einzige Problem für Sie beim Variablenabruf stellt die Dekodierung der Content-Daten dar. Hierfür gibt es aber aus der CGI-Programmierung heraus genügend bewährte Algorithmen. Nach geringen Modifizierungen sind diese Quelltexte auch für ISAPI-DLLs verwendbar.

formular-kodierte Daten

Das Prinzip soll an einem typischen Beispiel verdeutlicht werden.

Screen

Der Anwender habe in seinem Browser ein Formular mit dem Datenfeld Name mit Max Möller und ein Datenfeld Mail mit mm@firma.de ausgefüllt. Anschließen hat er den Submit-Button mit der Aufschrift Abschicken betätigt. Die über die Methode POST an den Server gesandten Daten werden der ISAPI-Erweiterung per ECB übergeben. ECB.lpbData zeigt dann auf einen Zeichenpuffer mit folgendem Inhalt:

Name=Max+M%F6ller&Mail=mm@firma.de&Button=Abschicken
Daran erkennen Sie die wichtigsten Regeln der "www-form-url"-Kodierung.

In nahezu jedem ISAPI-Projekt benötigen Sie deshalb eine Dekodier-Routine, die diese Veränderungen wieder auflöst. Dazu ist die folgende Funktion GetContentData geeignet. Sie finden sie neben anderen Hilfsroutinen in meiner Unit IsapiHlp, die den folgenden Demoprojekten beiliegt.

function GetContentData(var List: TStringList): Boolean;
var pCont: PChar;
    nContentLength, i: Integer;
    sPair: String;
begin
 nContentLength:=ECB.cbAvailable;
 if nContentLength>=0 then begin
  pCont:=ECB.lpbData;
  i:=0;
  sPair:='';
  while i<nContentLength do begin
   case (pCont+i)^ of {Sonderzeichen behandeln}
    '+': sPair:=sPair+' ';
    '%': begin
          sPair:=sPair+Chr((HxToInt((pCont+i+1)^)shl 4)+
          HxToInt((pCont+i+2)^));
          Inc(i,2);
         end;
    '&': begin List.Add(sPair); sPair:='' end;
    else sPair:=sPair+(pCont+i)^;
   end;
   Inc(i,1);
  end;
  List.Add(sPair);
  GetContentData:=true;
 end else GetContentData:=false;
end;

Der mittels ECB.lpbCont übergebene Zeichenpuffer wird hier Schritt für Schritt durchlaufen. Gefundene Sonderzeichen werden umkodiert. Beim Auftreten eines Separators in Form von "&" wird in der Ergebnisliste List ein neuer Eintrag angelegt. Die mit den dekodierten Parameterpaaren gefüllte Liste wird an das aufrufende Programm zurückgegeben.

Die Verwendung einer Ergebnisliste vom Typ TStringList hat den Vorteil, daß Sie später darin bequem nach dem Auftreten eines beliebigen Schlüsselpaares suchen können. Die Anweisung

sName:=Liste.Values['Name']

zieht aus der Stringliste den Wert des Namen heraus, ganz gleich an welcher Position innerhalb der Liste und somit innerhalb des Formulars er steht.

Nicht verschwiegen werden soll, daß GetContentData( ) nicht nachschaut, ob mehr als 48 KByte Eingabedaten vom Formular gesendet wurden. Es wird nur cbAvailable zur Längenbestimmung herangezogen. Erfahrungsgemäß dürften aber bereits Datenmengen von 1 KByte aufwärts bei Formularinhalten die Ausnahme darstellen.

Noch mehr Eingangs-Informationen - die Servervariablen

Recht häufig benötigen Sie für Ihr ISAPI-Programm noch weitere Informationen. So interessiert Sie vielleicht, welchen Browser der Anwender benutzt oder woher die Anfrage kommt. Für diese Fälle stellt der Server zusätzliche Informationen bereit. Allerdings erhalten Sie sie nicht ganz so bequem wie die bisherigen Eingabedaten.

Zusatzinformationen rufen Sie über die Funktion GetServerVariable ab. Auch diese Prozedur ist in der Unit Isapi deklariert. Sie erfordert als entscheidenden Parameter den Namen der abzufragenden Servervariablen. Welche Variablen laut Microsoft-Spezifikation verfügbar sind, zeigt folgende Tabelle.

ServervariableBeschreibung
AUTH_TYPE Beinhaltet den zur Beglaubigung verwendeten Typ. Bei elementarer Beglaubigung wird der Wert "basic" sein. Für NT-Anforderungen kann das "NTLM" sein. Bei leerem Rückgabewert wird keine Beglaubigung verwendet.
CONTENT_LENGTH Anzahl der Bytes, die das Programm als Eingabedaten vom Klient erhalten hat.
CONTENT_TYPE Der Content-Typ der Informationen, die aus einem Formular bei Verwendung der Methode "POST" empfangen wurden.
PATH_INFO Zusätzliche Pfad-Informationen, die der Klient übermittelt. Sie besteht aus einen Zusatzteil der URL nach dem DLL-Namen bis zum QueryString, sofern ein solcher existiert.
PATH_TRANSLATED Eine Pfadangabe zum virtuellen Scriptverzeichnis, jedoch als real übersetztes Verzeichnis auf dem Server-Computer.
QUERY_STRING Die Information, die dem "?" in der URL beim Aufruf des Programmes folgt.
REMOTE_ADDR Die IP-Adresse des Klient. Falls der Klient hinter einer Firewall oder über einen Agenten arbeitet beinhaltet REMOTE_ADDR nur deren Adressen oder ist leer.
REMOTE_HOST Der Hostname des Klient oder dessen Agenten, von dem die Anfrage eintrifft.
REMOTE_USER Beinhaltet den Nutzernamen, wie er vom Klient eingespeist wird. Allerdings nur bei Beglaubigung durch den Server. Anderenfalls zählt der Nutzer als anonym und es ist ein leerer String.
UNMAPPED_REMOTE_USER Benutzername, bevor etwa vorhandene ISAPI-Filter den Nutzer kartiert haben.
REQUEST_METHOD Die HTTP-Anforderungs-Methode.
SCRIPT_NAME Der Name des Programmes, das gerade ausgeführt wird.
SERVER_NAME Der Hostname des Servers oder notfalls dessen IP-Adresse.
SERVER_PORT Der TCP/IP-Port, über den die Anfrage eingetroffen ist.
SERVER_PORT_SECURE Ein String mit dem Wert 0 oder 1. Wenn die Anfrage über einen geschützten Port eintrifft, dann 1. Anderenfalls 0.
SERVER_PROTOCOL Der Standard und die Version des Abruf-Protokolls der Anfrage. Üblicherweise ist das HTTP/1.0.
SERVER_SOFTWARE Name und Version des Webservers, auf dem die ISAPI-DLL als Servererweiterung läuft.
ALL_HTTP Alle HTTP-Angaben, die bisher noch nicht in einer der vorherigen Variablen übergeben wurden. Alle diese Angaben haben die Form HTTP_<FeldName>. Es ist ein null-terminierter String. Die Einzelteile sind durch Zeilenvorschübe voneinander getrennt.
HTTP_ACCEPT Spezieller HTTP-Kopf mit Werten der vom Klient akzeptierten Datentypen. Die einzelnen Angaben sind durch Komma getrennt.
URL (neu in Version 2.0) Liefert den Basis-Teil der URL.

Eine Möglichkeit zum komfortablen Abruf einer solchen Variable zeigt Ihnen die folgende Hilfsroutine. Sie liefert das Ergebnis in der Form eines bequem zu handhabenden Pascal-Strings.

function GetVar(ServerVar: PChar): String;
var cBuf: array[0..1535] of Char;
    BufLen: DWORD;
begin
 BufLen:=SizeOf(cBuf);
 FillChar(cBuf,BufLen,#0);
 ECB.GetServerVariable(ECB.ConnID, ServerVar, Addr(cBuf), BufLen);
 GetVar:=cBuf;
end;

Die meisten Servervariablen geben einen relativ kurzen Inhalt zurück. Ausnahmen stellen die Variablen HTTP_ACCEPT und ALL_HTTP dar, die bei manchen Browsern gut einige hundert Zeichen lang sind. Dimensionieren Sie den Rückgabepuffer zur Ergebnisaufnahme für beide bitte nicht zu knapp.

HTTP_ACCEPT liefert Ihnen die Information darüber, welche Typen von Ergebnisdateien der Browser des anfragenden Klient verarbeiten kann. HTTP_ALL verrät viele Details über die Ausstattung des Klient. Eine darin enthaltene Angabe zu HTTP_USER_AGENT referenziert den verwendeten Browser. Falls auch HTTP_UA_PIXELS übermittelt wird, erfahren Sie damit in Ihrem Serverprogramm die Bildschirmauflösung auf dem Klient-Computer. Leider liefern die unterschiedlichen Browser innerhalb der Variablen HTTP_ALL sehr differierende Angaben.


[ Seitenanfang ] [ ISAPI ]

J. Hummel,   2000