Skip to content

10. Server in PHP

Da Programme mit der Kennung PHP von einem Server mit der Kennung WEB ausgeführt werden können, wird ein solches Programm zu einem Serverprogramm, das mehrere Clients bedienen kann. Aus Sicht des Clients entspricht der Aufruf eines Dienstes WEB der Anforderung des URL dieses Dienstes. Der Client kann in einer beliebigen Sprache geschrieben sein, insbesondere in PHP. Im letzteren Fall werden dann die soeben behandelten Netzwerkfunktionen verwendet. Außerdem müssen wir wissen, wie man mit einem WEB-Dienst „kommuniziert“, d. h. das http-Kommunikationsprotokoll zwischen einem Webserver und seinen Clients verstehen. Dies ist das Ziel der folgenden Programme.

Der in Abschnitt 9.2 beschriebene Web-Client hat es uns ermöglicht, einen Teil des Protokolls HTTP zu entdecken.

Image

In ihrer einfachsten Form verläuft der Austausch zwischen Client und Server wie folgt:

  • Der Client baut eine Verbindung zum Port 80 des Webservers auf
  • er stellt eine Anfrage bezüglich eines Dokuments
  • der Webserver sendet das angeforderte Dokument und schließt die Verbindung
  • der Client schließt seinerseits die Verbindung

Der Client kann unterschiedlicher Art sein: ein Text im Format HTML, ein Bild, ein Video, … Es kann sich um ein bereits vorhandenes Dokument (statisches Dokument) oder um ein Dokument handeln, das von einem Skript dynamisch generiert wird (dynamisches Dokument). Im letzteren Fall spricht man von Webprogrammierung. Das Skript zur dynamischen Dokumentgenerierung kann in verschiedenen Sprachen geschrieben sein: PHP, Python, Perl, Java, Ruby, C#, VB.net, …

Wir verwenden hier PHP, um Textdokumente dynamisch zu generieren.

  • In [1] stellt der Client eine Verbindung zum Server her, fordert ein Skript PHP an und sendet gegebenenfalls Parameter an dieses Skript
  • Bei „[2]“ lässt der Webserver das Skript „PHP“ vom Interpreter „PHP“ ausführen. Dieses Skript erzeugt ein Dokument, das an den Client „[3]“ gesendet wird
  • Der Server beendet die Verbindung. Der Client tut dasselbe.

Der Webserver kann mehrere Clients gleichzeitig bedienen. Mit dem Softwarepaket WampServer handelt es sich bei dem Webserver um einen Apache-Server, einen Open-Source-Server der Apache Foundation (http://www.apache.org/). In den folgenden Anwendungen muss WampServer gestartet werden. Dadurch werden drei Programme aktiviert: der Apache-Webserver, SGBD und MySQL sowie der Interpreter PHP.

Die vom Webserver ausgeführten Skripte werden mit dem Tool NetBeans geschrieben. Bisher haben wir PHP-Skripte geschrieben, die in einer Konsolenumgebung ausgeführt werden:

Der Benutzer nutzt die Konsole, um die Ausführung eines Skripts PHP anzufordern und die Ergebnisse zu erhalten.

In den folgenden Client-Server-Anwendungen

  • wird das Client-Skript in einem Konsolenkontext ausgeführt
  • wird das Serverskript in einem Webkontext ausgeführt

Das Serverskript PHP darf sich nicht an einem beliebigen Ort im Dateisystem befinden. Der Webserver sucht nämlich an den in der Konfiguration festgelegten Orten nach den angeforderten statischen und dynamischen Dokumenten. Die Standardkonfiguration von WampServer sieht vor, dass die Dokumente im Ordner <WampServer>/www gesucht werden, wobei <WampServer> der Installationsordner von WampServer ist. Wenn also ein Webclient ein Dokument D mit dem Namen URL [http://localhost/D] anfordert, liefert der Webserver ihm das Dokument D mit dem Pfad [<WampServer>/www/D].

In den folgenden Beispielen werden wir die Serverskripte im Ordner [www/exemples-web] ablegen. Wenn ein Serverskript den Namen S.php trägt, wird es über die Pfade URL und [http://localhost/exemples-web/S.php] vom Webserver angefordert. Daraufhin wird ihm das Dokument „[<WampServer>/www/exemples-web/S.php]“ bereitgestellt.

Um mit NetBeans ein Serverskript für zu erstellen, gehen wir wie folgt vor:

  • In [1] erstellen wir ein neues Projekt
  • In [2] wählen wir die Kategorie [PHP] und das Projekt [PHP Application]
  • in [3], wir geben dem Projekt einen Namen
  • Bei [4] wählen wir einen Ordner für das Projekt aus
  • In [5] geben wir an, dass das Skript von einem lokalen Webserver ausgeführt werden soll (die URL des Skripts lautet dann http://localhost/...). Der lokale Webserver ist der Apache-Webserver von WampServer.
  • In [6] geben wir die Projekt-ID URL an. Hier legen wir fest, dass ein Skript S.php des Projekts zusammen mit den Skripten URL und [http://localhost/exemples-web/S.php] aufgerufen wird. Aus dem Gesagten folgt, dass der Pfad des Skripts S.php im Dateisystem [<WampServer>/www/exemples-web/S.php] lautet. Dies ist in [7] angegeben. Wir fordern hier, dass jedes Skript S.php des Projekts in die Verzeichnisstruktur des Apache-Webservers kopiert wird.
  • in [8], dem neuen Projekt.

Schreiben wir ein Testskript:

  • in [1] erstellen wir im Projekt [exemples-web] ein erstes Skript PHP
  • in [2], wir geben ihm einen Namen
  • in [3], und nachdem wir es erstellt haben, fügen wir folgenden Inhalt ein

Im weiteren Verlauf muss WampServer gestartet werden.

  • in [4] führen wir das Webskript [exemple1.php] aus. NetBeans startet daraufhin den Standardbrowser des Computers und weist ihn an, die Dateien URL, [http://localhost/exemples-web/exemple1.php] und [5]
  • in [6] an, und der Browser zeigt dem Client an, was das Serverskript geschrieben hat.

Im weiteren Verlauf werden wir auf zwei Arten von Web-Clients stoßen:

  • einen Browser wie oben beschrieben. Wir haben geschrieben, dass der Webserver eine Antwort in folgender Form sendet: Header HTTP, Leerzeile, Text. Der Browser zeigt nur texte an.
  • ein Skript PHP, das ihm die gesamte Antwort anzeigt: Header HTTP, Leerzeile, Text.

Im Folgenden

  • werden die Serverskripte wie das oben genannte [exemple1.php] geschrieben
  • werden die Client-Skripte wie die Konsolenskripte geschrieben, die wir bisher erstellt haben.

10.1. Client-/Server-Anwendung für Datum und Uhrzeit

10.1.1. Der Server (web_01)


<?php
// Zeit: Anzahl der Millisekunden seit dem 01.01.1970
// Anzeigeformat für Datum und Uhrzeit
// d: Tag, zweistellig
// m: zweistelliger Monat
// y: zweistelliges Jahr
// H: Stunde 0,23
// i: Minuten
// s: Sekunden
print date("d/m/y H:i:s",time());

Im Grunde schreibt das oben stehende Skript PHP die aktuelle Uhrzeit auf den Bildschirm. Wenn es jedoch von einem Webserver ausgeführt wird, wird der Stream Nr. 1, der normalerweise mit dem Bildschirm verbunden ist, auf die Verbindung umgeleitet, die den Server mit seinem Client verbindet. In einem Webkontext sendet das obige Skript die aktuelle Uhrzeit also als Text an den Client.

Führen wir dieses Skript in NetBeans aus:

  • In [1] wird das Skript ausgeführt. Daraufhin wird ein Webbrowser gestartet.
  • In [2] wird die vom Webbrowser angeforderte Datei URL
  • in [3], der vom Server-Skript gesendete Text

Der Client-Browser nutzt das Protokoll HTTP für die Kommunikation mit dem Webserver. Wir haben die Struktur dieses Protokolls bereits beschrieben.

Der Client sendet Textzeilen, die sich in drei Teile untergliedern lassen: HTTP-Header, Leerzeile, Dokument. Das an den Webserver gesendete Dokument ist meist leer oder besteht aus einer Reihe von Parametern parami=vali, wobei vali ein Wert ist, den der Benutzer in ein HTML-Formular eingegeben hat.

Die Antwort des Servers hat dieselbe Form: Header HTTP, leere Zeile, Dokument, wobei document diesmal das vom Client-Browser angeforderte Dokument ist. Hat der Client Parameter übermittelt, hängt das gelieferte Dokument in der Regel von diesen Parametern ab.

Mit dem Firefox-Browser lässt sich der tatsächliche Datenaustausch zwischen Client und Webserver einsehen. Es gibt ein Plugin namens Firebug, mit dem sich dieser Datenaustausch nachverfolgen lässt. Firebug ist unter URL [https://addons.mozilla.org/fr/firefox/addon/firebug/] verfügbar. Wenn man den Browser Firefox verwendet, um diese URL aufzurufen, kann man das Plugin Firebug herunterladen. Im Folgenden gehen wir davon aus, dass das Firebug-Plugin heruntergeladen und installiert wurde. Es ist über eine Option im Firefox-Menü verfügbar:

 

Im Firefox-Browserfenster öffnet sich ein Firebug-Fenster. Dieses Fenster verfügt seinerseits über ein Menü:

 

Um den Datenaustausch zwischen Client und Server bei einer Anfrage HTTP zu untersuchen, rufen wir die Seite URL [http://localhost/exemples-web/web_01.php] mit dem Firefox-Browser auf. Das Firebug-Fenster füllt sich daraufhin mit Informationen:

Oben sehen wir eine Zusammenfassung des Client-Server-Datenaustauschs:

  • [1]: Der Client hat den Befehl gesendet HTTP: GET /exemples-web/web_01.php HTTP/1.1, um das Dokument [web01.php] anzufordern
  • [2]: Der Server hat die Antwort gesendet: HTTP/1.1 200 OK, was darauf hinweist, dass er das angeforderte Dokument gefunden hat.

Mit Firebug lassen sich die vollständigen Datenaustausche einsehen. Dazu muss man lediglich das URL „aufklappen“:

Oben sehen wir die HTTP-Header, die zwischen dem Client (Anfrage) und dem Server (Antwort) ausgetauscht wurden. Es ist möglich, den Quellcode des Datenaustauschs abzurufen (c.a.d) sowie die tatsächlich ausgetauschten Textzeilen ([1]). Wir erhalten dann den folgenden Quellcode:

 

Um ein Client-Skript für den Webserver zu schreiben, müssen wir lediglich das Verhalten des Browsers nachbilden. Nach dem Aufbau einer Verbindung zum Server könnte das Client-Skript die 8 Zeilen der obigen Anfrage senden. Tatsächlich ist jedoch nicht alles erforderlich, und wir werden nur die folgenden drei Zeilen senden:

1
2
3
GET /exemples-web/web01.php HTTP/1.1
Host: localhost
Connection: close
  • Zeile 1: Gibt das angeforderte Dokument und das verwendete Protokoll HTTP an
  • Zeile 2: gibt den Namen des Rechners an, auf dem das Client-Skript läuft
  • Zeile 3: Gibt an, dass der Client nach dem Datenaustausch die Verbindung zum Server schließen wird

Wenden wir uns nun der Antwort des Servers zu. Wir wissen, dass sie vom Skript PHP [web_01.php] erzeugt wurde. Oben sehen wir die Header HTTP der Antwort. Der Code des Skripts [web01.php] zeigt, dass es diese nicht generiert hat. Erinnern wir uns an die Konfiguration des Serverskripts:

Es war der Webserver, der die HTTP-Header der Antwort generiert hat. Das Serverskript kann diese selbst generieren. Ein Beispiel dafür sehen wir uns etwas später an.

Wir haben gesagt, dass die Antwort des Webservers folgende Form hat: Header HTTP, Leerzeile, Dokument. Handelt es sich bei dem Dokument um ein Textdokument, kann man dieses im Reiter [Réponse] von Firebug sehen:

 

Diese Antwort wurde vom Skript [web_01.php] generiert.

10.1.2. Ein Client (client1_web_01)

Wir schreiben nun ein Client-Skript für den vorherigen Dienst. Wir wissen, dass der Client Folgendes tun muss:

  • eine Verbindung zum Webserver herstellen
  • den Text senden: Header HTTP, leere Zeile
  • die vollständige Antwort des Servers lesen, bis dieser die Verbindung zum Client schließt
  • die Verbindung zum Server schließen

Das Client-Skript wird in einer NetBeans-Konsolenumgebung ausgeführt:

  • In [1] ist das Client-Skript [client1_web_01.php] im NetBeans-Projekt [exemples] enthalten
  • in [2], die Projekteigenschaften des NetBeans-Projekts [exemples]
  • In [3] wird das NetBeans-Projekt [exemples] im „Befehlszeilenmodus“ ausgeführt, den wir auch als „Konsolenmodus“ bezeichnet haben.

Der Code des Client-Skripts lautet wie folgt:


<?php

// Daten
$HOTE = "localhost";
$PORT = 80;
$urlServeur = "/exemples-web/web_01.php";
// Verbindung zum Port 80 von $HOTE hergestellt
$connexion = fsockopen($HOTE, $PORT);
// Fehler?
if (!$connexion) {
  print "Erreur : $erreur\n";
  exit;
}
// Die Header des Protokolls HTTP müssen mit einer Leerzeile enden
// GET
fputs($connexion, "GET $urlServeur HTTP/1.1\n");
// Host
fputs($connexion, "Host: localhost\n");
// Verbindung
fputs($connexion,"Connection: close\n");
// Leerzeile
fputs($connexion,"\n");
// Der Server antwortet nun auf dem Kanal $connexion. Er sendet alle
// seine Daten senden und dann den Kanal schließen. Der Client liest also alles, was von $connexion
// bis zum Schließen des Kanals
while ($ligne = fgets($connexion, 1000)) {
  print "$ligne";
}//, während
// der Client seinerseits die Verbindung schließt
fclose($connexion);
// Ende
exit;

Kommentare

  • Zeile 8: Herstellen einer Verbindung zum Server
  • Zeile 16: Befehl HTTP GET
  • Zeile 18: Befehl HTTP Host
  • Zeile 20: Befehl HTTP Verbindung
  • Zeile 22: leere Zeile
  • Zeilen 26–28: Einlesen aller vom Server gesendeten Textzeilen, bis dieser die Verbindung schließt.
  • Zeile 30: Der Client schließt seinerseits die Verbindung

Ergebnisse

Die Ausführung des Client-Skripts liefert folgende Ergebnisse:

1
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
Date: Wed, 17 Aug 2011 13:35:00 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Content-Length: 17
Connection: close
Content-Type: text/html

17/08/11 13:35:00

Kommentare

  • Zeilen 1–7: Die Antwort „HTTP“ vom Webserver.
  • Zeile 8: Die Leerzeile, die das Ende der Header anzeigt: HTTP
  • Zeilen 9 und folgende: das Dokument. In diesem Fall handelt es sich um einen einfachen Text, der das aktuelle Datum und die aktuelle Uhrzeit angibt. Dies ist der Text, den das Skript PHP auf die Ausgabe Nr. 1 schreibt.
  • Zeile 1: Der Server antwortet, dass er das angeforderte Dokument gefunden hat.
  • Zeile 2: Aktuelles Datum und aktuelle Uhrzeit des Servers
  • Zeile 3: Kennung des Webservers
  • Zeile 4: Gibt an, dass das nachfolgende Dokument von einem Skript namens PHP generiert wurde
  • Zeile 5: Anzahl der Zeichen im Dokument
  • Zeile 6: Der Server gibt an, dass er nach dem Senden des Dokuments die Verbindung schließen wird
  • Zeile 7: Gibt an, dass das vom Server gesendete Dokument ein Text im Format HTML ist. Dies ist hier nicht korrekt. Das Dokument ist Text ohne spezielles Format. Wenn das Dokument kein Text im Format HTML ist, muss das Skript PHP dies angeben. Das haben wir hier nicht getan.

10.1.3. Ein zweiter Client (client2_web_01)

Der vorherige Client zeigte alles an, was ihm der Webserver übermittelte. In der Praxis ignoriert man in der Regel die HTTP-Header der Antwort und nutzt den Dokumentteil. Wir versuchen hier, das vom Server-Skript PHP übermittelte Datum und die Uhrzeit abzurufen. Wir werden diese Informationen mithilfe eines regulären Ausdrucks extrahieren.


<?php

// die von einem Webserver gesendeten Informationen abrufen
// Daten
$HOTE = "localhost";
$PORT = 80;
$urlServeur = "/exemples-web/web_01.php";
// Verbindung zum Port 80 von $HOTE herstellen
$connexion = fsockopen($HOTE, $PORT);
// Fehler?
if (!$connexion) {
  print "Erreur : $erreur\n";
  exit;
}
// Die Header des Protokolls HTTP müssen mit einer Leerzeile enden
// GET
fputs($connexion, "GET $urlServeur HTTP/1.1\n");
// Host
fputs($connexion, "Host: localhost\n");
// Verbindung
fputs($connexion,"Connection: close\n");
// Leerzeile
fputs($connexion,"\n");
// Der Server antwortet nun auf dem Kanal $connexion. Er sendet alle
// diese Daten senden und dann den Kanal schließen. Der Client liest also alles, was von $connexion
// und sucht dabei nach der gesuchten Zeile im Format tt/mm/jj hh:mm:ss
while ($ligne = fgets($connexion, 1000)) {
  print "$ligne";
  if (preg_match("/(\d\d)\/(\d\d)\/(\d\d) (\d\d):(\d\d):(\d\d)/", $ligne, $champs)) {
     // und ruft die # Felder ab
    array_shift($champs); // entfernt das erste Element aus dem Feld-Array
     // die 6 Felder werden in 6 Variablen gespeichert
    list($j, $m, $a, $h, $i, $s) = $champs;
     // Ergebnis anzeigen
    print "\ndateheure=[$j,$m,$a,$h,$i,$s]\n";
  }//if
}//while
// Der Client schließt seinerseits die Verbindung
fclose($connexion);
// Ende
exit;

Ergebnisse

HTTP/1.1 200 OK
Date: Wed, 17 Aug 2011 14:14:51 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Content-Length: 17
Connection: close
Content-Type: text/html

17/08/11 14:14:51
dateheure=[17,08,11,14,14,51]

10.2. Abruf der vom Client gesendeten Parameter durch den Server

Im Protokoll HTTP stehen einem Client zwei Methoden zur Verfügung, um Parameter an den Webserver zu übergeben:

  • Er fordert den Dienst URL in der Form

GET url?param1=val1&param2=val2&param3=val3… HTTP/1.0

wobei die Werte vali zuvor kodiert werden müssen, damit bestimmte reservierte Zeichen durch ihren Hexadezimalwert ersetzt werden.

  • Er fordert den Dienst URL in der Form

POST url HTTP/1.0

und fügt dann unter den an den Server gesendeten HTTP-Header den folgenden Header ein:

Content-length=N

Die weiteren vom Client gesendeten Header enden mit einer Leerzeile. Anschließend kann er seine Daten in der Form

val1&param2=val2&param3=val3…

wobei die Werte vali, wie bei der Methode GET, zuvor kodiert werden müssen. Die Anzahl der an den Server gesendeten Zeichen muss N betragen, wobei N der im Header

Content-length=N

Das Skript PHP, das die zuvor vom Client gesendeten Parameter parami abruft, entnimmt deren Werte aus dem Array:

  • $_GET["parami"] für einen Befehl GET
  • $_POST["parami"] für einen Befehl POST

10.2.1. Der Kunde GET (client1_web_02)

Das unten stehende Skript PHP sendet drei Parameter [nom, prenom, age] an den Server.


<?php

// Client: Sendet Vorname, Nachname und Alter über die Methode GET an den Server
// Daten
$HOTE = "localhost";
$PORT = 80;
$URL = "/exemples-web/web_02.php";
list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
// Verbindung zum Webserver
$connexion = fsockopen($HOTE, $PORT);
// Rückgabe bei Fehler
if (!$connexion) {
  print "Echec de la connexion au site ($HOTE,$PORT) : $erreur";
  exit;
}//if
// Senden der Informationen an den Server PHP
// die Informationen werden verschlüsselt
$infos = "prenom=" . urlencode(utf8_decode($prenom)) . "&nom=" . urlencode(utf8_decode($nom)) . "&age=" . urlencode("$age");
// Konsole überwachen
print "infos envoyées au serveur (GET)=$infos\n";
print "URL demandée=[$URL?$infos]\n\n";
// Die Header des Protokolls HTTP müssen mit einer Leerzeile enden
// GET
fputs($connexion, "GET $URL?$infos HTTP/1.1\n");
// Host
fputs($connexion, "Host: localhost\n");
// Verbindung
fputs($connexion,"Connection: close\n");
// Leerzeile
fputs($connexion,"\n");
// Der Server antwortet nun auf dem Kanal $connexion. Er sendet alle
// seine Daten und schließt dann den Kanal. Der Client liest alles, was von $connexion eintrifft, bis der Kanal geschlossen wird
while ($ligne = fgets($connexion, 1000))
  print "$ligne";
// Der Client schließt seinerseits die Verbindung
fclose($connexion);

Kommentare

  • Zeile 7: URL des Serverskripts
  • Zeile 8: Die Werte der 3 Parameter
  • Zeile 10: Aufbau einer Verbindung zum Webserver
  • Zeile 18: Kodierung der drei Parameter. Wir befinden uns in einem unter NetBeans geschriebenen Skript mit einer Zeichenkodierung in UTF-8. Daher werden die drei Parameterwerte aus Zeile 8 in UTF-8 kodiert. Die Funktion utf8_decode wandelt ihre Kodierung in ISO-8859-1 um. Anschließend können sie für URL kodiert werden. Alle nicht-alphabetischen Zeichen werden durch %xx ersetzt, wobei xx der Hexadezimalwert des Zeichens ist. Leerzeichen werden durch das Pluszeichen (+) ersetzt.
  • Zeile 24: Das angeforderte URL lautet $URL?$infos, wobei $infos die Form nom=val1&prenom=val2&age=val3 hat.

10.2.2. Der Server (web_02)

Der Server gibt lediglich das wieder, was er empfängt.


<?php

// Fehlerbehandlung
ini_set("display_errors", "off");

// Abruf der vom Client gesendeten Informationen durch den Server
// hier: Vorname=P&Nachname=N&Alter=A
// Diese Informationen stehen automatisch in den Variablen zur Verfügung
// $_GET['prenom'], $_GET['nom'], $_GET['age']
// sie werden an den Client zurückgesendet

// Header UTF-8
header("Content-Type: text/plain; charset=utf-8");

// an den Server gesendete Parameter
$prenom = isset($_GET['prenom']) ? $_GET['prenom'] : "";
$nom = isset($_GET['nom']) ? $_GET['nom'] : "";
$age = isset($_GET['age']) ? $_GET['age'] : "";

// Antwort an den Kunden
$réponse = "informations reçues du client [" .
        utf8_encode(htmlspecialchars($prenom, ENT_QUOTES)) .
        "," . utf8_encode(htmlspecialchars($nom, ENT_QUOTES)) .
        "," . utf8_encode(htmlspecialchars($age, ENT_QUOTES)) . "]\n";
print $réponse;

Anmerkungen

  • Zeile 13: Legt den Header „Content-Type“ von HTTP fest. Standardmäßig sendet der Webserver den Header
Content-Type: text/html

, der angibt, dass die Antwort Text im Format HTML ist. Hier wird die Antwort unformatierter Text mit Zeichen sein, die im Format UTF-8 kodiert sind:

Content-Type: text/plain; charset=utf-8

Die Header HTTP müssen vor der Antwort des Servers gesendet werden. Daher steht im obigen Beispiel der Aufruf der Funktion header vor jeder Anweisung print.

  • Zeilen 16–18: Die drei Parameter werden aus dem Array $_GET abgerufen.
  • Zeile 21: Hier wird die Zeichenkette zusammengestellt, die als Antwort an den Client gesendet wird. Bestimmte Zeichen haben in HTML eine besondere Bedeutung und müssen durch HTML-Entitäten ersetzt werden, damit sie angezeigt werden können. htmlspecialchars($string) ersetzt alle diese Zeichen durch ihr Äquivalent in der Zeichenkette $string. Beispielsweise wird das Zeichen $ zu &amp. Da wir in Zeile 13 festgelegt haben, dass die Antwort aus dem Text UTF-8 bestehen soll, kodieren wir die abgerufenen Werte in UTF-8.
  • Zeile 25: Die Antwort wird an den Client gesendet

Test 1

Führen wir das Skript [web_02] in NetBeans aus. Daraufhin wird ein Browser gestartet, um URL und [http://localhost/exemples-web/web_02.php] anzuzeigen:

  • In [1] zeigt der Browser die URL [http://localhost/exemples-web/web_02.php] an. Da wir dieser URL keine Parameter angehängt haben, hat der Server mit leeren Parametern geantwortet. Zur Erinnerung: Die Antwort des Servers ist diejenige, die mit der Anweisung print geschrieben wurde.
  • Bei [2] fügen wir an URL Parameter an. Diesmal gibt das Serverskript diese korrekt zurück.

Beachten Sie, dass NetBeans für die Ausführung eines Serverskripts nicht erforderlich ist. Es reicht aus, die URL des Serverskripts in einen Browser einzugeben, damit dieses Skript ausgeführt wird.

Test 2

Wir führen in NetBeans den Client „[client1_web_02.php]“ aus. Wir erhalten folgende Antwort:

infos envoyées au serveur (GET)=prenom=jean-paul&nom=de+la+h%FBche&age=45
URL demandée=[/exemples-web/web_02.php?prenom=jean-paul&nom=de+la+h%FBche&age=45]

HTTP/1.1 200 OK
Date: Wed, 17 Aug 2011 14:31:01 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Content-Length: 59
Connection: close
Content-Type: text/plain; charset=utf-8

informations reçues du client [jean-paul,de la hûche,45]
  • Zeile 1: Kodierung der 3 Parameter. Man sieht, dass das Zeichen „û“ zu „%FB“ geworden ist.
  • Zeile 12: Die Antwort des Servers

10.2.3. Der Client POST (client2_web_03)

Ein HTTP-Client sendet die folgende Textsequenz an den Webserver: Header HTTP, leere Zeile, Dokument. Im vorherigen Client lautete diese Sequenz wie folgt:

GET /url?paramètres HTTP/1.1
… autres entêtes HTTP
ligne vide

Es gab kein Dokument. Es gibt eine weitere Möglichkeit, Parameter zu übermitteln, die sogenannte Methode POST. In diesem Fall lautet die an den Webserver gesendete Textsequenz wie folgt:

POST /url HTTP/1.1
… autres entêtes HTTP
ligne vide
paramètres

Während beim Client GET die Parameter in den Headern HTTP enthalten waren, sind sie beim Client POST Teil des Dokuments, das hinter den Headern gesendet wird.

Das Skript des Mandanten POST lautet wie folgt:


<?php

// Client: Sendet Vorname, Nachname und Alter über die Methode POST an den Server
// Daten
$HOTE = "localhost";
$PORT = 80;
$URL = "/exemples-web/web_03.php";
list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
// Verbindung zum Webserver
$connexion = fsockopen($HOTE, $PORT);
// Rückgabe bei Fehler
if (!$connexion) {
  print "Echec de la connexion au site ($HOTE,$PORT) : $erreur";
  exit;
}//if
// Senden der Informationen an den Server PHP
// die Informationen werden verschlüsselt
$infos = "prenom=" . urlencode(utf8_decode($prenom)) . "&nom=" . urlencode(utf8_decode($nom)) . "&age=" . urlencode("$age");
print "client : infos envoyées au serveur (POST) : $infos\n";
// Verbindung zum URL $URL herstellen, indem Parameter (POST) an ihn gesendet werden
// Die Header des Protokolls HTTP müssen mit einer Leerzeile enden
// POST
fputs($connexion, "POST $URL HTTP/1.1\n");
// Host
fputs($connexion, "Host: localhost\n");
// Verbindung
fputs($connexion,"Connection: close\n");
// Content-Type
fputs($connexion, "Content-type: application/x-www-form-urlencoded\n");
// Content-Length
// Es wird die Größe (Anzahl der Zeichen) der zu sendenden Informationen übermittelt
fputs($connexion, "Content-length: " . strlen($infos) . "\n");
// Es wird eine leere Zeile gesendet
fputs($connexion, "\n");
// Die Informationen werden gesendet
fputs($connexion, $infos);
// Der Server antwortet nun auf dem Kanal $connexion. Er sendet alle
// seine Daten senden und dann den Kanal schließen. Der Client liest alles, was von $connexion
// bis zum Schließen des Kanals
while ($ligne = fgets($connexion, 1000))
  print "$ligne";
// schließt der Client seinerseits die Verbindung
fclose($connexion);

Kommentare

  • Zeile 7: Der Webdienst URL, mit dem sich der Client POST verbinden wird. Dieser Webdienst wird im Folgenden beschrieben.
  • Zeile 8: Die an den Webdienst zu übermittelnden Parameter
  • Zeile 10: Verbindung zum Webserver
  • Zeile 18: Kodierung der an den Webdienst zu sendenden Parameter
  • Zeile 23: HTTP-Befehl POST
  • Zeile 25: HTTP-Befehl „Host“
  • Zeile 27: HTTP-Befehl „Connection“
  • Zeile 29: HTTP-Befehl „Content-type“. Diesen Header HTTP haben wir bereits kennengelernt. Er ist immer dann vorhanden, wenn ein Dokument gesendet wird. Ein Webserver, der ein HTML-Dokument sendet, verwendet den Header HTTP
Content-type : text/html

Wenn er unformatierten Text sendet, verwendet er den Header HTTP

Content-type : text/plain

Unser Client POST sendet ein Dokument, bei dem es sich um Text in der Form param1=val1&param2=val2&... handelt. Dieser Dokumenttyp hat den Typ „application/x-www-form-urlencoded“. Wir werden nicht näher darauf eingehen, warum das so ist, da wir sonst erklären müssten, was ein Webformular ist.

  • Zeile 32: Befehl „Content-Length“. Wir sind diesem Header HTTP bereits begegnet. Er ist jedes Mal vorhanden, wenn ein Dokument gesendet wird. Er gibt die Anzahl der Bytes des Dokuments an.
  • Zeile 34: Die leere Zeile, die das Ende der Header signalisiert HTTP
  • Zeile 36: Das Senden der Parameter
  • Zeilen 40–41: Einlesen der vollständigen Antwort des Servers
  • Zeile 43: Schließen der Verbindung

10.2.4. Der Server (web_03)

Der Webdienst [web_03] führt dieselben Schritte aus wie der Webdienst [web_02]. Er liest die vom Client POST gesendeten Parameter und sendet sie an den Client zurück. Sein Code lautet wie folgt:


<?php

// Fehlerbehandlung
ini_set("display_errors", "off");
// Header UTF-8
header("Content-Type: text/plain; charset=utf-8");

// Abruf der vom Client gesendeten Informationen durch den Server
// hier Vorname=P&Nachname=N&Alter=A
// Diese Informationen stehen automatisch in den Variablen zur Verfügung
// $_POST['prenom'], $_POST['nom'], $_POST['age']
// und werden an den Client zurückgesendet
// an den Server gesendete Parameter
$prenom = isset($_POST['prenom']) ? $_POST['prenom'] : "";
$nom = isset($_POST['nom']) ? $_POST['nom'] : "";
$age = isset($_POST['age']) ? $_POST['age'] : "";
// Antwort an den Client
$réponse = "informations reçues du client [" .
        utf8_encode(htmlspecialchars($prenom, ENT_QUOTES)) .
        "," . utf8_encode(htmlspecialchars($nom, ENT_QUOTES)) .
        "," . utf8_encode(htmlspecialchars($age, ENT_QUOTES)) . "]\n";
print $réponse;

Kommentare

  • Zeilen 14–16: Die von einem Client POST gesendeten Parameter stehen im Array $_POST für den Webdienst zur Verfügung, der sie empfängt.
  • Zeile 6: Header HTTP Content-Type. Es mag überraschen, dass in den Headern HTTP der Header HTTP Content-Length fehlt, der die Größe des an den Client zurückgesendeten Dokuments angibt. Wir haben gesehen, dass der Webserver standardmäßig HTTP-Header sendet. Der Header Content-Length gehört dazu.

Ergebnisse

Sobald das Serverskript in NetBeans geschrieben wurde, ist es über den Apache-Server unter der Adresse WampServer sofort verfügbar. Zur Erinnerung: Dies wird durch die Konfiguration erreicht (siehe Abschnitt 10). Wir starten den Client, der den Server abfragt, und erhalten daraufhin die folgende Antwort:

client : infos envoyées au serveur (POST) : prenom=jean-paul&nom=de+la+h%FBche&age=45
HTTP/1.1 200 OK
Date: Sat, 20 Aug 2011 12:55:19 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Content-Length: 59
Connection: close
Content-Type: text/plain; charset=utf-8

informations reçues du client [jean-paul,de la hûche,45]
  • Zeilen 2–10: Die Antwort des Servers
  • Zeilen 2–8: die HTTP-Header
  • Zeile 10: das Dokument
  • Zeile 6: Der Header HTTP Content-Length. Da dieser Header nicht vom Serverskript generiert wurde, stammt er vom Webserver.
  • Zeile 8: Der einzige vom Serverskript generierte Header

10.3. Abruf der Umgebungsvariablen des Servers WEB

Ein Serverskript wird in einer Webumgebung ausgeführt, die ihm bekannt sein kann. Diese Umgebung ist im Wörterbuch $_SERVER gespeichert. Zunächst schreiben wir eine Serveranwendung, die den Inhalt dieses Wörterbuchs an ihre Clients sendet.

10.3.1. Der Server (web_04)


<?php

// Fehlerbehandlung
ini_set("display_errors", "off");
// Header UTF-8
header("Content-Type: text/plain; charset=utf-8");

// Die Liste der in der Serverumgebung verfügbaren Variablen wird an den Client zurückgesendet
foreach ($_SERVER as $clé => $valeur) {
  print "[$clé,$valeur]\n";
}
  • Die (Schlüssel, Wert)-Paare des Wörterbuchs $_SERVER werden an die Clients gesendet.

Das Ergebnis, wenn der Client ein Webbrowser ist, sieht wie folgt aus:

Image

Hier die Bedeutung einiger Variablen (für Windows; unter Linux wären sie anders):

HTTP_CMDE
CMDE steht für den vom Client gesendeten Header HTTP. Auf alle diese Header kann zugegriffen werden.
PATH
Der Pfad zu den ausführbaren Dateien auf dem Rechner, auf dem das Server-Skript ausgeführt wird
COMSPEC
der Pfad zum DOS-Befehlsinterpreter
PATHEXT
die Dateiendungen der ausführbaren Dateien
WINDIR
das Windows-Installationsverzeichnis
SERVER_SIGNATURE
Die Signatur des Webservers. Hier nichts.
SERVER_SOFTWARE
Der Typ des Webservers
SERVER_NAME 
Der Internetname des Webserver-Rechners
SERVER_PORT
der Listening-Port des Webservers
SERVER_ADDR
die Adresse IP des Webserver-Rechners
REMOTE_ADDR
die Adresse IP des Clients. In diesem Fall befand sich der Client auf demselben Rechner wie der Server.
REMOTE_PORT
der Kommunikationsport des Clients
DOCUMENT_ROOT
das Stammverzeichnis der vom Webserver bereitgestellten Dokumente
SERVER_ADMIN
die E-Mail-Adresse des Webserver-Administrators
SCRIPT_FILENAME
der vollständige Pfad zum Server-Skript
SERVER_PROTOCOL
die vom Webserver verwendete Version des Protokolls HTTP
REQUEST_METHOD
Der vom Client verwendete HTTP-Befehl. Es gibt vier davon: GET, POST, PUT, DELETE
QUERY_STRING
Die mit einem Befehl GET gesendeten Parameter: /url?parameter
REQUEST_URI
die vom Client angeforderte URL. Wenn der Browser die URL http://machine[:port]/uri anfordert, ergibt sich REQUEST_URI=uri
SCRIPT_NAME
$_SERVER['SCRIPT_FILENAME']=$_SERVER['DOCUMENT_ROOT'].$_SERVER['SCRIPT_NAME']

10.3.2. Der Client (client1_web_04)

Der Client gibt einfach alles an, was ihm der Server sendet.


<?php

// Daten
$HOTE = "localhost";
$PORT = 80;
$urlServeur = "/exemples-web/web_04.php";
// Aufbau einer Verbindung auf Port 80 von $HOTE
$connexion = fsockopen($HOTE, $PORT);
// Fehler?
if (!$connexion) {
  print "Erreur : $erreur\n";
  exit;
}

// Verbindung zum Webserver URL wird hergestellt
// Die Header des Protokolls HTTP müssen mit einer Leerzeile enden
// GET
fputs($connexion, "GET $urlServeur HTTP/1.1\n");
// Host
fputs($connexion, "Host: localhost\n");
// Verbindung
fputs($connexion,"Connection: close\n");
// Leerzeile
fputs($connexion,"\n");
// Der Server antwortet nun auf dem Kanal $connexion. Er sendet alle
// seine Daten senden und dann den Kanal schließen. Der Client liest also alles, was von $connexion
// bis zum Schließen des Kanals
while ($ligne = fgets($connexion, 1000)) {
  print "$ligne";
}//während
// der Client seinerseits die Verbindung schließt
fclose($connexion);
// Ende
exit;

Ergebnisse

HTTP/1.1 200 OK
Date: Sat, 20 Aug 2011 14:11:58 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Content-Length: 1353
Connection: close
Content-Type: text/plain; charset=utf-8

[HTTP_HOST,localhost]
[HTTP_CONNECTION,close]
[PATH,C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files\Microsoft SQL Server\90\Tools\binn\;...;]
[SystemRoot,C:\Windows]
[COMSPEC,C:\Windows\system32\cmd.exe]
[PATHEXT,.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC]
[WINDIR,C:\Windows]
[SERVER_SIGNATURE,]
[SERVER_SOFTWARE,Apache/2.2.17 (Win32) PHP/5.3.5]
[SERVER_NAME,localhost]
[SERVER_ADDR,127.0.0.1]
[SERVER_PORT,80]
[REMOTE_ADDR,127.0.0.1]
[DOCUMENT_ROOT,C:/serveursSGBD/wamp21/www/]
[SERVER_ADMIN,admin@localhost]
[SCRIPT_FILENAME,C:/serveursSGBD/wamp21/www/exemples-web/web_04.php]
[REMOTE_PORT,54552]
[GATEWAY_INTERFACE,CGI/1.1]
[SERVER_PROTOCOL,HTTP/1.1]
[REQUEST_METHOD,GET]
[QUERY_STRING,]
[REQUEST_URI,/exemples-web/web_04.php]
[SCRIPT_NAME,/exemples-web/web_04.php]
[PHP_SELF,/exemples-web/web_04.php]
[REQUEST_TIME,1313849518]

10.4. Sitzungsverwaltung WEB

In den vorangegangenen Client-Server-Beispielen funktionierte das wie folgt:

  • Der Client öffnet eine Verbindung zum Port 80 des Webserver-Rechners
  • er sendet die Textsequenz: HTTP-Header, Leerzeile, [document]
  • Als Antwort sendet der Server eine Sequenz desselben Typs
  • der Server beendet die Verbindung zum Client
  • Der Client schließt die Verbindung zum Server

Wenn derselbe Client kurz darauf eine neue Anfrage an den Webserver stellt, wird eine neue Verbindung zwischen dem Client und dem Server hergestellt. Der Server kann nicht erkennen, ob der sich verbindende Client bereits zuvor da war oder ob es sich um eine erste Anfrage handelt. Zwischen zwei Verbindungen „vergisst“ der Server seinen Client. Aus diesem Grund wird das Protokoll HTTP als zustandsloses Protokoll bezeichnet. Es ist jedoch sinnvoll, dass sich der Server an seine Clients erinnert. Wenn eine Anwendung gesichert ist, sendet der Client dem Server beispielsweise einen Benutzernamen und ein Passwort, um sich zu identifizieren. Wenn der Server seinen Client zwischen zwei Verbindungen „vergisst“, müsste sich dieser bei jeder neuen Verbindung erneut identifizieren, was nicht praktikabel ist.

Um einen Client nachzuverfolgen, geht der Server wie folgt vor: Bei einer ersten Anfrage eines Clients fügt er seiner Antwort eine Kennung hinzu, die der Client ihm anschließend bei jeder neuen Anfrage zurücksenden muss. Dank dieser Kennung, die für jeden Client unterschiedlich ist, kann der Server einen Client erkennen. Er kann dann einen Speicher für diesen Client in Form einer Datei verwalten, die eindeutig mit der Kennung des Clients verknüpft ist.

Technisch läuft das so ab:

  • In der Antwort an einen neuen Client fügt der Server den Header „HTTP Set-Cookie: MotClé=Identifikator“ ein. Dies geschieht nur bei der ersten Anfrage.
  • Bei seinen folgenden Anfragen sendet der Client seine Kennung über den Header „HTTP“ mit dem Cookie „MotClé=Kennung“ zurück, damit der Server ihn wiedererkennt.

Man könnte sich fragen, wie der Server erkennen kann, dass es sich um einen neuen Kunden handelt und nicht um einen bereits bekannten. Dies wird ihm durch das Vorhandensein des Headers HTTP Cookie in den HTTP-Headern des Kunden angezeigt. Bei einem neuen Kunden fehlt dieser Header.

Die Gesamtheit der Verbindungen eines bestimmten Kunden wird als Sitzung bezeichnet.

10.4.1. Die Konfigurationsdatei

Damit die Sitzungsverwaltung mit PHP ordnungsgemäß funktioniert, muss überprüft werden, ob das Programm korrekt konfiguriert ist. Unter Windows lautet der Name der Konfigurationsdatei PHP.ini. Je nach Ausführungskontext (Konsole, Web) muss die Konfigurationsdatei [PHP.ini] in unterschiedlichen Ordnern gesucht werden. Um diese zu ermitteln, wird das folgende Skript verwendet:

1
2
3
4
<?php

// Informationen PHP
phpinfo();

In Zeile 4 liefert die Funktion „phpinfo“ Informationen über den Interpreter „PHP“, der das Skript ausführt. Sie gibt insbesondere den Pfad zur verwendeten Konfigurationsdatei „[PHP.ini]“ an.

In einer Konsolenumgebung erhält man ein Ergebnis, das in etwa wie folgt aussieht:

1
2
3
4
5
6
Configuration File (PHP.ini) Path => C:\Windows
Loaded Configuration File => C:\serveursSGBD\wamp21\bin\PHP\php5.3.5\PHP.ini
Scan this dir for additional .ini files => (none)
Additional .ini files parsed => (none)

Zeile 2: Die Hauptkonfigurationsdatei ist c:\windows\PHP.ini

Zeile 3: Eine sekundäre Konfigurationsdatei ist C:\serveursSGBD\wamp21\bin\PHP\php5.3.5\PHP.ini. Damit lassen sich bestimmte Konfigurationsoptionen der Hauptkonfigurationsdatei ändern.

In einer Webumgebung erhält man folgendes Ergebnis:

Image

Die sekundäre Konfigurationsdatei ist hier nicht dieselbe wie in der Konsolenumgebung. Wir werden uns letztere ansehen. In dieser Datei findet sich ein Abschnitt „session“:

[Session]
session.save_handler = files
session.save_path = "C:/serveursSGBD/wamp21/tmp"
session.use_cookies = 1
session.use_only_cookies = 1
session.name = PHPSESSID
session.auto_start = 0
session.cookie_lifetime = 0
session.cookie_path = /
session.cookie_domain =
session.serialize_handler = PHP
session.cache_expire = 180
session.hash_function = 0
  • Zeile 1: Die Daten einer Client-Sitzung werden in einer Datei gespeichert
  • Zeile 3: Der Ordner, in dem die Sitzungsdaten gespeichert werden. Wenn dieser Ordner nicht existiert, wird kein Fehler gemeldet, und die Sitzungsverwaltung funktioniert nicht.
  • Zeilen 4–5: Geben an, dass die Sitzungs-ID über die HTTP-Header „Set-Cookie“ und „Cookie“ verwaltet wird
  • Zeile 6: Der Header Set-Cookie hat das Format „Set-Cookie: PHPSESSID=identifiant_de_session“
  • Zeile 7: Eine Client-Sitzung wird nicht automatisch gestartet. Das Serverskript muss sie explizit mit dem Befehl session_start() anfordern.

10.4.2. Server 1 (web_05)

Die Verwaltung der Sitzungs-ID ist für einen Webdienst transparent. Diese ID wird vom Webserver verwaltet. Ein Webdienst hat über die Anweisung session_start() Zugriff auf die Client-Sitzung. Ab diesem Zeitpunkt kann der Webdienst über das Wörterbuch $_SESSION Daten in der Client-Sitzung lesen bzw. schreiben. Der folgende Code zeigt die Verwaltung von drei Zählern innerhalb einer Sitzung.


<?php

// Fehlerbehandlung
ini_set("display_errors", "off");
// Kopfzeile UTF-8
header("Content-Type: text/plain; charset=utf-8");

// Eine Sitzung wird eröffnet
session_start();
// 3 Variablen werden in die Sitzung übernommen
if (!isset($_SESSION['N1'])) {
  $_SESSION['N1'] = 0;
}
if (!isset($_SESSION['N2'])) {
  $_SESSION['N2'] = 10;
}
if (!isset($_SESSION['N3'])) {
  $_SESSION['N3'] = 100;
}
// Inkrementierung der 3 Variablen
$_SESSION['N1']++;
$_SESSION['N2']++;
$_SESSION['N3']++;
// Informationen werden an den Client gesendet
print "N1=".$_SESSION['N1']."\n";
print "N2=".$_SESSION['N2']."\n";
print "N3=".$_SESSION['N3']."\n";
// Ende der Sitzung
session_close();
  • Zeile 9: Start einer Kundensitzung
  • Zeilen 11–13: Das Array $_SESSION ist ein Schlüssel-Wert-Dictionary. Die in diesem Dictionary gespeicherten Daten bleiben über mehrere Anfragen desselben Kunden hinweg erhalten. Es handelt sich um den Kundenspeicher auf dem Server.
  • Zeilen 11–19: Wenn die drei Zähler N1, N2 und N3 nicht in der Sitzung enthalten sind, werden sie dort hinzugefügt.
  • Zeilen 21–23: Sie werden um eins erhöht.
  • Zeilen 25–27: Ihr Wert wird an den Client gesendet

In der Client-Server-Beziehung hängt die Verwaltung der Client-Sitzung auf dem Server von beiden Akteuren ab, dem Client und dem Server:

  • Der Server ist dafür zuständig, seinem Client bei dessen erster Anfrage eine Kennung zu senden
  • Der Client ist dafür verantwortlich, diese Kennung bei jeder neuen Anfrage zurückzusenden. Tut er dies nicht, geht der Server davon aus, dass es sich um einen neuen Client handelt, und generiert eine neue Kennung für eine neue Sitzung.

Ergebnisse

Als Client verwenden wir einen Webbrowser. Standardmäßig (bzw. aufgrund der Konfiguration) sendet dieser die vom Server übermittelten Sitzungs-IDs ordnungsgemäß an den Server zurück. Im Laufe der Anfragen empfängt der Browser die drei vom Server gesendeten Zähler und beobachtet, wie sich deren Werte erhöhen.

  • zu [1], die erste Anfrage an den Webdienst [web_05]
  • zu [2]; die dritte Anfrage zeigt, dass die Zähler tatsächlich inkrementiert wurden. Die Werte der Zähler werden im Verlauf der Anfragen tatsächlich gespeichert.

Verwenden wir Firebug, um die zwischen Server und Client ausgetauschten HTTP-Header anzuzeigen. Wir schließen Firefox, um die aktuelle Sitzung mit dem Server zu beenden, öffnen ihn erneut und aktivieren Firebug. Wir rufen den Dienst [web_05] auf:

Oben sehen wir die Sitzungs-ID, die der Server in seiner Antwort auf die erste Anfrage des Clients gesendet hat. Er verwendet den Header HTTP Set-Cookie.

Führen wir eine neue Anfrage durch, indem wir die Seite im Webbrowser aktualisieren (F5):

Hier fallen zwei Dinge auf:

  • Der Webbrowser sendet die Sitzungs-ID mit dem Header HTTP Cookie zurück.
  • In seiner Antwort enthält der Webdienst diese ID nicht mehr. Es ist nun Aufgabe des Clients, sie bei jeder seiner Anfragen zu übermitteln.

10.4.3. Client 1 (client1_web_05)

Wir schreiben nun ein Client-Skript auf der Grundlage des vorherigen Server-Skripts. Bei der Verwaltung der Sitzung muss es sich wie ein Webbrowser verhalten:

  • In der Antwort des Servers auf seine erste Anfrage muss er die Sitzungs-ID finden, die der Server ihm übermittelt. Er weiß, dass er sie im Header „HTTP Set-Cookie“ finden wird.
  • Bei jeder seiner nachfolgenden Anfragen muss er die erhaltene Kennung an den Server zurücksenden. Dies geschieht mit dem Header HTTP Cookie.

Der Code des Kunden lautet wie folgt:


<?php

// Daten
$HOTE = "localhost";
$PORT = 80;
$urlServeur = "/exemples-web/web_05.php";
// Tests
$cookie = "";
for ($i = 0; $i < 5; $i++) {
  list($erreur, $cookie, $N1, $N2, $N3) = connecte($HOTE, $PORT, $urlServeur, $cookie);
  print "----------------------------\n";
  print "client(erreur,cookie,N1,N2,N3)=[$erreur,$cookie,$N1,$N2,$N3]\n";
  print "----------------------------\n";
}
// Ende
exit;

function connecte($HOTE, $PORT, $urlServeur, $cookie) {
  // verbindet den Kunden mit ($HOTE,$PORT,$urlServeur)
  // sendet das Cookie $cookie, sofern dieses nicht leer ist
  // zeigt alle als Antwort empfangenen Zeilen an
  // Aufbau einer Verbindung zum Port 80 von $HOTE
  $connexion = fsockopen($HOTE, $PORT);
  // Fehler?
  if (!$connexion)
    return array("erreur lors de la connexion au serveur ($HOTE, $PORT)");
  // Verbindung zu $urlserveur wird hergestellt
  // Die Header des Protokolls HTTP müssen mit einer Leerzeile enden
  // GET
  fputs($connexion, "GET $urlServeur HTTP/1.1\n");
  // Host
  fputs($connexion, "Host: localhost\n");
  // Verbindung
  fputs($connexion, "Connection: close\n");
  // Das Cookie wird gesendet, wenn es nicht leer ist
  if ($cookie) {
    fputs($connexion, "Cookie: $cookie\n");
  }//if
  // leere Zeile senden
  fputs($connexion, "\n");
  // Die Antwort des Webservers wird angezeigt
  // und man achtet darauf, das eventuelle Cookie und die Ni-Werte abzurufen
  $N = "";
  while ($ligne = fgets($connexion, 1000)) {
    print "$ligne";
    // Cookie – nur bei der ersten Antwort
    if (!$cookie) {
      if (preg_match("/^Set-Cookie: (.*?)\s*$/", $ligne, $champs)) {
        $cookie = $champs[1];
      }
    }
    // Wert von N1
    if (preg_match("/^N1=(.*?)\s*$/", $ligne, $champs))
      $N1 = $champs[1];
    // Wert von N2
    if (preg_match("/^N2=(.*?)\s*$/", $ligne, $champs))
      $N2 = $champs[1];
    // Wert von N3
    if (preg_match("/^N3=(.*?)\s*$/", $ligne, $champs))
      $N3 = $champs[1];
  }//während
  // die Verbindung geschlossen wird
  fclose($connexion);
  // Zurück
  return array("", $cookie, $N1, $N2, $N3);
}

Kommentare

  • Zeilen 3–16: Das Hauptprogramm
  • Zeilen 18–67: die Funktion connecte
  • Zeilen 9–14: Der Client ruft den Server fünfmal auf und zeigt die aufeinanderfolgenden Werte der Zähler N1, N2 und N3 an. Bei korrekter Verwaltung der Sitzung sollten diese Zähler bei jeder neuen Anfrage um 1 erhöht werden.
  • Zeile 10: Die Funktion connecte verwendet die Parameter $HOTE, $PORT und $urlServeur, um den Client mit dem Webdienst zu verbinden. Der Parameter $cookie stellt die Sitzungs-ID dar. Beim ersten Aufruf ist dies die leere Zeichenkette. Bei den folgenden Aufrufen ist es die Sitzungs-ID, die vom Server als Antwort auf den ersten Aufruf des Clients gesendet wurde. Die Funktion connecte gibt als Ergebnisse die Werte der drei Zähler $N1, $N2, $N3, die Sitzungs-ID $cookie sowie einen eventuellen Fehler $erreur zurück.
  • Zeile 18: Die Funktion connecte weist die Merkmale eines klassischen HTTP-Clients auf. Wir gehen hier nur auf die Neuerungen ein.
  • Zeilen 30–40: Senden der HTTP-Header.
  • Zeilen 36–38: Ist die Sitzungs-ID bekannt, wird sie an den Server gesendet.
  • Zeilen 44–66: Auswertung aller vom Server gesendeten Textzeilen
  • Zeilen 47–51: Wenn die Sitzungs-ID noch nicht abgerufen wurde, wird sie aus dem Header HTTP Set-Cookie mittels eines regulären Ausdrucks extrahiert.
  • Zeilen 53–54: Der Zähler N1 wird ebenfalls mithilfe eines regulären Ausdrucks ermittelt
  • Zeilen 56–57, 59–60: Gleiches gilt für die Zähler N2 und N3
  • Zeile 63: Beenden der Verbindung zum Server.
  • Zeile 65: Rückgabe der Ergebnisse in Form eines Arrays.

Ergebnisse

Die Ausführung des Client-Skripts führt zur folgenden Anzeige in der NetBeans-Konsole:

HTTP/1.1 200 OK
Date: Sun, 21 Aug 2011 13:59:17 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Set-Cookie: PHPSESSID=ohiqtkv7hu2b26kdshjtqms9p7; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 18
Connection: close
Content-Type: text/plain; charset=utf-8

N1=1
N2=11
N3=101
----------------------------
client(erreur,cookie,N1,N2,N3)=[,PHPSESSID=ohiqtkv7hu2b26kdshjtqms9p7; path=/,1,11,101]
----------------------------
HTTP/1.1 200 OK
Date: Sun, 21 Aug 2011 13:59:18 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 18
Connection: close
Content-Type: text/plain; charset=utf-8

N1=2
N2=12
N3=102
----------------------------
client(erreur,cookie,N1,N2,N3)=[,PHPSESSID=ohiqtkv7hu2b26kdshjtqms9p7; path=/,2,12,102]
----------------------------
HTTP/1.1 200 OK
Date: Sun, 21 Aug 2011 13:59:18 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 18
Connection: close
Content-Type: text/plain; charset=utf-8

N1=3
N2=13
N3=103
----------------------------
client(erreur,cookie,N1,N2,N3)=[,PHPSESSID=ohiqtkv7hu2b26kdshjtqms9p7; path=/,3,13,103]
----------------------------
HTTP/1.1 200 OK
Date: Sun, 21 Aug 2011 13:59:18 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 18
Connection: close
Content-Type: text/plain; charset=utf-8

N1=4
N2=14
N3=104
----------------------------
client(erreur,cookie,N1,N2,N3)=[,PHPSESSID=ohiqtkv7hu2b26kdshjtqms9p7; path=/,4,14,104]
----------------------------
HTTP/1.1 200 OK
Date: Sun, 21 Aug 2011 13:59:18 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 18
Connection: close
Content-Type: text/plain; charset=utf-8

N1=5
N2=15
N3=105
----------------------------
client(erreur,cookie,N1,N2,N3)=[,PHPSESSID=ohiqtkv7hu2b26kdshjtqms9p7; path=/,5,15,105]
  • Zeile 5: In seiner ersten Antwort sendet der Server die Sitzungs-ID. In den folgenden Antworten sendet er diese nicht mehr.
  • Es ist deutlich zu erkennen, dass der Webserver die Werte (N1, N2, N3) über mehrere Client-Anfragen hinweg beibehält. Dies wird als Sitzungsverfolgung bezeichnet.

Die beiden folgenden Beispiele zeigen, dass man auch die Werte eines Arrays oder eines Objekts speichern kann.

10.4.4. Server 2 (web_06)

Das folgende Serverskript zeigt, dass man ein Array oder ein Dictionary in die Sitzung aufnehmen kann.


<?php

// Fehlerbehandlung
ini_set("display_errors", "off");
// Kopfzeile UTF-8
header("Content-Type: text/plain; charset=utf-8");

// Anmeldung
session_start();
// Ein Array und ein Wörterbuch werden gespeichert
// Das Array wird initialisiert oder geändert
if (isset($_SESSION['tableau'])) {
  for ($i = 0; $i < count($_SESSION['tableau']); $i++) {
    $_SESSION['tableau'][$i]++;
  }
} else {
  for ($i = 0; $i < 10; $i++) {
    $_SESSION['tableau'][$i] = $i * 10;
  }
}
// Das Wörterbuch wird initialisiert oder geändert
if (isset($_SESSION['dico'])) {
  foreach (array_keys($_SESSION['dico']) as $clé) {
    $_SESSION['dico'][$clé]++;
  }
} else {
  $_SESSION['dico'] = array("zéro" => 0, "dix" => 10, "vingt" => 20);
}
// Informationen werden an den Client gesendet
print "tableau=" . join(",", $_SESSION['tableau']) . "\n";
print "dico=";
foreach ($_SESSION['dico'] as $clé => $valeur) {
  print "($clé,$valeur) ";
}
print "\n";

Kommentare

  • Zeilen 17–19: Ein Array wird zunächst in die Sitzung aufgenommen, sofern es dort noch nicht vorhanden ist
  • Zeilen 12–15: Ist es bereits vorhanden, werden seine Elemente um 1 erhöht
  • Zeile 27: Ein Wörterbuch mit numerischen Werten wird in die Sitzung aufgenommen, sofern es noch nicht vorhanden ist
  • Zeilen 22–25: Ist es bereits vorhanden, werden seine numerischen Werte um 1 erhöht
  • Zeilen 30–35: Das Array und das Wörterbuch werden an den Client gesendet

10.4.5. Der Client 2 (client1_web_06)


<?php

// Daten
$HOTE = "localhost";
$PORT = 80;
$urlServeur = "/exemples-web/web_06.php";
// Tests
$cookie = "";
for ($i = 0; $i < 5; $i++) {
  connecte($HOTE, $PORT, $urlServeur, $cookie);
}
// Ende
exit;

function connecte($HOTE, $PORT, $urlServeur, &$cookie) {
  // verbindet den Client mit ($HOTE,$PORT,$urlServeur)
  // sendet das Cookie $cookie, sofern dieses nicht leer ist
  // zeigt alle als Antwort empfangenen Zeilen an
  // Das Cookie wird per Referenz übergeben, um zwischen
  // dem aufgerufenen und dem aufrufenden Programm gemeinsam genutzt zu werden
  // Öffnen einer Verbindung auf dem Port $PORT von $HOTE
  $connexion = fsockopen($HOTE, $PORT);
  // Fehler?
  if (!$connexion)
    return array("erreur lors de la connexion au serveur ($HOTE, $PORT)");
  // Die Header des Protokolls HTTP müssen mit einer Leerzeile enden
  // GET
  fputs($connexion, "GET $urlServeur HTTP/1.1\n");
  // Host
  fputs($connexion, "Host: localhost\n");
  // Verbindung
  fputs($connexion, "Connection: close\n");
  // Das Cookie wird gesendet, sofern es nicht leer ist
  if ($cookie) {
    fputs($connexion, "Cookie: $cookie\n");
  }
  // Leere Zeile senden
  fputs($connexion, "\n");
  // Die Antwort des Webservers wird angezeigt
  // und man achtet darauf, das eventuelle Cookie abzurufen
  while ($ligne = fgets($connexion, 1000)) {
    print "$ligne";
    // Cookie – nur bei der ersten Antwort
    if (!$cookie) {
      if (preg_match("/^Set-Cookie: (.*?)\s*$/", $ligne, $champs)) {
        $cookie = $champs[1];
      }
    }
  }
  // Die Verbindung wird geschlossen
  fclose($connexion);
  // Zurück
  return "";
}

Der Code des Mandanten entspricht dem bereits kommentierten Code.

Ergebnisse

HTTP/1.1 200 OK
...
Set-Cookie: PHPSESSID=6lvttr0uhpj5q3sl91h4h7p322; path=/
...

tableau=0,10,20,30,40,50,60,70,80,90
dico=(zéro,0) (dix,10) (vingt,20) 

HTTP/1.1 200 OK
...

tableau=1,11,21,31,41,51,61,71,81,91
dico=(zéro,1) (dix,11) (vingt,21) 

HTTP/1.1 200 OK
...

tableau=2,12,22,32,42,52,62,72,82,92
dico=(zéro,2) (dix,12) (vingt,22) 

HTTP/1.1 200 OK
...

tableau=3,13,23,33,43,53,63,73,83,93
dico=(zéro,3) (dix,13) (vingt,23) 

HTTP/1.1 200 OK
...

tableau=4,14,24,34,44,54,64,74,84,94
dico=(zéro,4) (dix,14) (vingt,24) 

10.4.6. Server 3 (web_07)

Das folgende Server-Skript zeigt, dass man ein Objekt in eine Sitzung aufnehmen kann.


<?php

// Fehlerbehandlung
ini_set("display_errors", "off");
// Header UTF-8
header("Content-Type: text/plain; charset=utf-8");

// Anmeldung
session_start();
// Ein „Person“-Objekt wird initialisiert oder geändert
if (isset($_SESSION['personne'])) {
  $personne = $_SESSION['personne'];
  // Das Alter wird erhöht
  $personne->setAge($personne->getAge() + 1);
} else {
  // Person wird definiert
  $_SESSION['personne'] = new Personne("paul", "langévin", 10);
}
// Anzeige für den Kunden
print "personne=".$_SESSION['personne']."\n";
// Ende
exit;

// ----------------------------------------------------------------
class Personne {

// Attribute der Klasse
  private $prénom;
  private $nom;
  private $âge;

// Getter und Setter
  public function getPrénom() {
    return $this->prénom;
  }

  public function getNom() {
    return $this->nom;
  }

  public function getAge() {
    return $this->âge;
  }

  public function setPrénom($prénom) {
    $this->prénom = $prénom;
  }

  public function setNom($nom) {
    $this->nom = $nom;
  }

  public function setAge($age) {
    $this->âge = $age;
  }

// Konstruktor
  function __construct($prénom, $nom, $âge) {
    // Wir gehen die Setter durch
    $this->setPrénom($prénom);
    $this->setNom($nom);
    $this->setAge($âge);
  }

// Methode toString
  function __toString() {
    return "[$this->prénom,$this->nom,$this->âge]";
  }

}

Kommentare

  • Zeile 17: Ein Objekt vom Typ Personne wird in die Sitzung aufgenommen, sofern es dort noch nicht vorhanden ist.
  • Zeilen 11–15: Ist es bereits vorhanden, wird sein Alter um 1 erhöht.
  • Zeile 20: Das Objekt vom Typ Personne wird an den Client gesendet.

10.4.7. Der Client 3 (client1_web_07)


<?php

// Daten
$HOTE = "localhost";
$PORT = 80;
$urlServeur = "/exemples-web/web_07.php";
// Tests
$cookie = "";
for ($i = 0; $i < 5; $i++) {
  connecte($HOTE, $PORT, $urlServeur, $cookie);
}//if
// Ende
exit;

function connecte($HOTE, $PORT, $urlServeur, &$cookie) {
...
}

Kommentare

  • Zeile 15: Die Funktion connecte ist identisch mit der des vorherigen Client-Skripts

Ergebnisse

HTTP/1.1 200 OK
...

personne=[paul,langévin,10]

HTTP/1.1 200 OK
...

personne=[paul,langévin,11]

HTTP/1.1 200 OK
...

personne=[paul,langévin,12]

HTTP/1.1 200 OK
...

personne=[paul,langévin,13]

HTTP/1.1 200 OK
...

personne=[paul,langévin,14]