Skip to content

17. Webdienste

Hinweis: Unter „Webdienst“ verstehen wir hier jede Webanwendung, die Rohdaten bereitstellt, die von einem Client genutzt werden – in den folgenden Beispielen ein Konsolenskript. Wir befassen uns nicht mit einer bestimmten Technologie – beispielsweise REST (REpresentational State Transfer) oder SOAP (Simple Object Access Protocol) –, die mehr oder weniger Rohdaten in einem genau definierten Format bereitstellt. REST gibt JSON zurück, während SOAP XML zurückgibt. Jede dieser Technologien legt genau fest, wie der Client den Server abfragen muss und welches Format die Antwort des Servers haben muss. In diesem Kurs werden wir hinsichtlich der Art der Client-Anfrage und der Server-Antwort wesentlich flexibler sein. Die geschriebenen Skripte und die verwendeten Tools ähneln jedoch denen der REST-Technologie.

17.1. Einführung

Da PHP-Programme von einem Webserver ausgeführt werden können, wird ein solches Programm zu einem serverseitigen Programm, das mehrere Clients bedienen kann. Aus Sicht des Clients läuft der Aufruf eines Webdienstes auf die Anforderung der URL dieses Dienstes hinaus. Der Client kann in jeder beliebigen Sprache geschrieben sein, einschließlich PHP. Im letzteren Fall nutzen wir die soeben behandelten Netzwerkfunktionen. Wir müssen auch wissen, wie man mit einem Webdienst „kommuniziert“, d. h. das HTTP-Protokoll für die Kommunikation zwischen einem Webserver und seinen Clients verstehen. Das war der Zweck des Abschnitts „Link“.

Der im Abschnitt „Link“ beschriebene Web-Client ermöglichte es uns, einen Teil des HTTP-Protokolls zu erkunden.

Image

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

  • Der Client öffnet eine Verbindung zu Port 80 auf dem Webserver;
  • er fordert ein Dokument an;
  • der Webserver sendet das angeforderte Dokument und schließt die Verbindung;
  • der Client schließt daraufhin die Verbindung;

Das Dokument kann verschiedene Formen annehmen: Text im HTML-Format, ein Bild, ein Video… Es kann sich um ein bereits vorhandenes Dokument (statisches Dokument) oder um ein Dokument handeln, das von einem Skript in Echtzeit generiert wird (dynamisches Dokument). Im letzteren Fall sprechen wir von Webprogrammierung. Das Skript zur dynamischen Generierung von Dokumenten kann in verschiedenen Sprachen geschrieben sein: PHP, Python, Perl, Java, Ruby, C#, VB.NET…

Im Folgenden werden wir PHP-Skripte verwenden, um Textdokumente dynamisch zu generieren.

Image

  • In [1] stellt der Client eine Verbindung zum Server her, fordert ein PHP-Skript an und sendet möglicherweise Parameter an dieses Skript;
  • In [2] führt der Webserver das PHP-Skript mithilfe des PHP-Interpreters aus. Das Skript generiert ein Dokument, das an den Client gesendet wird [3];
  • Der Server schließt die Verbindung. Der Client tut dasselbe;

Der Webserver kann mehrere Clients gleichzeitig bedienen.

Beim Softwarepaket [Laragon] 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 [Laragon] gestartet werden:

Image

Dadurch werden sowohl der Apache-Webserver als auch das MySQL-DBMS gestartet.

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:

Image

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

In den folgenden Client-Server-Anwendungen:

  • wird das Client-Skript in einer Konsolenumgebung ausgeführt;
  • wird das Server-Skript in einem Webkontext ausgeführt;

Image

Das serverseitige PHP-Skript kann nicht an einem beliebigen Ort im Dateisystem abgelegt werden. Tatsächlich sucht der Webserver die angeforderten statischen und dynamischen Dokumente an den in der Konfiguration festgelegten Speicherorten. Die Standardkonfiguration von Laragon bewirkt, dass Dokumente im Ordner <Laragon>/www gesucht werden, wobei <Laragon> der Installationsordner von Laragon unter ist. Wenn also ein Webclient ein Dokument D mit der URL [http://localhost/D] anfordert, liefert der Webserver das Dokument D, das sich unter dem Pfad [<Laragon>/www/D] befindet.

In den folgenden Beispielen werden wir die Server-Skripte im Ordner [www/php7/scripts-web] ablegen. Wenn ein Server-Skript den Namen S.php trägt, wird es über die URL [http://localhost/php7/scripts-web/S.php] vom Webserver angefordert. Das Dokument [<Laragon>/www/php7/scripts-web/S.php] wird dann bereitgestellt.

Image

  • in [1] der Ordner [<laragon>/www];
  • in [2] der Ordner [php7/scripts-web];

Um mit NetBeans Server-Skripte zu erstellen, gehen wir wie folgt vor:

Image

  • In [1-2] erstellen wir ein neues Projekt
  • In [3-4] wählen wir die Kategorie [PHP] und das Projekt [PHP-Anwendung] aus

Image

  • in [5] den Projektnamen;
  • in [6] den Projektordner im Dateisystem. Beachten Sie, dass sich dieser im Ordner [<laragon>/www] befindet, wo er hingehört;
  • In [7-8] übernehmen Sie die Standardwerte;
  • Übernehmen Sie in [9-10] die vorgegebenen Standardwerte. Beachten Sie in [10], dass die URL der Skripte, die wir in diesem Projekt ablegen werden, mit dem Pfad [http://localhost/php7/scripts-web/] beginnt;

Image

  • In [11] werden Ihnen in PHP geschriebene Web-Frameworks angeboten. Diese Frameworks sind unverzichtbar, sobald die Webanwendung an Umfang zunimmt;
  • In [12] können Sie PHP-Bibliotheken mit dem Tool [Composer] hinzufügen. Wir haben dieses Tool zweimal in einem Laragon-[Terminal]-Fenster verwendet:
    • um die [SwiftMailer]-Bibliothek zu installieren, mit der Sie E-Mails versenden können;
    • um die Bibliothek [php-mime-mail-parser] zu installieren, mit der Sie E-Mails lesen können;
  • In [13] erscheint das Projekt nach Bestätigung des Projekt-Assistenten in [13] auf der Registerkarte „Projekte“;

17.2. Eine statische Seite erstellen

Hinweis: Für den Rest dieser Anleitung muss [Laragon] ausgeführt werden.

Wir zeigen Ihnen, wie Sie mit NetBeans eine statische HTML-Seite (HyperText Markup Language) erstellen:

Image

  • In [1-5] erstellen wir einen Ordner mit dem Namen [01];

Image

Image

  • In [6-12] erstellen wir eine HTML-Datei mit dem Namen [example-01.html];

Die Datei [example-01.html] wird mit dem folgenden vorgefertigten Inhalt generiert (Mai 2019):


<!DOCTYPE html>
<!--
To change this license header, choose License Headers in Project Properties.
To change this template file, choose Tools | Templates
and open the template in the editor.
-->
<html>
    <head>
        <title>TODO supply a title</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <div>TODO write content</div>
    </body>
</html>

Aktualisieren wir den Inhalt wie folgt:


<!DOCTYPE html>
<html>
    <head>
        <title>PHP7 par l'exemple</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <div><b>Ceci est un exemple de page statique</b></div>
    </body>
</html>

Wir haben den Seitentitel (Zeile 4) und den Inhalt (Zeile 9) geändert.

Lassen wir nun den Apache-Server von Laragon diese HTML-Seite anzeigen:

Image

  • In [1-2] lassen wir den Laragon-Apache-Server die Seite anzeigen;
  • in [3] die URL der angezeigten Seite;
  • in [4] den von uns geänderten Titel;
  • in [5] den von uns geänderten Inhalt;

Die angezeigte Seite ist eine statische Seite: Sie können sie im Browser beliebig oft neu laden (F5), und es wird immer derselbe Inhalt angezeigt.

Die meisten Browser bieten Zugriff auf die zwischen Client und Server ausgetauschten Daten, wie im Abschnitt „Link“ beschrieben. In Firefox (Stand: Mai 2019) drücken Sie F12, um auf diese Daten zuzugreifen:

Image

Wie in [1] angegeben, laden wir die Seite neu (F5):

Image

  • in [2] das vom Browser geladene Dokument: Wir wählen es aus;

Image

  • in [5] ist das zu analysierende Dokument ausgewählt;
  • in [3-4] fordern wir die Anzeige des Client-Server-Austauschs an;
  • In [6] diese Datenaustausche;

Image

  • in [7] wählen Sie die Registerkarte „Header“ aus;
  • in [8] die vom Browser angeforderte URL;
  • in [9] lautet der an den Server gesendete Befehl [GET http://localhost/php7/scripts-web/01/exemple-01.html HTTP/1.1];
  • in [10] die anschließend vom Browser (dem Client) gesendeten HTTP-Header;
  • in [11] die HTTP-Header der Antwort des Servers;

Image

  • in [12–14] die nach den HTTP-Headern gesendete Antwort des Servers;
  • in [14] sehen wir, dass der Client-Browser die von uns erstellte HTML-Seite empfangen hat. Er hat diesen Code dann interpretiert, um Folgendes anzuzeigen:

Image

17.3. Erstellen einer dynamischen Seite in PHP

Wir werden nun eine dynamische Seite in PHP schreiben:

Image

Image

  • In [1-8] erstellen wir eine Seite [example-01.php];

Die Datei [example-01.php] wird wie folgt vorbelegt generiert (Mai 2019):


<!DOCTYPE html>
<!--
To change this license header, choose License Headers in Project Properties.
To change this template file, choose Tools | Templates
and open the template in the editor.
-->
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <?php
        // put your code here
        ?>
    </body>
</html>

Wir ändern den obigen Code wie folgt:


<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Exemple de page dynamique</title>
    </head>
    <body>
        <?php
        // time : nb de millisecondes entre le moment présent et le 01/01/1970
        // format affichage date-heure
        // d : jour sur 2 chiffres
        // m : mois sur 2 chiffres
        // y : année sur 2 chiffres
        // H : heure 0,23
        // I : minutes
        // s: secondes
        print "<b>Date et heure du jour : </b>" . date("d/m/y H:i:s", time());
        ?>
    </body>
</html>

Kommentare

  • Zeile 5: Wir haben den Seitentitel geändert;
  • Zeile 17: gibt das aktuelle Datum und die aktuelle Uhrzeit aus;

Im Grunde gibt das obige PHP-Skript die aktuelle Uhrzeit auf der Konsole aus. Bei der Ausführung durch einen Webserver wird die Ausgabe der [print]-Anweisung – die normalerweise an die Ausführungskonsole des Skripts gerichtet ist – hier jedoch an die Verbindung umgeleitet, die den Server mit seinem Client verbindet. Daher sendet das obige Skript im Webkontext die aktuelle Uhrzeit als Text an den Client, in diesem Fall einen Browser.

Führen wir das Skript [example-01.php] aus:

Image

  • in [3] die vom Apache-Webserver angeforderte URL;
  • in [4] der Seitentitel, den wir geändert haben;
  • in [5] der durch die Anweisung [print] generierte Inhalt;

Dies ist eine dynamische Seite, denn wenn Sie die Seite im Browser mehrmals neu laden (F5), ändert sich ihr Inhalt (die Uhrzeit ändert sich).

Der Browser hat einen HTML-Stream empfangen. Um ihn anzuzeigen, müssen Sie den Quellcode der Seite im Browser anzeigen:

Image

  • Um das Menü [1] aufzurufen, klicken Sie mit der rechten Maustaste auf die Seite im Browser;
  • in [2] die URL der Seite [example-01.php], jedoch mit dem Präfix [view-source:] [3];
  • in [4] den HTML-Inhalt, den der Browser angezeigt hat;

Es ist daher wichtig zu beachten, dass ein PHP-Skript, das von einem Webserver ausgeführt werden soll, einen HTML-Stream erzeugen muss.

Sehen wir uns nun (F12) die HTTP-Header an, die der Server an den Browser des Clients sendet:

Image

  • in [3] einen HTTP-Header, der bei der Anforderung der statischen Seite nicht vorhanden war. Dieser Header zeigt an, dass die Antwort des Servers von einem PHP-Skript generiert wurde;

Wir haben gesehen, dass die Antwort des Servers (hier die HTML-Ausgabe) durch ein PHP-Skript generiert werden kann. Das Skript kann auch die HTTP-Header und praktisch alle Elemente der Serverantwort generieren.

17.4. Grundlagen von HTML

Dieses Kapitel befasst sich nicht eingehend mit der Webprogrammierung in PHP. Eine MVC-Webanwendung wird im verlinkten Abschnitt entwickelt. Dieses Kapitel konzentriert sich stattdessen auf Webdienste: PHP-Seiten, die über einen Webserver Daten an andere PHP-Clients liefern. Dennoch hielten wir es für sinnvoll, dem Leser einige Grundlagen von HTML zu vermitteln.

Ein Webbrowser kann verschiedene Dokumente anzeigen, wobei HTML-Dokumente (HyperText Markup Language) am häufigsten sind. Diese bestehen aus Text, der mit Tags in der Form <tag>text</tag> formatiert ist. So wird der Text <b>important</b> den Text „important“ in Fettdruck anzeigen. Es gibt eigenständige Tags, wie beispielsweise das <hr/>-Tag, das eine horizontale Linie anzeigt. Wir werden nicht alle Tags durchgehen, die in einem HTML-Text vorkommen können. Es gibt viele WYSIWYG-Programme, mit denen Sie eine Webseite erstellen können, ohne eine einzige Zeile HTML-Code schreiben zu müssen. Diese Tools generieren automatisch den HTML-Code für ein Layout, das mit der Maus und vordefinierten Steuerelementen erstellt wurde. Sie können also (mit der Maus) eine Tabelle in die Seite einfügen und dann den von der Software generierten HTML-Code ansehen, um die Tags zu entdecken, die zur Definition einer Tabelle auf einer Webseite verwendet werden. So einfach ist das. Darüber hinaus sind HTML-Kenntnisse unerlässlich, da dynamische Webanwendungen den HTML-Code selbst generieren müssen, um ihn an Web-Clients zu senden. Dieser Code wird programmgesteuert generiert, und Sie müssen natürlich wissen, was zu generieren ist, damit der Client die gewünschte Webseite erhält.

Kurz gesagt: Sie müssen nicht die gesamte HTML-Sprache beherrschen, um mit der Webprogrammierung zu beginnen. Dieses Wissen ist jedoch notwendig und kann durch die Verwendung von WYSIWYG-Webseiten-Editoren wie DreamWeaver und Dutzenden anderen erworben werden. Eine weitere Möglichkeit, die Feinheiten von HTML zu entdecken, besteht darin, im Web zu surfen und den Quellcode von Seiten anzusehen, die interessante Elemente enthalten, auf die Sie noch nicht gestoßen sind.

Betrachten Sie das folgende Beispiel, das einige Elemente hervorhebt, die häufig in einem Webdokument vorkommen, wie zum Beispiel:

  • eine Tabelle;
  • ein Bild;
  • einem Link.

Image

Ein HTML-Dokument hat im Allgemeinen folgende Form:

<html> <head> <title>Ein Titel</title> ... </head> <body-Attribute> ... </body></html>

Das gesamte Dokument wird von den Tags <html>…</html> umschlossen. Es besteht aus zwei Teilen:

  1. <head>…</head>: Dies ist der nicht sichtbare Teil des Dokuments. Er liefert Informationen an den Browser, der das Dokument anzeigt. Er enthält häufig die Tags <title>…</title>, die den Text festlegen, der in der Titelleiste des Browsers angezeigt wird. Er kann auch andere Tags enthalten, insbesondere solche, die die Schlüsselwörter des Dokuments definieren, die dann von Suchmaschinen verwendet werden. Dieser Abschnitt kann auch Skripte enthalten, die in der Regel in JavaScript oder VBScript geschrieben sind und vom Browser ausgeführt werden.
  1. <body attributes>…</body>: Dies ist der Abschnitt, der vom Browser angezeigt wird. Die in diesem Abschnitt enthaltenen HTML-Tags teilen dem Browser das „gewünschte“ visuelle Layout des Dokuments mit. Jeder Browser interpretiert diese Tags auf seine eigene Weise. Daher kann es vorkommen, dass zwei Browser dasselbe Webdokument unterschiedlich darstellen. Dies ist im Allgemeinen eine der Herausforderungen, denen sich Webdesigner stellen müssen.

Der HTML-Code für unser Beispieldokument lautet wie folgt:


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>Quelques balises HTML</title>
    </head>
 
    <body style="background-image: url(images/standard.jpg)">
        <h1 style="text-align: left">Quelques balises HTML</h1>
        <hr />
 
        <table border="1">
            <thead>
                <tr>
                    <th>Colonne 1</th>
                    <th>Colonne 2</th>
                    <th>Colonne 3</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>cellule(1,1)</td>
                    <td style="text-align: center;">cellule(1,2)</td>
                    <td>cellule(1,3)</td>
                </tr>
                <tr>
                    <td>cellule(2,1)</td>
                    <td>cellule(2,2)</td>
                    <td>cellule(2,3</td>
                </tr>
            </tbody>
        </table>
        <br/><br/>
        <table border="0">
            <tr>
                <td>Une image</td>
                <td>
                    <img border="0" src="images/cerisier.jpg"/></td>
            </tr>
            <tr>
                <td>Le site de Polytech'Angers</td>
                <td><a href="http://www.polytech-angers.fr/fr/index.html">ici</a></td>
            </tr>
        </table>
    </body>
</html>
HTML
HTML-Tags und Beispiele
Dokumenttitel
<title>Einige HTML-Tags</title> (Zeile 5)
Der Text [Einige HTML-Tags] wird in der Titelleiste des Browsers angezeigt, wenn das Dokument angezeigt wird
Horizontale Linie
<hr />: zeigt eine horizontale Linie an (Zeile 10)
Tabelle
<table attributes>….</table>: zum Definieren der Tabelle (Zeilen 12, 32)
<thead>…</thead>: zum Definieren der Spaltenüberschriften (Zeilen 13, 19)
<tbody>…</tbody>: zum Definieren des Tabelleninhalts (Zeilen 20, 31)
<tr attributes>…</tr>: zum Definieren einer Zeile (Zeilen 21, 25)
<td attributes>…</td>: zum Definieren einer Zelle (Zeile 22)
Beispiele:
<table border="1">…</table>: Das Attribut „border“ definiert die Dicke des Tabellenrandes
<td style="text-align: center;">cell(1,2)</td> (Zeile 23): definiert eine Zelle, deren Inhalt cell(1,2) ist. Dieser Inhalt wird horizontal zentriert (text-align: center).
Bild
<img border="0" src="images/cherrytree.jpg"/> (Zeile 38): Definiert ein Bild ohne Rahmen (border="0"), dessen Quelldatei sich unter [images/cherrytree.jpg] auf dem Webserver befindet (src="images/cherrytree.jpg"). Dieser Link befindet sich in einem Webdokument, das über die URL http://localhost/php7/scripts-web/01/balises.html zugänglich ist. Daher fordert der Browser die URL http://localhost/php7/scripts-web/01/images/cerisier.jpg an, um das hier referenzierte Bild abzurufen.
Link
<a href="http://www.polytech-angers.fr/fr/index.html">here</a> (Zeile 42): Lässt den Text „here“ als Link zur URL http://www.polytech-angers.fr/fr/index.html fungieren.
Seitenhintergrund
<body style="background-image: url(images/standard.jpg)"> (Zeile 8): gibt an, dass sich das Bild, das als Seitenhintergrund verwendet werden soll, unter der URL [images/standard.jpg] auf dem Webserver befindet. In unserem Beispiel fordert der Browser die URL http://localhost/php7/scripts-web/01/images/standard.jpg an, um dieses Hintergrundbild abzurufen.

An diesem einfachen Beispiel sehen wir, dass der Browser zum Erstellen des gesamten Dokuments drei Anfragen an den Server senden muss:

  1. http://localhost/php7/scripts-web/01/images/balises.html, um den HTML-Quellcode des Dokuments abzurufen
  2. http://localhost/php7/scripts-web/01/images/cerisier.jpg, um das Bild cerisier.jpg abzurufen
  3. http://localhost/php7/scripts-web/01/images/standard.jpg, um das Hintergrundbild standard.jpg abzurufen

Dies wird durch den Netzwerkverkehr zwischen dem Client und dem Server angezeigt (F12 im Browser):

Image

  • In [3-5] sehen wir die drei vom Browser gesendeten Anfragen;

17.5. Eine statische Seite dynamisch gestalten

Zeigen wir, wie wir die HTML-Seite [example-01.html] dynamisch gestalten können. Kopieren Sie den Inhalt

Image

Wir haben den Inhalt von [example-01.html] in die Datei [page-01.php] kopiert. Wenn wir dieses Webskript [2] ausführen, sehen wir im Browser Folgendes:

Image

  • in [3] die angeforderte URL;
  • in [4] den Seitentitel;
  • in [5] den Seiteninhalt;

Wenn wir uns den vom Browser empfangenen Code ansehen, finden wir Folgendes:

Image

  • in [7], der HTML-Code, der im Skript [example-01.php] platziert wurde

Der PHP-Interpreter hat das Skript [page-01.php] interpretiert und dieselbe HTML-Ausgabe erzeugt wie die statische Seite [example-01.html]. Im Skript [page-01.php] befand sich kein PHP, sondern nur HTML. Daraus lernen wir Folgendes: Wenn der PHP-Interpreter HTML in einem PHP-Skript findet, lässt er es unverändert und sendet es so wie es ist an den Client.

Fügen wir nun einige PHP-Anweisungen zum Skript [page-01.php] hinzu, damit der PHP-Interpreter etwas zu tun hat:


<!DOCTYPE html>
<html>
    <head>
        <title><?php print $page->title ?></title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <div><b><?php print $page->contents ?></b></div>
    </body>
</html>

In den Zeilen 4 und 9 haben wir PHP-Code hinzugefügt, um den Seitentitel und den Inhalt dynamisch zu generieren. Hier gehen wir davon aus, dass die Variable [$page] ein Objekt ist, das die anzuzeigenden Daten enthält.

Wenn wir diesen neuen Code ausführen, erhalten wir im Browser folgendes Ergebnis:

Image

  • in [1] die angeforderte URL;
  • in [2] konnte der Seitentitel nicht angezeigt werden, da die Variable [$page] nicht definiert war;
  • in [3] gilt dasselbe für den Inhalt;

Schreiben wir nun das folgende Webskript [example-02.php]:

Image

Das Skript [example-02.php] sieht wie folgt aus:


<?php
 
// define the page elements to be displayed
$page=new \stdclass();
$page->title="Un nouveau titre";
$page->contents="Un nouveau contenu généré dynamiquement";
// display [page-01]
require_once "page-01.php";
  • Zeilen 4–6: Wir definieren das Objekt [$page];
  • Zeile 8: Das Skript [page-01.php] einbinden. Der Code in diesem Skript wird dann interpretiert:
    • Die Variable [$page] ist nun definiert und wird vom PHP-Interpreter verwendet;
    • der HTML-Code aus [page-01.php] wird unverändert an den Client gesendet;
    • die Ergebnisse der PHP-Operationen [print] werden in den an den Client gesendeten Textstrom aufgenommen;

Wenn wir nun das Webskript [example-02.php] ausführen, erhalten wir im Browser Folgendes:

Image

Wenn wir den vom Browser empfangenen Textinhalt anzeigen:

Image

  • Der PHP-Code, der sich in [2] und [3] befand, wurde durch die Ergebnisse der beiden [print]-Befehle ersetzt;

Aus diesem Beispiel lassen sich zwei wichtige Punkte ableiten:

  • HTML-Seiten, die für den Browser bestimmt sind, können in PHP-Skripten isoliert werden, die nur HTML-Code und einige wenige, durch PHP-Code generierte dynamische Teile enthalten. In diesen Seiten sollte so wenig PHP wie möglich enthalten sein;
  • Die gesamte Logik, die die in HTML-Seiten enthaltenen dynamischen Daten generiert, muss in reinen PHP-Skripten isoliert werden, die keinen Code zur Seitendarstellung (HTML, CSS, JavaScript usw.) enthalten;

Dies ermöglicht eine Aufgabentrennung:

  • die Aufgabe der Erstellung der anzuzeigenden Webseiten (HTML, CSS, JavaScript usw.);
  • die Aufgabe der Webanwendungslogik, die wir entwickeln. Diese Logik kann mithilfe einer dreischichtigen Architektur implementiert werden, genau wie wir es bei den Konsolenskripten getan haben;

Als Nächstes werden wir spezifische Webskripte erstellen;

  • diese senden lediglich Daten an den Client und keine Darstellungselemente (HTML, CSS, JavaScript). Sie sind daher eher Datenserver als Webseiten;
  • Die Clients für diese Webskripte sind Konsolenskripte, die die vom Server gesendeten Daten abrufen und verarbeiten;

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

Wir befinden uns nun in der folgenden Konfiguration:

Image

Wir schreiben:

  • ein Webskript [1], das das aktuelle Datum und die aktuelle Uhrzeit an seinen Client sendet;
  • ein Konsolenskript [2], das als Client für das Webskript fungiert: Es ruft das vom Webskript gesendete Datum und die Uhrzeit ab und zeigt sie auf der Konsole an;

Image

  • in [1] das Webskript [date-time-server.php];
  • in [2] das Konsolenskript [date-time-client], das als Client des Webskripts fungiert;

17.6.1. Das Server-Skript

Wir haben bereits ein Webskript geschrieben, das das aktuelle Datum und die aktuelle Uhrzeit generiert, wie im verlinkten Abschnitt beschrieben. Es handelte sich um das folgende Skript [example-01.php]:


<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Exemple de page dynamique</title>
    </head>
    <body>
        <?php
        // time : nb de millisecondes depuis 01/01/1970
        // format affichage date-heure
        // d: jour sur 2 chiffres
        // m: mois sur 2 chiffres
        // y : année sur 2 chiffres
        // H : heure 0,23
        // i : minutes
        // s: secondes
        print "<b>Date et heure du jour : </b>" . date("d/m/y H:i:s", time());
        ?>
    </body>
</html>

Wir hatten gesagt, dass wir Datenserver schreiben würden: Rohdaten ohne HTML-Markup. Das Server-Skript [date-time-server.php] sieht dann wie folgt aus:


<?php
 
// set header HTP [Content-Type]
header('Content-Type: text/plain; charset=UTF-8');
//
// send date and time
// time: number of milliseconds since 01/01/1970
// date-time display format
// d: 2-digit day
// m: 2-digit month
// y: 2-digit year
// H: hour 0.23
// i : minutes
// s: seconds
print date("d/m/y H:i:s", time());
  • Zeile 4: Wir setzen den HTTP-Header [Content-Type], der dem Client die Art des Dokuments mitteilt, das er erhalten wird. Bisher lautete der [Content-Type]: [Content-Type: text/html; charset=UTF-8]. Hier teilen wir dem Client mit, dass es sich bei dem Dokument um reinen Text ohne HTML-Markup handelt. Für unseren Konsolen-Client ist dies nicht wichtig, da er diesen Header nicht verwendet. Wichtiger ist dies für Browser-Clients, die diesen Header sehr wohl verwenden;

Führen wir dieses serverseitige Skript aus:

Image

Wenn wir die Antwort des Servers im Browser (F12) untersuchen, sehen wir in [5] den vom Serverskript gesetzten HTTP-Header und in [8] das empfangene Textdokument;

Image

17.6.2. Das clientseitige Skript

Im vorigen Abschnitt haben wir mehrere HTTP-Clients entwickelt. Wir könnten sie nutzen, um das vom Server-Skript [date-time-server.php] gesendete Textdokument abzurufen. Das werden wir jedoch nicht tun. Wie bereits bei den SMTP- und IMAP-Protokollen werden wir eine Bibliothek eines Drittanbieters verwenden, nämlich die [HttpClient]-Komponente des Symfony-Frameworks [https://symfony.com/doc/master/components/http_client.html].

Wie bei den beiden vorherigen Bibliotheken verwenden wir das [Composer]-Tool, um die Symfony-Komponente [HttpClient] zu installieren. Geben Sie in einem Laragon-[Terminal]-Fenster (siehe den verlinkten Abschnitt) den folgenden Befehl ein:

Image

  • Überprüfen Sie in [3], ob Sie sich im Verzeichnis [<laragon>/www/] befinden, wobei <laragon> das Laragon-Installationsverzeichnis ist;
  • in [4] den [composer]-Befehl, der die Symfony-Bibliothek [HttpClient] installiert;
  • in [5] wird nichts installiert, da die [HttpClient]-Bibliothek auf diesem Rechner bereits installiert war;
  • In [6-7] erscheinen neue Ordner in [<laragon>/www/vendor/symfony];

Anstelle von [5] sollte bei Ihnen etwa Folgendes stehen:


C:\myprograms\laragon-lite\www
? composer require symfony/http-client
Using version ^4.3 for symfony/http-client
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 4 installs, 0 updates, 0 removals
  - Installing symfony/polyfill-php73 (v1.11.0): Downloading (100%)
  - Installing symfony/http-client-contracts (v1.1.1): Downloading (100%)
  - Installing psr/log (1.1.0): Loading from cache
  - Installing symfony/http-client (v4.3.0): Downloading (100%)
Writing lock file
Generating autoload files

Stellen Sie sicher, dass der Ordner [<laragon>/www/vendor] in den [Include Path] Ihres Projekts aufgenommen wurde (siehe verlinkten Abschnitt):

Image

Sobald dies erledigt ist, können wir das Konsolenskript [date-time-client.php] schreiben:

Image

Das Konsolenskript [date-time-client.php] verwendet die folgende JSON-Datei [config-date-time-client.json]:

1
2
3
{
    "url": "http://localhost/php7/scripts-web/02/date-time-server.php"
}
  • Zeile 2: die URL des Server-Skripts;

Das Client-Skript [date-time-client.php] sieht wie folgt aus:


<?php
 
// service customer date / time
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
 
// customer configuration
const CONFIG_FILE_NAME = "config-date-time-client.json";
 
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}
 
// create a HTTP customer
$httpClient = HttpClient::create();
 
try {
  // query
  $response = $httpClient->request('GET', $config['url']);
  // answer status
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // we retrieve the headers
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // retrieve the body of the reply
  $content = $response->getContent();
  // we display it
  print "---Réponse du serveur : [$content]\n";
} catch (TypeError | RuntimeException $ex) {
  // error is displayed
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
  exit;
}

Kommentare

  • Zeile 10: Wie bei den vorherigen Bibliotheken laden wir die Datei [<laragon>/www/vendor/autoload.php];
  • Zeile 11: Wir deklarieren die Klasse [HttpClient], die wir verwenden werden;
  • Zeilen 13–24: Wir rufen die Konfiguration des Skripts aus dem Wörterbuch [$config] ab;
  • Zeile 27: Wir erstellen ein Objekt vom Typ [HttpClient];
  • Zeile 31: Wir fordern die URL des Server-Skripts mit einer GET-Anfrage an: [GET URL HTTP/1.1]. Dieser Vorgang ist asynchron. Die Ausführung wird in Zeile 33 fortgesetzt, ohne auf die Antwort zu warten;
  • Zeile 33: Der Antwortstatus wird abgerufen. Dieser Status ist im ersten vom Server zurückgegebenen HTTP-Header enthalten. Wenn dieser Header also [HTTP/1.1 200 OK] lautet, ist der Antwortstatus 200. Dieser Vorgang ist blockierend: Die Ausführung wird erst fortgesetzt, wenn der Client die gesamte Antwort vom Server erhalten hat;
  • Zeile 37: Die HTTP-Header der Antwort werden abgefragt;
  • Zeile 42: Wir rufen das vom Server zurückgegebene Dokument ab: Wir wissen, dass es sich bei diesem Dokument um Text handelt.
  • Zeilen 45–49: Tritt ein Fehler auf, wird die Fehlermeldung angezeigt;

Wenn das Client-Skript ausgeführt wird (Laragon muss laufen, damit das Server-Skript erreichbar ist), wird das folgende Ergebnis auf der Konsole angezeigt:


---Réponse avec statut : 200
---Entêtes de la réponse
date: Thu, 30 May 2019 14:42:03 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
content-length: 17
content-type: text/plain; charset=UTF-8
---Réponse du serveur : [30/05/19 14:42:03]

In Zeile 8 rufen wir erfolgreich das aktuelle Datum und die aktuelle Uhrzeit ab.

Vielleicht interessiert es Sie, was das Client-Skript an den Server gesendet hat. Dazu verwenden wir unseren generischen TCP-Server (siehe Abschnitt „Link“):

Image

  • in [1], dem Ordner „utilities“;
  • In [2] läuft der TCP-Server auf Port 100;
  • in [3] wartet er auf einen über die Tastatur eingegebenen Befehl;

Wir ändern die Konfigurationsdatei des Skripts [date-time-client.php]:


{
    "url": "http://localhost:100/php7/scripts-web/02/date-time-server.php"
}

Diesmal kontaktiert der Client den Server [localhost] auf Port 100. Daher wird unser generischer TCP-Server aufgerufen. Wenn wir das Konsolenskript [date-time-client.php] ausführen, ändert sich die Konsole des generischen TCP-Servers wie folgt:

Image

  • in [3] die vom Client-Skript erstellte HTTP-GET-Anfrage;
  • in [4] die Signatur des Konsolenskripts;
  • in [5] die Antwort des Servers an das Client-Skript. Beachten Sie, dass dies keine gültige HTTP-Antwort ist:
    • Es sollten HTTP-Header vorhanden sein;
    • gefolgt von einer Leerzeile;
    • dann das an den Client gesendete Textdokument;
  • in [6] schließen wir die Verbindung zum Client-Skript, damit dieses erkennt, dass es die gesamte Antwort erhalten hat;

Im clientseitigen Skript zeigt die Konsole Folgendes an:

Image

  • in [7], was der Symfony-Client empfangen hat;

17.6.3. Das Server-Skript – Version 2

Standardmäßig sind PHP-Funktionen zum Schreiben eines Webskripts nicht objektorientiert. Auf der Serverseite sind wir daher gezwungen, traditionelle PHP-Klassen und -Funktionen zu mischen. Um einen einheitlicheren Programmierstil zu erreichen, verwenden wir die [HttpFoundation]-Bibliothek aus dem Symfony-Framework. Sie kapseln alle traditionellen PHP-Funktionen für einen Webdienst in ein System aus Klassen und Schnittstellen. Die Dokumentation der Bibliothek ist unter [https://symfony.com/doc/current/components/http_foundation.html] (Mai 2019) verfügbar.

Um die Bibliothek zu installieren, führen Sie die folgenden Schritte in einem Laragon-Terminal aus (siehe verlinkten Abschnitt):

Image

  • [2-3]: Vergewissern Sie sich, dass Sie sich im Ordner [<laragon>/www] befinden;
  • [4]: Geben Sie den Befehl [composer] ein, um die Bibliothek [HttpFoundation] zu installieren;
  • [5]: In diesem Beispiel war die Bibliothek bereits installiert;

Bei der ersten Installation sollten Sie Konsolenprotokolle sehen, die in etwa wie folgt aussehen:


C:\myprograms\laragon-lite\www
? composer require symfony/http-foundation
Using version ^4.3 for symfony/http-foundation
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
  - Installing symfony/mime (v4.3.0): Downloading (100%)
  - Installing symfony/http-foundation (v4.3.0): Downloading (100%)
Writing lock file
Generating autoload files

Die zweite Version des Webservers [date-time-server-2.php] lautet wie folgt:


<?php
 
// using Symfony libraries
 
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpFoundation\Response;
 
// we set the Content-Type header
$response=new Response();
$response->headers->set("content-type","text/plain");
$response->setCharset("utf-8");
 
// set the content of the response
//
// send date and time
// time: number of milliseconds since 01/01/1970
// date-time display format
// d: 2-digit day
// m: 2-digit month
// y: 2-digit year
// H: hour 0.23
// i : minutes
// s: seconds
$response->setContent(date("d/m/y H:i:s", time()));
 
// we send the answer
$response->send();

Kommentare

  • Zeile 7: Die Klasse [Response] aus der Symfony-Bibliothek [HttpFoundation] verarbeitet die gesamte Antwort an die Clients des Webdienstes;
  • Zeile 10: Erstellung einer Instanz der Klasse [Response];
  • Zeile 11: gibt an, dass die Antwort vom Typ [text/plain] ist;
  • Zeile 12: Die Antwort ist UTF-8-Text;
  • Zeile 25: Der Antworttext wird auf den vom Client angeforderten Inhalt gesetzt;
  • Zeile 28: Die Antwort wird an den Client gesendet;

17.6.4. Das Client-Skript – Version 2

Das Client-Skript bleibt unverändert. Wir ändern lediglich seine Konfigurationsdatei [config-date-time-client.json]:

1
2
3
{
    "url": "http://localhost/php7/scripts-web/02/date-time-server-2.php"
}

Die Ergebnisse sind dieselben wie in Version 1.

17.7. Ein JSON-Datenserver

Die Antwort eines Webskripts kann aus mehreren Datenpunkten bestehen, die in Arrays und Objekten organisiert werden können. Das Skript kann diese verschiedenen Elemente dann in einer JSON-Zeichenkette senden, die der Client entschlüsselt.

Image

17.7.1. Das Server-Skript

Das Skript [json-server.php] verwendet die folgende Klasse [Person]:


<?php
 
namespace Modèles;
 
class Personne implements \JsonSerializable {
  // attributes
  private $nom;
  private $prénom;
  private $âge;
 
  // convert associative array to object [Person]
  public function setFromArray(array $assoc): Personne {
    // initialize the current object with the associative array
    foreach ($assoc as $attribute => $value) {
      $this->$attribute = $value;
    }
    // result
    return $this;
  }

  // getters and setters
  public function getNom() {
    return $this->nom;
  }
 
  public function getPrénom() {
    return $this->prénom;
  }
 
  public function setNom($nom) {
    $this->nom = $nom;
    return $this;
  }
 
  public function setPrénom($prénom) {
    $this->prénom = $prénom;
    return $this;
  }
 
  public function getÂge() {
    return $this->âge;
  }
 
  public function setÂge($âge) {
    $this->âge = $âge;
    return $this;
  }
 
  // toString
  public function __toString(): string {
    return "Personne [$this->prénom, $this->nom, $this->âge]";
  }
 
  // implements the JsonSerializable interface
  public function jsonSerialize(): array {
    // render an associative array with the object's attributes as keys
    // this table can then be encoded as jSON
    return get_object_vars($this);
  }
 
  // convert a jSON to a [Person] object
  public static function jsonUnserialize(string $json): Personne {
    // we create a person from the string jSON
    return (new Personne())->setFromArray(json_decode($json, true));
  }
 
}

Kommentare

  • Zeile 5: Die Klasse implementiert die PHP-Schnittstelle [JsonSerializable]. Dies erfordert die Implementierung der Methode [jsonSerialize] in den Zeilen 55–59. Die Methode muss ein assoziatives Array zurückgeben, das in JSON serialisiert wird. Bei Verwendung des Ausdrucks [json_encode($person)] prüft die Funktion [json_encode], ob die Klasse [Person] die Schnittstelle [JsonSerializable] implementiert. Ist dies der Fall, wird der Ausdruck zu [json_encode($person→serialize())];
  • Zeilen 12–19: Die Klasse verfügt über keinen Konstruktor, sondern über einen Initialisierer. Die Klasse [Person] kann dann mit dem Ausdruck [(new Person()) → setFromArray($array)] instanziiert werden. Es kann verschiedene Arten von Initialisierern geben, während es nur einen Konstruktor geben kann. Diese Initialisierer ermöglichen verschiedene Instanziierungsmodi der Form [(new Person())→initializer(…));
  • Zeilen 62–65: Die statische Funktion [jsonUnserialize] ermöglicht es Ihnen, ein [Person]-Objekt aus seiner JSON-Zeichenkette zu erstellen;

Das Skript [json-server.php] sieht wie folgt aus:


<?php
 
// dependencies
require_once __DIR__ . "/Personne.php";
use \Modèles\Personne;
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
 
// set the Content-Type header and the character library used
$response = new Response();
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
 
// create a Person object
$personne = (new Personne())->setFromArray([
  "nom" => "de la Hûche",
  "prénom" => "jean-paul",
  "âge" => 27]);
// an associative table
$assoc = ["attr1" => "value1",
  "attr2" => [
    "prenom" => "Jean-Paul",
    "nom" => "de la Hûche"
  ]
];
// the content of the response is jSON
$response->setContent(json_encode([$personne, $assoc]));
 
// reply sent
$response->send();

Kommentare

  • Zeilen 4–5: Importieren der Klasse [Person];
  • Zeile 11: Wir geben an, dass das Dokument vom Typ [application/json] sein wird. Nach Erhalt dieses Headers zeigen Browser die JSON-Zeichenkette formatiert anstelle von reinem Text an;
  • Zeile 12: Die JSON-Zeichenkette enthält UTF-8-Zeichen;
  • Zeilen 15–18: Wir erstellen ein [Person]-Objekt;
  • Zeilen 20–25: Es wird ein zweistufiges assoziatives Array erstellt;
  • Zeile 27: Wir senden die JSON-Zeichenkette eines Arrays an den Client:
    • Das Element [$person] wird mithilfe seiner Methode [jsonSerialize] in JSON serialisiert;
    • das Element [$assoc] wird nativ in JSON serialisiert;

Wenn wir dieses serverseitige Skript ausführen (Laragon muss laufen), erhalten wir im Browser folgende Antwort:

Image

Image

Kommentare

  • in [2] die formatierte JSON-Antwort;
  • in [4] die rohe JSON-Antwort. Beachten Sie die Kodierung von Zeichen mit Akzenten;
  • In [6] war es der vom Server gesendete Inhaltstyp [application/json], der den Browser dazu veranlasste, die Ausgabe auf diese Weise zu formatieren;

17.7.2. Der Client

Image

Der Client [json-client.php] wird durch die folgende JSON-Datei [config-json-client.json] konfiguriert:

1
2
3
{
    "url": "http://localhost/php7/scripts-web/03/json-server.php"
}

Das Skript [json-client.php] lautet wie folgt:


<?php
 
// service customer jSON
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
require_once __DIR__ . "/Personne.php";
use \Modèles\Personne;
 
// customer configuration
const CONFIG_FILE_NAME = "config-json-client.json";
 
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}
 
// create a HTTP customer
$httpClient = HttpClient::create();
 
try {
  // query
  $response = $httpClient->request('GET', $config['url']);
  // answer status
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // we retrieve the headers
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // retrieve the jSON body of the response
  list($personne, $assoc) = json_decode($response->getContent(), true);
  // a person is instantiated from an array of attributes
  $personne = (new Personne())->setFromArray($personne);
  // server response is displayed
  print "---Réponse du serveur\n";
  print "$personne\n";
  print "tableau=" . json_encode($assoc, JSON_UNESCAPED_UNICODE) . "\n";
} catch (TypeError | RuntimeException $ex) {
  // error is displayed
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}
 
 

Kommentare

  • Zeilen 12–13: Importiere die Klasse [Person];
  • Zeile 30: Erstellen des HTTP-Clients;
  • Zeile 44: Dekodierung der vom Server gesendeten JSON-Zeichenkette. Wir wissen, dass es sich bei dem kodierten Inhalt um ein Array mit zwei Elementen handelt, das zwei assoziative Arrays enthält;
  • Zeile 46: Erstellen eines [Person]-Objekts, um es in Zeile 49 anzuzeigen;
  • Zeile 50: Das zweite assoziative Array wird angezeigt. Die Anweisung [print] kann keine Arrays anzeigen. Daher wandeln wir dieses in eine JSON-Zeichenkette um. Um Zeichen mit Akzenten korrekt anzuzeigen, müssen wir den zweiten Parameter auf [JSON_UNESCAPED_UNICODE] setzen. Wir haben gesehen, dass die Zeichen mit Akzenten tatsächlich in der JSON-Zeichenkette kodiert sind;

Die Ausführung des clientseitigen Skripts liefert folgende Ergebnisse:


---Réponse avec statut : 200
---Entêtes de la réponse
date: Sun, 02 Jun 2019 09:56:29 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 143
connection: close
content-type: application/json
---Réponse du serveur
Personne [jean-paul, de la Hûche, 27]
tableau={"attr1":"value1","attr2":{"prenom":"Jean-Paul","nom":"de la Hûche"}}

Zeilen 11 und 12: Zeichen mit Akzenten wurden korrekt abgerufen.

17.8. Abrufen von Umgebungsvariablen des Webdienstes

Ein Server-Skript läuft in einer Webumgebung, auf die es zugreifen kann. Diese Umgebung ist im $_SERVER-Dictionary gespeichert, einer globalen PHP-Variablen. Wenn wir die [HttpFoundation]-Bibliothek verwenden, befindet sich diese Umgebung im Feld [Request→server], wobei [Request] die vom Webskript verarbeitete HTTP-Anfrage ist.

17.8.1. Das Server-Skript

Wir schreiben eine Serveranwendung, die ihre Ausführungsumgebung an ihre Clients sendet.

Image

Das Webskript [env-server.php] sieht wie folgt aus:


<?php
 
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
 
// we retrieve the request
$request = Request::createFromGlobals();
// we work out the answer
$response = new Response();
// response content is json utf-8
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
// set the jSON content of the response
$response->setContent(json_encode($request->server->all()));
// reply sent
$response->send();
  • Zeile 9: Wir rufen das [Request]-Objekt ab, das alle verfügbaren Informationen über die vom Webskript empfangene HTTP-Anfrage sowie deren Ausführungsumgebung enthält;
  • Zeilen 13–14: Wir senden Klartext mit UTF-8-Zeichen an den Client;
  • Zeile 16: Die an den Client gesendete Information ist eine Zeichenkette, die durch JSON-Serialisierung des Objekts [$request→server→all()] erhalten wird: [$request→server] repräsentiert die Ausführungsumgebung des Webskripts. Es handelt sich um ein Objekt vom Typ [ServerBag], eine Art Wörterbuch. [$request→server→all()] ist ein echtes Wörterbuch, das den Inhalt des [ServerBag] enthält;
  • Zeile 18: Die Informationen werden gesendet;

Wenn dieses Skript von NetBeans aus ausgeführt wird, zeigt der Browser die folgende Seite an:

Image

  • in [2] die verschiedenen Schlüssel des Umgebungswörterbuchs;
  • in [3] die Werte dieser Schlüssel;

17.8.2. Das Client-Skript

Image

Das Client-Skript [env-client.php] wird durch die folgende JSON-Datei [config-env-client.json] konfiguriert:

1
2
3
{
    "url": "http://localhost/php7/scripts-web/04/env-server.php"
}

Das Client-Skript [env-client.php] lautet wie folgt:


<?php
 
// server script environment
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
 
// customer configuration
const CONFIG_FILE_NAME = "config-env-client.json";
 
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}
 
// create a HTTP customer
$httpClient = HttpClient::create();
try {
  // make a request to the server
  $response = $httpClient->request('GET', $config['url']);
  // answer status
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // we retrieve the headers
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // server response is displayed
  print "---Réponse du serveur\n";
  $env = json_decode($response->getContent());
  foreach ($env as $key => $value) {
    print "[$key]=>$value\n";
  }
} catch (TypeError | RuntimeException $ex) {
  // error is displayed
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Kommentare

  • Zeile 42: Deserialisiere die JSON-Antwort vom Server. Dies gibt einen Hash zurück;
  • Zeilen 43–45: Alle Werte in diesem assoziativen Array anzeigen;

Es wird folgende Konsolenausgabe erhalten:


---Réponse avec statut : 200
---Entêtes de la réponse
date: Sun, 02 Jun 2019 17:35:50 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 1505
connection: close
content-type: application/json
---Réponse du serveur
[HTTP_HOST]=>localhost
[HTTP_USER_AGENT]=>Symfony HttpClient/Curl
[HTTP_ACCEPT_ENCODING]=>deflate, gzip
[PATH]=>C:\Program Files (x86)\Mail Enable\BIN;C:\windows\system32;C:\windows;C:\windows\System32\Wbem;C:\windows\System32\WindowsPowerShell\v1.0\;C:\windows\System32\OpenSSH\;C:\Program Files\dotnet\;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files (x86)\Mail Enable\BIN64;C:\Users\serge\AppData\Local\Microsoft\WindowsApps;;C:\myprograms\Microsoft VS Code\bin
[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.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
[SERVER_NAME]=>localhost
[SERVER_ADDR]=>::1
[SERVER_PORT]=>80
[REMOTE_ADDR]=>::1
[DOCUMENT_ROOT]=>C:/myprograms/laragon-lite/www
[REQUEST_SCHEME]=>http
[CONTEXT_PREFIX]=>
[CONTEXT_DOCUMENT_ROOT]=>C:/myprograms/laragon-lite/www
[SERVER_ADMIN]=>admin@example.com
[SCRIPT_FILENAME]=>C:/myprograms/laragon-lite/www/php7/scripts-web/04/env-server.php
[REMOTE_PORT]=>63744
[GATEWAY_INTERFACE]=>CGI/1.1
[SERVER_PROTOCOL]=>HTTP/1.1
[REQUEST_METHOD]=>GET
[QUERY_STRING]=>
[REQUEST_URI]=>/php7/scripts-web/04/env-server.php
[SCRIPT_NAME]=>/php7/scripts-web/04/env-server.php
[PHP_SELF]=>/php7/scripts-web/04/env-server.php
[REQUEST_TIME_FLOAT]=>1559496950.644
[REQUEST_TIME]=>1559496950

Hier ist die Bedeutung einiger der Variablen (für Windows. Unter Linux wären sie anders):

HTTP_HOST
Der Wert xxx des vom Client gesendeten HTTP-Headers [Host: xxx]
HTTP_USER_AGENT
der Wert xxx des vom Client gesendeten HTTP-Headers [User_Agent: xxx]
HTTP_ACCEPT_ENCODING
der Wert xxx des vom Client gesendeten HTTP-Headers [Accept-Encoding: xxx]
PATH
der Pfad zu den ausführbaren Dateien auf dem Rechner, auf dem das Server-Skript ausgeführt wird
COMSPEC
der Pfad der DOS-Eingabeaufforderung
PATHEXT
Erweiterungen ausführbarer Dateien
WINDIR
der Windows-Installationsordner
SERVER_SIGNATURE
die Webserver-Signatur. Hier steht nichts.
SERVER_SOFTWARE
der Typ des Webservers
SERVER_NAME
Der Internetname des Webserver-Rechners
SERVER_PORT
Der Listening-Port des Webservers
SERVER_ADDR
die IP-Adresse des Webserver-Rechners, hier 127.0.0.1
REMOTE_ADDR
die IP-Adresse 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
REQUEST_SCHEME
das TCP-Protokoll der URL-Anfrage (http://localhost/php7/ usw.)
SERVER_ADMIN
die E-Mail-Adresse des Webserver-Administrators
SCRIPT_FILENAME
der vollständige Pfad des Serverskripts
REMOTE_PORT
der Port, von dem aus der Client seine Anfrage gestellt hat
SERVER_PROTOCOL
die vom Webserver verwendete Version des HTTP-Protokolls
REQUEST_METHOD
die vom Client verwendete HTTP-Methode. Es gibt vier: GET, POST, PUT, DELETE
QUERY_STRING
die mit einer GET-Anfrage gesendeten Parameter /url?parameters
REQUEST_URI
Die vom Client angeforderte URL. Wenn der Browser die URL http://machine[:port]/uri anfordert, ist REQUEST_URI gleich uri
SCRIPT_NAME
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'].$_SERVER['SCRIPT_NAME']

17.9. Abruf der vom Client gesendeten Parameter durch den Server

17.9.1. Einführung

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

  • Er fordert die Service-URL in der Form

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

wobei die gültigen Werte zunächst kodiert werden müssen, sodass bestimmte reservierte Zeichen durch ihre Hexadezimalwerte ersetzt werden;

  • sie fordert die Service-URL in der Form

POST url HTTP/1.0

dann enthält die an den Server gesendete HTTP-Header-Reihe den folgenden Header:

Content-length=N

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

val1&param2=val2&param3=val3…

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

Content-length=N

Das PHP-Skript des Webdienstes, das die zuvor vom Client gesendeten „parami“-Parameter abruft, entnimmt deren Werte dem Array:

  • $_GET["parami"] für eine GET-Anfrage;
  • $_POST["parami"] für eine POST-Anfrage;

Dies gilt für grundlegende PHP-Funktionen. Wenn die [HttpFoundation]-Bibliothek verwendet wird, finden sich diese Parameter in:

  • [Request]->query->get('parami') bei einer GET-Anfrage;
  • [Request]->request->get('parami') für eine POST-Anfrage;

wobei [Request] alle Informationen über die vom Webskript empfangene Anfrage enthält;

17.9.2. Der GET-Client – Version 1

Image

Client-Skripte werden mithilfe der folgenden JSON-Datei [config-parameters-client.json] konfiguriert:

1
2
3
4
{
    "url-get": "http://localhost/php7/scripts-web/05/parameters-server.php",
    "url-post": "http://localhost/php7/scripts-web/05/parameters-server.php"
}
  • Zeile 1: die URL des Ziel-Webskripts für GET-Clients;
  • Zeile 2: die URL des Ziel-Webskripts für POST-Clients;

GET-Clients senden drei Parameter [Nachname, Vorname, Alter] an den Server. Der Client [parameters-get-client.php] sieht wie folgt aus:


<?php
 
// client GET of a web server
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
 
// customer configuration
const CONFIG_FILE_NAME = "config-parameters-client.json";
 
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}
 
// create a HTTP customer
$httpClient = HttpClient::create();
 
try {
  // prepare the parameters
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
// information is encoded
  $parameters = "prenom=" . urlencode($prenom) .
    "&nom=" . urlencode($nom) .
    "&age=$age”;
  // query
  $response = $httpClient->request('GET', $config['url-get'] . "?$parameters");
  // answer status
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // we retrieve the headers
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // server response is displayed
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  // error is displayed
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Kommentare

  • Zeilen 33–35: Kodierung der an den Server gesendeten Parameter. Die Parameter [$first_name, $last_name], die UTF-8-Zeichen enthalten können, werden mit der Funktion [urlencode] kodiert. Alle nicht-alphanumerischen Zeichen (wie durch relationale Ausdrücke definiert) werden durch %xx ersetzt, wobei xx der Hexadezimalwert des Zeichens ist. Leerzeichen werden durch das Pluszeichen (+) ersetzt;
  • Zeile 37: Die angeforderte URL lautet $URL?$parameters, wobei $parameters die Form name=val1&firstname=val2&age=val3 hat;
  • Zeile 48: Der Client zeigt einfach die Antwort des Servers an;

Vielleicht interessiert es Sie, was der Server bei einer parametrisierten GET-Anfrage empfängt. Dazu starten wir unseren generischen Server [RawTcpServer] auf Port 100 des lokalen Rechners von einem Laragon-Terminal aus (siehe Abschnitt „Links“):

Image

Vergewissern Sie sich, dass Sie sich in [4] tatsächlich im Ordner „utilities“ befinden.

Wir ändern die JSON-Datei [parameters-get-client.json], die die GET- und POST-Clients konfiguriert:


{
    "url-get": "http://localhost:100/php7/scripts-web/05/parameters-server.php",
    "url-post": "http://localhost/php7/scripts-web/05/parameters-server.php"
}
  • Zeile 2: Wir haben den Port des Webservers geändert. Daher wird [RawTcpServer] kontaktiert;

Wir starten den Client. Im Fenster [RawTcpServer] sehen wir folgende Informationen:

Image

  • in [1] die vom Client gesendete GET-Anfrage. Wir können die Kodierung bestimmter Zeichen deutlich erkennen;

17.9.3. Der GET-/POST-Server

Image

Das Server-Skript [parameters-server.php] lautet wie folgt:


<?php
 
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
 
// we retrieve the request
$request = Request::createFromGlobals();
// retrieve query parameters
$getParameters = $request->query->all();
$bodyParameters = $request->request->all();
 
// we work out the answer
$response = new Response();
// the content of the answer is utf-8 text
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
// response content - an array encoded in jSON
$response->setContent(json_encode([
  "method" => $request->getMethod(),
  "uri" => $request->getRequestUri(),
  "getParameters" => $getParameters,
  "bodyParameters" => $bodyParameters
    ], JSON_UNESCAPED_UNICODE));
// reply sent
$response->send();

Kommentare

  • Zeile 9: Erstellung des [Request]-Objekts für das Webskript. Dieses Objekt fasst alle Informationen zusammen, die das Webskript vom Client erhalten hat;
  • Zeile 11: Das Objekt [Request→query] ist vom Typ [ParameterBag] und sammelt die Parameter einer beliebigen GET-Operation von einem Client. Der Ausdruck [Request→query→get("X")] ruft den Parameter mit dem Namen X aus den GET-Parametern [name=val1&firstname=val2&age=val3] ab. Der Ausdruck [Request→query→all()] ruft das Wörterbuch der GET-Parameter ab;
  • Zeile 12: Das Objekt [Request→request] ist vom Typ [ParameterBag] und enthält die Parameter, die als Dokument vom Client an den Server gesendet wurden. Diese Parameter werden auch als hochgeladen bezeichnet, da sie zu einem Dokument gehören, das der Client an den Server sendet. Der Ausdruck [Request→request→get("X")] ruft den Parameter namens X aus den hochgeladenen Parametern [last_name=val1&first_name=val2&age=val3] ab. Der Ausdruck [Request→request→all()] ruft das Wörterbuch der hochgeladenen Parameter ab;
  • Zeilen 17–18: Der Client wird darüber informiert, dass er in UTF-8 kodiertes JSON erhalten wird;
  • Zeilen 20–25: Der Server sendet dem Client alle empfangenen Parameter, die vom Client ausgeführte Operation [GET / POST / …] und die angeforderte URI zurück. Diese Methode wird über den Ausdruck [$request→getMethod()] ermittelt. Das an den Client gesendete Dokument ist die JSON-Zeichenkette eines assoziativen Arrays, dessen Werte teilweise selbst assoziative Arrays sind. Der Parameter [JSON_UNESCAPED_UNICODE] stellt sicher, dass Unicode-Zeichen (wie beispielsweise Zeichen mit Akzenten) unverändert und nicht kodiert gesendet werden;
  • Zeile 27: Die Antwort wird an den Client gesendet;

Die Ausführung des clientseitigen Skripts liefert folgende Ergebnisse:

---Réponse avec statut : 200
---Entêtes de la réponse
date: Mon, 03 Jun 2019 10:08:45 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 207
connection: close
content-type: application/json
---Réponse du serveur [{"method":"GET","uri":"\/php7\/scripts-web\/05\/parameters-server.php?prenom=jean-paul&nom=de+la+h%C3%BBche&age=45","getParameters":{"prenom":"jean-paul","nom":"de la hûche","age":"45"},"bodyParameters":[]}]
  • Zeile 10:
    • [method]: Die Methode ist GET;
    • [uri]: Die URL-kodierten Parameter der GET-Anfrage sind in der angeforderten URI sichtbar;
    • [getParameters]: das Array der GET-Parameter;
    • [bodyParameters]: das Array der hochgeladenen Parameter: es ist leer;

17.9.4. Der GET-Client – Version 2

In der vorherigen Version des Client-Skripts haben wir die an den Server gesendeten Parameter zu Lehrzwecken selbst URL-kodiert. Das [HttpClient]-Objekt kann diese Aufgabe selbst übernehmen. Hier ist das entsprechende Skript [parameters-get-client-2.php]:


<?php
 
// client GET of a web server
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
 
// customer configuration
const CONFIG_FILE_NAME = "config-parameters-client.json";
 
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}
 
// create a HTTP customer
$httpClient = HttpClient::create();
try {
  // prepare the parameters
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
  // make a request to the server
  $response = $httpClient->request('GET', $config['url-get'],
    ["query" => [
        "prenom" => $prenom,
        "nom" => $nom,
        "age" => $age
  ]]);
  // answer status
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // we retrieve the headers
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // server response is displayed
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  // error is displayed
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Kommentare

  • Zeilen 33–37: Hinzufügen von Parametern zur GET-Anfrage aus Zeile 32. Das [HttpClient]-Objekt übernimmt die URL-Kodierung selbst;

17.9.5. Der POST-Client

Ein HTTP-Client sendet die folgende Textsequenz an den Webserver: HTTP-Header, Leerzeile, Dokument. Beim vorherigen Client sah diese Sequenz wie folgt aus:

1
2
3
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 senden, die als POST-Methode bekannt ist. In diesem Fall lautet die an den Webserver gesendete Textsequenz wie folgt:

1
2
3
4
POST /url HTTP/1.1
… autres entêtes HTTP
ligne vide
paramètres

Diesmal sind die Parameter, die im GET-Client in den HTTP-Headern enthalten waren, Teil des Dokuments, das im POST-Client nach den Headern gesendet wird.

Das Skript für den POST-Client [parameters-postclient.php] lautet wie folgt:


<?php
 
// client POST of a web server
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
 
// customer configuration
const CONFIG_FILE_NAME = "config-parameters-client.json";

// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}
 
// create a HTTP customer
$httpClient = HttpClient::create();
try {
  // prepare the parameters
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
  // make a request to the server
  $response = $httpClient->request('POST', $config['url-post'],
    ["body" => [
        "prenom" => $prenom,
        "nom" => $nom,
        "age" => $age
  ]]);
  // answer status
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // we retrieve the headers
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // server response is displayed
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  // error is displayed
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}
  • Zeile 32: Wir haben nun eine POST-HTTP-Anfrage;
  • Zeilen 33–37: Die POST-Parameter werden als Body der POST-Anfrage bezeichnet: Dies ist das Dokument, das vom Client an den Server gesendet wird. Hier werden drei Parameter gesendet [last_name, first_name, age];
  • Zeile 48: Wir zeigen die JSON-Antwort des Servers an;

Die Ergebnisse der Ausführung des Client-Skripts lauten wie folgt:


---Réponse avec statut : 200
---Entêtes de la réponse
date: Mon, 03 Jun 2019 11:43:02 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 163
connection: close
content-type: application/json
---Réponse du serveur [{"method":"POST","uri":"\/php7\/scripts-web\/05\/parameters-server.php","getParameters":[],"bodyParameters":{"prenom":"jean-paul","nom":"de la hûche","age":"45"}}]
  • Zeile 10: Die Methode ist [Post] und die Parameter sind vom Typ [bodyParameters]. Es gibt keine [getParameters], wie aus der [uri] hervorgeht;

Vielleicht interessiert es Sie, was der Server bei einer POST-Anfrage empfängt. Dazu starten wir unseren generischen [RawTcpServer] auf Port 100 des lokalen Rechners von einem Laragon-Terminal aus (siehe verlinkten Absatz):

Image

Vergewissere dich, dass du dich in [4] tatsächlich im Ordner „utilities“ befindest.

Wir ändern die JSON-Datei [config-parameters-client.json], die den POST-Client konfiguriert:


{
    "url-get": "http://localhost:100/php7/scripts-web/05/parameters-server.php",
    "url-post": "http://localhost:100/php7/scripts-web/05/parameters-server.php"
}
  • Zeile 3: Wir haben den Port des Webservers geändert. Daher wird [RawTcpServer] kontaktiert;

Wir starten den Client. Im Fenster [RawTcpServer] sehen wir folgende Informationen:

Image

  • in [6] die POST-Anfrage;
  • in [7]: Der HTTP-Header [Content-Length] gibt die Anzahl der Bytes in dem Dokument an, das der Client an den Server senden wird. Der HTTP-Header [Content-Type] gibt die Art dieses Dokuments an. Der Typ [application/x-www-form-urlencoded] bezeichnet URL-kodierten Text;
  • in [8] die Leerzeile, die das Ende der HTTP-Header und den Beginn des 44-Byte-Dokuments markiert. Was der Screenshot nicht zeigt, ist das Dokument selbst. Es handelt sich um die URL-kodierte Zeichenfolge der Parameter: [first_name=jean-paul&last_name=de+la+h%C3%BBche&age=45]. Der Leser kann überprüfen, dass sie tatsächlich 44 Zeichen umfasst;

17.9.6. Ein gemischter POST-Client

In einer POST-Anfrage können Sie in der URL kodierte Parameter mit solchen mischen, die im vom Client nach den HTTP-Headern gesendeten Dokument kodiert sind. Hier ist ein Beispiel [parameters-mixte-postclient.php]:


<?php
 
// client POST of a web server
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
 
// customer configuration
const CONFIG_FILE_NAME = "config-parameters-client.json";
 
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}
 
// create a HTTP customer
$httpClient = HttpClient::create();
try {
  // prepare the parameters
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
  // make a request to the server
  $response = $httpClient->request('POST', $config['url-post'],
    [
      // document parameters (body)
      "body" => [
        "prenom" => $prenom,
        "nom" => $nom,
        "age" => $age
      ],
      // parameters of URL (query)
      "query" => [
        "prenom2" => $prenom,
        "nom2" => $nom,
        "age2" => $age
  ]]);
  // answer status
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // we retrieve the headers
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // server response is displayed
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  // error is displayed
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Kommentare

  • Zeile 32: eine POST-Anfrage;
  • Zeilen 40–45: URL-kodierte Parameter in der URL;
  • Zeilen 35–39: URL-kodierte Parameter im Request-Body (Body, Dokument);

Bei der Ausführung wird die folgende Konsolenausgabe erhalten:

---Réponse avec statut : 200
---Entêtes de la réponse
date: Mon, 03 Jun 2019 12:34:23 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 270
connection: close
content-type: application/json
---Réponse du serveur [{"method":"POST","uri":"\/php7\/scripts-web\/05\/parameters-server.php?prenom2=jean-paul&nom2=de%20la%20h%C3%BBche&age2=45","getParameters":{"prenom2":"jean-paul","nom2":"de la hûche","age2":"45"},"bodyParameters":{"prenom":"jean-paul","nom":"de la hûche","age":"45"}}]
  • Zeile 10: Wir sehen, dass der Server beide Arten von Parametern abrufen konnte;

17.9.7. Ein gemischter GET-Client

Wir versuchen nun, dasselbe wie zuvor mit einer GET-Anfrage zu tun. Das Skript [parameters-mixte-get-client.php] lautet wie folgt:


<?php
 
// client POST of a web server
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
 
// customer configuration
const CONFIG_FILE_NAME = "config-parameters-client.json";
 
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}
 
// create a HTTP customer
$httpClient = HttpClient::create();
try {
  // prepare the parameters
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
  // make a request to the server
  $response = $httpClient->request('GET', $config['url-post'],
    [
      // document parameters (body)
      "body" => [
        "prenom" => $prenom,
        "nom" => $nom,
        "age" => $age
      ],
      // parameters of URL (query)
      "query" => [
        "prenom2" => $prenom,
        "nom2" => $nom,
        "age2" => $age
  ]]);
  // answer status
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // we retrieve the headers
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // server response is displayed
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  // error is displayed
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Kommentare

  • Zeile 32: eine POST-Anfrage;
  • Zeilen 40–45: URL-kodierte Parameter in der URL;
  • Zeilen 35–39: URL-kodierte Parameter im Request-Body (Body, Dokument);

Bei der Ausführung wird die folgende Konsolenausgabe erhalten:

---Réponse avec statut : 200
---Entêtes de la réponse
date: Mon, 03 Jun 2019 12:41:19 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 217
connection: close
content-type: application/json
---Réponse du serveur [{"method":"GET","uri":"\/php7\/scripts-web\/05\/parameters-server.php?prenom2=jean-paul&nom2=de%20la%20h%C3%BBche&age2=45","getParameters":{"prenom2":"jean-paul","nom2":"de la hûche","age2":"45"},"bodyParameters":[]}]
  • Zeile 10: Wir sehen, dass der Server keine URL-kodierten Parameter in dem vom Client gesendeten Dokument erhalten hat. Wenn wir uns die vom Client gesendeten HTTP-Header ansehen, stellen wir fest, dass dieser zwar ein 44 Zeichen langes Dokument gesendet hat, der Server dieses jedoch nicht verarbeitet hat;

Welche Methode sollten Sie also wählen, um Informationen an den Server zu senden?

  • Die Methode [GET URL?param1=val1&param2=val2&…] verwendet eine parametrisierte URL, die als Link dienen kann. Das ist ihr Hauptvorteil: Der Benutzer kann solche Links als Lesezeichen speichern;
  • In anderen Anwendungen möchten Sie die an den Server gesendeten Parameter möglicherweise nicht in einer URL anzeigen. Zum Beispiel aus Sicherheitsgründen. In diesem Fall verwenden Sie die [POST]-Methode und fügen die URL-kodierten Parameter in ein an den Server gesendetes Dokument ein;

17.10. Web-Sitzungsverwaltung

In den vorherigen Client/Server-Beispielen verlief der Prozess wie folgt:

  • Der Client öffnet eine Verbindung zu Port 80 auf dem Webserver;
  • er sendet die Textsequenz: HTTP-Header, Leerzeile, [Dokument];
  • als Antwort sendet der Server eine Sequenz desselben Typs;
  • der Server schließt 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 erstmalige Anfrage handelt. Zwischen den Verbindungen „vergisst“ der Server seinen Client. Aus diesem Grund wird das HTTP-Protokoll als zustandsloses Protokoll bezeichnet. Für den Server ist es jedoch nützlich, sich an seine Clients zu erinnern. Wenn eine Anwendung beispielsweise sicher ist, sendet der Client dem Server einen Benutzernamen und ein Passwort, um sich zu authentifizieren. Wenn der Server seinen Client zwischen den Verbindungen „vergisst“, müsste sich der Client bei jeder neuen Verbindung neu authentifizieren, was nicht praktikabel ist.

Um einen Client zu verfolgen, geht der Server wie folgt vor: Wenn ein Client eine erste Anfrage stellt, fügt der Server seiner Antwort eine Kennung hinzu, die der Client dann bei jeder neuen Anfrage zurücksenden muss. Dank dieser Kennung, die für jeden Client einzigartig ist, kann der Server einen Client erkennen. Er kann dann einen Speichereintrag für diesen Client in Form eines Speichereintrags verwalten, der eindeutig mit der Kennung des Clients verknüpft ist.

Technisch gesehen funktioniert das so:

  • In der Antwort an einen neuen Client fügt der Server den HTTP-Header „Set-Cookie: Key=Identifier“ ein. Dies geschieht nur bei der ersten Anfrage;
  • bei nachfolgenden Anfragen sendet der Client seine Kennung über den HTTP-Header „Cookie: Key=Identifier“ zurück, damit der Server ihn erkennen kann;

Man könnte sich fragen, woran der Server erkennt, dass es sich um einen neuen Client und nicht um einen wiederkehrenden handelt. Das verrät ihm das Vorhandensein des HTTP-Cookie-Headers in den HTTP-Headern des Clients. Bei einem neuen Client fehlt dieser Header.

Alle Verbindungen eines bestimmten Clients werden als Sitzung bezeichnet.

17.10.1. Die Konfigurationsdatei [php.ini]

Damit die Sitzungsverwaltung mit PHP korrekt funktioniert, müssen Sie sicherstellen, dass sie richtig konfiguriert ist. Unter Windows lautet der Name der Konfigurationsdatei php.ini. Je nach Ausführungskontext (Konsole, Web) muss sich die Konfigurationsdatei [php.ini] in unterschiedlichen Verzeichnissen befinden. Um diese zu finden, verwenden Sie das folgende Skript:

1
2
3
4
<?php

// infos PHP
phpinfo();

Zeile 4: Die Funktion phpinfo liefert Informationen über den PHP-Interpreter, der das Skript ausführt. Insbesondere gibt sie den Pfad zur verwendeten Konfigurationsdatei [php.ini] an.

Wir haben dieses Skript bereits in einer Konsolenumgebung verwendet (siehe Abschnitt „Link“). In einer Webumgebung erhalten wir das folgende Ergebnis:

Image

  • In [1-2] die Datei [php.ini], die den Web-Skript-Interpreter konfiguriert. Diese Datei enthält einen Abschnitt „session“:
[Session]
session.save_handler = files
session.save_path = "C:/myprograms/laragon-lite/tmp"
session.use_strict_mode = 0
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.cookie_httponly =
session.serialize_handler = php
session.gc_probability = 1
session.gc_divisor = 1000
session.gc_maxlifetime = 36000
session.referer_check =
session.cache_limiter = nocache
session.cache_expire = 180
session.use_trans_sid = 0
session.trans_sid_tags = "a=href,area=href,frame=src,form="
session.sid_bits_per_character = 5
  • Zeile 2: Client-Sitzungsdaten werden in einer Datei gespeichert;
  • Zeile 3: Das Verzeichnis, in dem die Sitzungsdaten gespeichert werden. Wenn dieses Verzeichnis nicht existiert, wird kein Fehler gemeldet und die Sitzungsverwaltung funktioniert nicht;
  • Zeilen 4–6: geben an, dass die Sitzungs-ID über die HTTP-Header „Set-Cookie“ und „Cookie“ verwaltet wird;
  • Zeile 7: Der Set-Cookie-Header hat die Form Set-Cookie: PHPSESSID=session_id;
  • Zeile 8: Eine Client-Sitzung wird nicht automatisch gestartet. Das Server-Skript muss sie explizit mit der Funktion session_start() anfordern;
  • Zeile 9: Das Sitzungs-Cookie bleibt gültig, bis der Client-Browser geschlossen wird;
  • Zeile 10: Der Pfad, für den das Sitzungs-Cookie gesendet werden muss. Wenn [session.cookie_path = /xxx] ist, muss das Cookie jedes Mal gesendet werden, wenn der Browser eine URL der Form [/xxx/yyy/zzz] anfordert. Hier gibt der Pfad [/] an, dass das Cookie für jede URL auf der Website gesendet werden muss;
  • Zeile 13: Bestimmte Session-Objekte müssen serialisiert werden, um in einer Datei gespeichert zu werden. PHP übernimmt diese Serialisierung/Deserialisierung mithilfe der Funktionen [serialize / unserialize];
  • Zeile 16: Zeitlimit, nach dessen Ablauf die in der Sitzungsdatei gespeicherten Sitzungsobjekte als veraltet gelten;
  • Zeile 19: Lebensdauer der Sitzung. Nach Ablauf dieser Zeit wird eine neue Sitzung erstellt und die in der vorherigen Sitzung gespeicherten Objekte gehen verloren;

17.10.2. Beispiel 1

17.10.2.1. Der Server

Image

Die Verwaltung der Sitzungs-ID ist für einen Webdienst transparent. Diese ID wird vom Webserver verwaltet. Ein Webdienst greift über die Funktion session_start() auf die Sitzung des Clients zu. Ab diesem Zeitpunkt kann der Webdienst über das $_SESSION-Dictionary Daten in die Sitzung des Clients schreiben bzw. aus dieser lesen. Wenn die Bibliothek [HttpFoundation] verwendet wird, ist die Sitzung über den Ausdruck [Request→getSession] verfügbar.

Der folgende Code [session-server.php] demonstriert die sitzungsbasierte Verwaltung von drei Zählern. Bei jeder neuen Anfrage erhöht das Webskript diese Zähler und speichert sie in der Sitzung, damit sie bei der nächsten Anfrage abgerufen werden können.


<?php
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
 
//
// we retrieve the request
$request = Request::createFromGlobals();
// session
$session = new Session();
$session->start();
// three counters are retrieved from the session
if ($session->has("N1")) {
  // n1 counter increment
  $session->set("N1", (int) $session->get("N1") + 1);
} else {
  // counter N1 is not in session - create it
  $session->set("N1", 0);
}
if ($session->has("N2")) {
  // n2 counter increment
  $session->set("N2", (int) $session->get("N2") + 1);
} else {
  // counter N2 is not in session - create it
  $session->set("N2", 10);
}
if ($session->has("N3")) {
  // n3 counter increment
  $session->set("N3", (int) $session->get("N3") + 1);
} else {
  // counter N3 is not in session - create it
  $session->set("N3", 100);
}
// we work out the answer
$response = new Response();
// the content of the answer is utf-8 text
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
// the answer will be the jSON of an array containing the three counters
$response->setContent(json_encode([
  "N1" => $session->get("N1"),
  "N2" => $session->get("N2"),
  "N3" => $session->get("N3")]));
 
// reply sent
$response->send();
  • Zeile 10: Das [$request]-Objekt fasst alle Informationen über die vom Webskript empfangene Anfrage zusammen;
  • Zeilen 12–13: Eine Sitzung wird erstellt und aktiviert. Das [Session]-Objekt fasst die Sitzungsdaten zusammen, die dem vom Client gesendeten Sitzungscookie entsprechen. Hat der Client kein solches Cookie gesendet, werden keine Daten in [Session] gespeichert. Das Webskript fügt den HTTP-Header [Set-Cookie: PHPSESSID=xxx] in seine erste Antwort ein. Bei nachfolgenden Anfragen sendet der Client den HTTP-Header [Cookie: PHPSESSID=xxx], um die Sitzung anzugeben, deren Inhalt er verwenden möchte. Eine Sitzung ist der Speicher eines Clients;
  • Zeile 15: Wir prüfen, ob die Sitzung einen Schlüssel namens [N1] enthält. Dies ist der Name unseres ersten Zählers. Falls nicht (Zeile 20), setzen wir seinen Wert auf 0 und fügen ihn der Sitzung hinzu. Falls ja (Zeile 23), gehen wir wie folgt vor:
    • holen wir ihn aus der Sitzung;
    • erhöhen seinen Wert um 1;
    • legen ihn wieder in die Sitzung zurück;
  • Zeilen 22–35: Wir verfahren ebenso mit den beiden anderen Zählern, N2 und N3;
  • Zeilen 36–40: Wir bereiten eine Antwort vom Typ [application/json] vor;
  • Zeilen 42–45: Die Antwort ist die JSON-Zeichenkette eines Arrays, das die drei Zähler enthält;
  • Zeile 48: Senden der Antwort an den Client;

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

  • Der Server ist dafür verantwortlich, dem 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

Wir verwenden einen Webbrowser als Client. Standardmäßig (bzw. je nach Konfiguration) sendet der Browser tatsächlich die Sitzungskennungen, die der Server ihm übermittelt, an den Server zurück. Bei jeder Anfrage erhält der Browser die drei vom Server gesendeten Zähler und sieht, wie sich deren Werte erhöhen.

Image

  • In [2] zeigt die erste Anfrage an den Webdienst;
  • in [4] zeigt die vierte Anfrage, dass die Zähler tatsächlich erhöht wurden. Die Zählerwerte werden im Verlauf der Anfragen tatsächlich gespeichert;

Verwenden wir den Entwicklermodus, 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 den Entwicklermodus (F12). Dadurch wird die aktuelle Sitzung des Browsers gelöscht, sodass eine neue Sitzung gestartet wird. Wir fragen den Dienst [session-server.php] ab:

Image

In [5] sehen wir die Sitzungs-ID, die der Server in seiner Antwort auf die erste Anfrage des Clients gesendet hat. Er verwendet den HTTP-Header „Set-Cookie“.

Erstellen wir eine neue Anfrage, indem wir die Seite im Webbrowser aktualisieren (F5):

Image

Hier fallen uns zwei Dinge auf:

  • In [11] sendet der Webbrowser die Sitzungs-ID mit dem HTTP-Cookie-Header zurück.
  • In [12] enthält der Webdienst diese Kennung nicht mehr in seiner Antwort. Es liegt nun in der Verantwortung des Clients, sie mit jeder seiner Anfragen zu senden.

17.10.2.2. Der Client

Wir werden nun ein clientseitiges Skript schreiben, das auf dem vorherigen serverseitigen Skript basiert. Bei der Sitzungsverwaltung muss es sich wie der Webbrowser verhalten:

  • In der Antwort des Servers auf seine erste Anfrage muss er die Sitzungs-ID finden, die der Server ihm sendet. Er weiß, dass er sie im HTTP-Set-Cookie-Header findet.
  • Bei jeder nachfolgenden Anfrage muss er die empfangene Kennung an den Server zurücksenden. Dies geschieht über den HTTP-Cookie-Header.

Image

Der Client [session-client] wird durch die folgende JSON-Datei [config-session-client.json] konfiguriert:

1
2
3
{
    "url": "http://localhost/php7/scripts-web/06/session-server.php"
}

Der Client-Code [session-client] lautet wie folgt:


<?php
 
// session management
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
 
// customer configuration
const CONFIG_FILE_NAME = "config-session-client.json";
 
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}
 
// create a HTTP customer
$httpClient = HttpClient::create();
try {
  // we'll make 10 requests
  for ($i = 0; $i < 10; $i++) {
    // make a request to the server
    if (!isset($sessionCookie)) {
      // no session
      $response = $httpClient->request('GET', $config['url']);
    } else {
      // with session
      $response = $httpClient->request('GET', $config['url'],
        ["headers" => ["Cookie" => $sessionCookie]]);
    }
    // answer status
    $statusCode = $response->getStatusCode();
    print "---Réponse avec statut : $statusCode\n";
    // we retrieve the headers
    print "---Entêtes de la réponse\n";
    $headers = $response->getHeaders();
    foreach ($headers as $type => $value) {
      print "$type: " . $value[0] . "\n";
    }
    // retrieve the session cookie if it exists
    if (isset($headers["set-cookie"])) {
      // session cookie ?
      foreach ($headers["set-cookie"] as $cookie) {
        $match = [];
        $match = preg_match("/^PHPSESSID=(.+?);/", $cookie, $champs);
        if ($match) {
          $sessionCookie = "PHPSESSID=" . $champs[1];
        }
      }
    }
  }
  // the jSON server response is displayed
  print "---Réponse du serveur : {$response->getContent()}\n";
} catch (TypeError | RuntimeException $ex) {
  // error is displayed
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}
 
 

Kommentare

  • Zeile 27: Erstellung des HTTP-Clients;
  • Zeile 30: Wir senden dieselbe Anfrage zehnmal an den Server [session-server.php];
  • Zeile 32: Die Variable [$sessionCookie] wird auf den Wert des vom Client empfangenen [Set-Cookie]-HTTP-Headers gesetzt;
  • Zeilen 32–34: Wenn diese Variable nicht existiert, bedeutet dies, dass die Sitzung noch nicht begonnen hat. Wir senden die [GET]-Anfrage ohne den [Cookie]-Header;
  • Zeilen 35–38: Andernfalls hat die Sitzung begonnen, und wir senden die [GET]-Anfrage mit dem [Cookie]-Header. Der Wert dieses Headers ist [$sessionCookie];
  • Zeile 50: Wenn der [Set-Cookie]-Header unter den empfangenen HTTP-Headern ist, suchen wir nach dem Sitzungscookie;
  • Zeile 52: Der Webserver kann mehrere [Set-Cookie]-Header senden. Das Sitzungscookie ist nur einer davon. In unserem Beispiel hat es das spezifische Format [PHPSESSID=xxx;;];
  • Zeilen 53–57: Zur Suche nach dem Sitzungscookie wird ein regulärer Ausdruck verwendet;
  • Zeile 62: Sobald die 10 Anfragen gestellt wurden, zeigen wir die letzte JSON-Antwort vom Server an;

Ergebnisse

Durch Ausführen des Client-Skripts wird Folgendes in der NetBeans-Konsole angezeigt:


"C:\myprograms\laragon-lite\bin\php\php-7.2.11-Win32-VC15-x64\php.exe" "C:\Data\st-2019\dev\php7\poly\scripts-console\clients web\06\session-client.php"
---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 13:41:34 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
set-cookie: PHPSESSID=1cerjgsgdlc35e1mkenvtltmh8; path=/
content-length: 25
connection: close
content-type: application/json
---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 13:41:34 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
content-length: 25
connection: close
content-type: application/json
---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 13:41:34 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
content-length: 25
connection: close
content-type: application/json
---Réponse avec statut : 200
…………………………………………………………
---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 13:41:34 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
content-length: 25
connection: close
content-type: application/json
---Réponse du serveur : {"N1":9,"N2":19,"N3":109}
  • Zeile 8: In seiner ersten Antwort sendet der Server die Sitzungs-ID. In den folgenden Antworten sendet er sie nicht mehr;
  • Zeile 41: Die drei Zähler [N1, N2, N3] wurden tatsächlich 9 Mal erhöht. Bei Anfrage Nr. 1 wurden sie auf Null zurückgesetzt;

Das folgende Beispiel zeigt, dass Sie auch die Werte eines Arrays oder eines Objekts in der Sitzung speichern können.

17.10.3. Beispiel 2

17.10.3.1. Der Server

Image

Wir werden ein [Person]-Objekt in die Sitzung einfügen. Die Definition dieser Klasse lautet wie folgt:


<?php
 
namespace Modèles;
 
class Personne implements \JsonSerializable {
  // attributes
  private $nom;
  private $prénom;
  private $âge;
 
  // convert associative array to object [Person]
  public function setFromArray(array $assoc): Personne {
    // initialize the current object with the associative array
    foreach ($assoc as $attribute => $value) {
      $this->$attribute = $value;
    }
    // result
    return $this;
  }
 
  // getters and setters
  public function getNom() {
    return $this->nom;
  }
 
  public function getPrénom() {
    return $this->prénom;
  }
 
  public function setNom($nom) {
    $this->nom = $nom;
    return $this;
  }
 
  public function setPrénom($prénom) {
    $this->prénom = $prénom;
    return $this;
  }
 
  public function getÂge() {
    return $this->âge;
  }
 
  public function setÂge($âge) {
    $this->âge = $âge;
    return $this;
  }
 
  // toString
  public function __toString(): string {
    return "Personne [$this->prénom, $this->nom, $this->âge]";
  }
 
  // implements the JsonSerializable interface
  public function jsonSerialize(): array {
    // render an associative array with the object's attributes as keys
    // this table can then be encoded as jSON
    return get_object_vars($this);
  }
 
  // convert a jSON to a [Person] object
  public static function jsonUnserialize(string $json): Personne {
    // we create a person from the string jSON
    return (new Personne())->setFromArray(json_decode($json, true));
  }
 
}

Das Server-Skript sieht wie folgt aus:


<?php
 
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
require_once __DIR__ . "/Personne.php";
use \Modèles\Personne;
 
//
// retrieve the current query
$request = Request::createFromGlobals();
 
// session
$session = new Session();
$session->start();
 
// retrieve various data from the session
// table
if ($session->has("tableau")) {
  // the array is in the session - all its values are incremented
  $tableau = $session->get("tableau");
  for ($i = 0; $i < count($tableau); $i++) {
    $tableau[$i] += 1;
  }
  // put the table back in the session
  $session->set("tableau", $tableau);
} else {
  // the array is not in the session - we create it
  $tableau = [0, 10, 100];
  // we put it in the session
  $session->set("tableau", $tableau);
}
// dictionary
if ($session->has("assoc")) {
  // [assoc] is in the session - all its elements are incremented
  $assoc = $session->get("assoc");
  foreach ($assoc as $key => $value) {
    $assoc[$key] = $value + 1;
  }
  // put $assoc in the session
  $session->set("assoc", $assoc);
} else {
  // [assoc] is not in the session - we create it
  $assoc = ["un" => 0, "deux" => 10, "trois" => 100];
  // put $assoc in the session
  $session->set("assoc", $assoc);
}
// object Person
if ($session->has("personne")) {
  // [person] is in the session - his age is incremented
  $personne = $session->get("personne");
  $personne->setÂge($personne->getÂge() + 1);
} else {
  // [person] is not in the session - we create it
  $personne = (new Personne())->setFromArray(
    ["prénom" => "Léonard", "nom" => "Hûche", "âge" => 0]);
  // put $personne in the session
  $session->set("personne", $personne);
}
// we work out the answer
$response = new Response();
// the content of the response is jSON utf-8
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
$response->setContent(json_encode([
  "tableau" => $tableau,
  "assoc" => $assoc,
  "personne" => $personne], JSON_UNESCAPED_UNICODE));
 
// reply sent
$response->send();

Kommentare

  • Zeilen 16–17: Die aktuelle Sitzung abrufen und aktivieren;
  • Zeilen 21–34: Wir verwalten ein in der Sitzung gespeichertes Array [array]. Bei jeder neuen Anfrage werden seine Elemente um 1 erhöht;
  • Zeilen 36–49: Verwaltung eines assoziativen Arrays [assoc], das in der Sitzung gespeichert ist. Bei jeder neuen Anfrage werden seine Elemente um 1 erhöht;
  • Zeilen 51–61: Verwaltung eines in der Sitzung gespeicherten Objekts [Person]. Bei jeder neuen Anfrage wird das Alter dieser Person um 1 erhöht;
  • Zeilen 62–73: Eine JSON-Antwort wird an den Client gesendet: die JSON-Zeichenkette eines assoziativen Arrays;

Führen wir dieses Skript in NetBeans aus. Die ersten beiden Anfragen liefern die folgenden Ergebnisse (drücken Sie im Browser F5 für die zweite):

Image

  • Wir sehen, dass in [6–8] alle Zähler erhöht wurden;

17.10.3.2. Der Client

Image

Der Client ist derselbe wie in Beispiel 1 (Link-Abschnitt). Wir ändern lediglich seine Konfigurationsdatei [config-session-client]:


{
    "url": "http://localhost/php7/scripts-web/07/session-server.php"
}

Die Ausführung liefert folgende Ergebnisse:


---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 14:25:24 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
set-cookie: PHPSESSID=qbfrj8clr20mod3eriur71mao6; path=/
content-length: 119
connection: close
content-type: application/json
---Réponse avec statut : 200
………….……………………………………………………….
---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 14:25:24 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
content-length: 119
connection: close
content-type: application/json
---Réponse du serveur : {"tableau":[9,19,109],"assoc":{"un":9,"deux":19,"trois":109},"personne":{"nom":"Hûche","prénom":"Léonard","âge":9}}
  • Zeile [22]: Wir sehen, dass alle Zähler erhöht wurden;

17.11. Authentifizierung

Wir konzentrieren uns nun auf Webdienste, die nur für bestimmte Benutzer bestimmt sind. Der Client muss sich daher beim Webdienst authentifizieren, bevor er eine Antwort erhält.

17.11.1. Der Client

Image

Der Client-Code [auth-client.php] lautet wie folgt:


<?php
 
// session management
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
 
// customer configuration
const CONFIG_FILE_NAME = "config-auth-client.json";
 
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}
 
// create a HTTP customer
$httpClient = HttpClient::create([
    'auth_basic' => ['admin', 'admin'],
    // "verify_peer" => false,
    // "verify_host" => false
  ]);
 
 
try {
  // make a request to the server
  $response = $httpClient->request('GET', $config['url']);
  // answer status
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // we retrieve the headers
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // the jSON server response is displayed
  print "---Réponse du serveur : {$response->getContent()}\n";
} catch (TypeError | RuntimeException $ex) {
  // error is displayed
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Kommentare

  • Zeilen 27–31: Wir haben der statischen Methode [HttpClient::create] einen Parameter übergeben, nämlich ein Hash;
  • Zeile 28: Der Schlüssel [auth_basic] hat den Wert eines Arrays mit zwei Elementen [user, password]. Der Client verwendet diese Elemente zur Authentifizierung beim Webdienst. Der Schlüssel [auth_basic] bezieht sich auf einen Authentifizierungstyp namens [Basic Authorization], benannt nach dem HTTP-Header, den der Client sendet. Es gibt noch andere Arten der Authentifizierung;
  • Abgesehen von diesem Code ist der Client identisch mit den vorherigen;

Um die vom Client gesendeten HTTP-Header anzuzeigen, verbinden wir ihn wie schon oft zuvor mit dem generischen TCP-Server [RawTcpServer]:

Image

Wir starten den Client mit der folgenden Konfiguration [config-auth-client.json]:


{
    "url": "http://localhost:100/php7/scripts-web/08/auth-server.php"
}

Der [RawTcpServer] empfängt dann die folgenden Zeilen:

Image

  • In [5] sehen wir den vom Client gesendeten Header [Authorization: Basic XXX]. Die Zeichenfolge XXX ist die in Base64 kodierte Zeichenfolge [Benutzername:Passwort];

Um dies zu überprüfen, können Sie die empfangene Zeichenfolge auf der Website [https://www.base64decode.org/] dekodieren:

Image

17.11.2. Der Server

Image

Der [auth-server.php]-Server sieht wie folgt aus:


<?php
 
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
 
// authorized users
$users = ["admin" => "admin"];
//
// retrieve the current query
$request = Request::createFromGlobals();
// authentication
$requestUser = $request->headers->get('php-auth-user');
$requestPassword = $request->headers->get('php-auth-pw');
// does the user exist?
$trouvé = array_key_exists($requestUser, $users) && $users[$requestUser] === $requestPassword;
// answer preparation
$response = new Response();
// set the response status code
if (!$trouvé) {
  // not found - code 401
  $response->setStatusCode(Response::HTTP_UNAUTHORIZED);
  $response->headers->add(["WWW-Authenticate"=> "Basic realm=".utf8_decode("\"PHP7 par l'exemple\"")]);
} else {
  // found - code 200
  $response->setStatusCode(Response::HTTP_OK);
}
// response has no content, only HTTP headers
$response->send();

Kommentare

  • Zeile 9: autorisierte Benutzer, in diesem Fall ein einzelner Benutzer mit dem Login [admin] und dem Passwort [admin];
  • Zeile 14: Die Benutzer-ID wird aus dem Header [PHP-AUTH-USER] abgerufen. Dies ist kein vom Client gesendeter Header, sondern einer, der vom PHP des Servers erstellt wird;
  • Zeile 15: Das Passwort des Benutzers wird aus dem [PHP-AUTH-PW]-Header abgerufen, einem von PHP erstellten Header;
  • Zeile 17: Wir suchen in der Liste der autorisierten Benutzer nach dem Benutzer, der versucht, sich anzumelden;
  • Zeilen 23–24: Wenn der Benutzer nicht erkannt wird, wird Folgendes an den Client gesendet
    • Zeile 23: der Statuscode [401 Unauthorized];
    • Zeile 24: ein [WWW-Authenticate: Basic realm=”something”]-Header. Die meisten Browser erkennen diesen Header und zeigen ein Authentifizierungsfenster an, in dem der Benutzer zur Anmeldung aufgefordert wird. HTTP-Header müssen in ISO 8859-1 kodiert sein. NetBeans-Text ist jedoch in UTF-8 kodiert. Die Funktion [utf8_decode] übernimmt die Konvertierung von UTF-8 nach ISO 8859-1. Hier war dies nicht erforderlich, da die Zeichen in der Zeichenkette [in diesem Beispiel PHP7] in UTF-8 und ISO 8859-1 identisch sind. Die Funktion wurde lediglich als Hinweis auf die von den HTTP-Headern verwendete Kodierung eingefügt;
  • Zeile 25: Wenn der Benutzer erkannt wurde, senden wir dem Client den Code [200 OK];

Rufen wir die URL [auth-server.php] mit einem Browser auf:

Image

Wir sehen, dass der Browser ein Authentifizierungsfenster anzeigt. In [2] sehen wir den Wert des vom Server gesendeten [WWW-Authenticate]-Headers. Wenn wir uns die vom Browser empfangenen HTTP-Header ansehen, finden wir Folgendes:

1
2
3
4
5
6
7
8
9
HTTP/1.0 401 Unauthorized
Date: Fri, 07 Jun 2019 09:11:23 GMT
Server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
X-Powered-By: PHP/7.2.11
Cache-Control: no-cache, private
WWW-Authenticate: Basic realm="PHP7 par l'exemple"
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8
  • Zeile 1: der Antwortstatuscode [401 Unauthorized];
  • Zeile 6: der HTTP-Header [WWW-Authenticate];
  • Zeile 7: Der Antworttext ist leer;

Wenn Sie in [3-4] zweimal [admin] eingeben, lautet die Antwort des Servers wie folgt:

1
2
3
4
5
6
7
8
HTTP/1.0 200 OK
Date: Fri, 07 Jun 2019 09:21:00 GMT
Server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
X-Powered-By: PHP/7.2.11
Cache-Control: no-cache, private
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8
  • Zeile 1: der Antwortcode 200 OK;
  • Zeile 6: Der Antworttext ist leer;

Wenn in [3-4] falsche Anmeldedaten eingegeben werden, zeigt der zum Testen verwendete Browser [Firefox] das Anmeldefenster so lange an, bis die richtigen Anmeldedaten eingegeben werden. Bei jedem Hin- und Rücklauf zum Server wird dieselbe Antwort empfangen, was das Anmeldefenster des Browsers auslöst.

Führen wir den Client [auth-client.php] mit einem nicht autorisierten Benutzer aus. Die Antwort des Servers lautet wie folgt:


---Réponse avec statut : 401
---Entêtes de la réponse
Erreur de communication avec le serveur : HTTP/1.0 401 Unauthorized returned for "https://localhost/php7/scripts-web/08/auth-server.php".
  • In [1] erhielt der Client tatsächlich einen 401-Code;
  • in [3] wurde im Client eine Ausnahme ausgelöst. Es war der Symfony [HttpClient], der sie ausgelöst hat: Er löst eine Ausnahme aus, wenn der HTTP-Antwortstatuscode auf einen serverseitigen Fehler hinweist und der Client versucht, die Header oder den Inhalt der Serverantwort zu lesen. Die Meldung in Zeile 3 zeigt, dass der Server mit [HTTP/1.0 401 Unauthorized] geantwortet hat, um anzuzeigen, dass der Benutzer nicht erkannt wurde;

Führen wir nun den Client [auth-client.php] mit dem autorisierten Benutzer [‘admin’,’admin’] aus. Die Antwort des Servers lautet dann wie folgt:


---Réponse avec statut : 200
---Entêtes de la réponse
date: Wed, 05 Jun 2019 10:11:02 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 0
connection: close
content-type: text/html; charset=UTF-8
---Réponse du serveur :
 
  • Zeile 1: Der Server antwortete mit [HTTP/1.2 200 OK];
  • Zeile 7: Die Antwort enthält keinen Inhalt (0 Bytes);

17.11.3. Sichern der Client-Server-Verbindung

Wir haben gesehen, dass der Client zur Authentifizierung beim Server den Header sendet:

authorization: Basic YWRtaW46YWRtaW4=

Wenn diese Zeile von Spyware abgefangen wird, kann diese problemlos die in Base64 verschlüsselten Anmeldedaten [Benutzername, Passwort] aus der Zeichenfolge [YWRtaW46YWRtaW4=] extrahieren. Aus diesem Grund muss die Authentifizierung über eine sichere Verbindung ( ) zwischen Client und Server erfolgen. Sichere URLs verwenden das [HTTPS]-Protokoll anstelle des HTTP-Protokolls. Das [HTTPS]-Protokoll ist das HTTP-Protokoll innerhalb einer sicheren Client-Server-Verbindung. Sichere URLs haben die Form [https://chemin_document].

Nicht alle Webserver akzeptieren URLs in diesem Format. Sie müssen angepasst werden, um sicher zu sein. Der Apache-Server von Laragon ist ein sicherer Server, aber das HTTPS-Protokoll ist standardmäßig nicht aktiviert. Sie müssen es im Laragon-Menü aktivieren:

Image

  • Aktivieren Sie unter [4] die SSL-Verschlüsselung für den Apache-Server;

Sobald dies geschehen ist, wird der Apache-Server automatisch neu gestartet:

Image

  • in [1] erscheint ein grünes Vorhängeschloss: Dies zeigt an, dass das HTTPS-Protokoll aktiviert wurde;
  • in [2] erscheint ein neuer Dienstport, in diesem Fall Port 443. Dies ist der Dienstport für das sichere HTTPS-Protokoll;

Da wir nun über einen sicheren Server verfügen, ändern wir die Konfigurationsdatei des Clients [config-auth-client.json] wie folgt:


{
    "url": "https://localhost:443/php7/scripts-web/08/auth-server.php"
}

In [2] wurde das Protokoll auf [https] und der Port auf [443] geändert.

Führen wir nun den Client [auth-client.php] mit dem autorisierten Benutzer [admin, admin] aus. Die Konsolenausgabe lautet wie folgt:

Erreur de communication avec le serveur : Peer certificate cannot be authenticated with given CA certificates for"https://localhost/php7/scripts-web/08/auth-server.php".

Der Symfony-Client [HttpClient] hat eine Ausnahme ausgelöst, da der Server ihm ein Vertrauenszertifikat gesendet hat, das [HttpClient] nicht akzeptiert hat. Die SSL-Kommunikation basiert auf Vertrauenszertifikaten, die von offiziellen Stellen ausgestellt wurden. Als wir das HTTPS-Protokoll auf dem Laragon-Apache-Server aktiviert haben, wurde ein selbstsigniertes Zertifikat für den Apache-Server generiert. Ein selbstsigniertes Zertifikat ist ein Zertifikat, das nicht von einer offiziellen Stelle validiert wurde. Der Symfony-Client [HttpClient] hat dieses selbstsignierte Zertifikat abgelehnt.

Es ist möglich, [HttpClient] anzuweisen, die Gültigkeit des vom Server gesendeten Zertifikats nicht zu überprüfen. Dies geschieht mithilfe von Optionen in der Methode [HttpClient::create]:


// on crée un client HTTP
$httpClient = HttpClient::create([
    'auth_basic' => ['admin', 'admin'],
    "verify_peer" => false
  ]);

Zeile 4 legt fest, dass das Serverzertifikat nicht überprüft werden soll. Wir sind bereits im Skript [http-02.php] im verlinkten Abschnitt auf dieses Problem gestoßen. Dieses Skript verwendete die Bibliothek [libcurl], um eine Verbindung zu HTTP- und HTTPS-Seiten herzustellen. Für diese Bibliothek hatten wir die folgende Konfiguration verwendet:


// Initialisation d'a cURL session
  $curl = curl_init($url);
  if ($curl === FALSE) {
    // il y a eu une erreur
    return "Erreur lors de l'initialisation de la session cURL pour le site [$site]";
  }
  // options de curl
  $options = [
    // mode verbose
    CURLOPT_VERBOSE => true,
    // nouvelle connexion - pas de cache
    CURLOPT_FRESH_CONNECT => true,
    // timeout de la requête (en secondes)
    CURLOPT_TIMEOUT => $timeout,
    CURLOPT_CONNECTTIMEOUT => $timeout,
    // ne pas vérifier la validité des certificats SSL
    CURLOPT_SSL_VERIFYPEER => false,
    // suivre les redirections
    CURLOPT_FOLLOWLOCATION => true,
    // récupération du document demandé sous la forme d'a character string
    CURLOPT_RETURNTRANSFER => true
  ];
 
  // paramétrage de curl
curl_setopt_array($curl, $options);

Zeile 17: Die Konstante [CURLOPT_SSL_VERIFYPEER] steuert, ob das vom Server gesendete Zertifikat überprüft werden soll oder nicht. Der [HttpClient]-Client ist eigentlich ein [curl]-Client, wenn die [curl]-Erweiterung in der PHP-Konfiguration aktiviert ist, wie es hier der Fall ist. Die durch [HttpClient::create] instanziierte Klasse ist dann die Klasse [CurlHttpClient]. Die [curl]-Konstanten sind in dieser Klasse verfügbar, jedoch unter anderen Namen:

$curlopts = [
            CURLOPT_URL => $url,
            CURLOPT_USERAGENT => 'Symfony HttpClient/Curl',
            CURLOPT_TCP_NODELAY => true,
            CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
            CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 0 < $options['max_redirects'] ? $options['max_redirects'] : 0,
            CURLOPT_COOKIEFILE => '', // Keep track of cookies during redirects
            CURLOPT_CONNECTTIMEOUT_MS => 1000 * $options['timeout'],
            CURLOPT_PROXY => $options['proxy'],
            CURLOPT_NOPROXY => $options['no_proxy'] ?? $_SERVER['no_proxy'] ?? $_SERVER['NO_PROXY'] ?? '',
            CURLOPT_SSL_VERIFYPEER => $options['verify_peer'],
            CURLOPT_SSL_VERIFYHOST => $options['verify_host'] ? 2 : 0,
            CURLOPT_CAINFO => $options['cafile'],
            CURLOPT_CAPATH => $options['capath'],
            CURLOPT_SSL_CIPHER_LIST => $options['ciphers'],
            CURLOPT_SSLCERT => $options['local_cert'],
            CURLOPT_SSLKEY => $options['local_pk'],
            CURLOPT_KEYPASSWD => $options['passphrase'],
            CURLOPT_CERTINFO => $options['capture_peer_cert_chain'],
        ];

Wir haben die von [CurlHttpClient] verwendeten Konstanten gelb hervorgehoben.

Wenn wir nun den [auth-client]-Client mit dem Benutzer [admin, admin] ausführen, erhalten wir folgendes Ergebnis:


---Réponse avec statut : 200
---Entêtes de la réponse
date: Wed, 05 Jun 2019 10:44:37 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 0
connection: close
content-type: text/html; charset=UTF-8
---Réponse du serveur :

Der Benutzer wurde erfolgreich authentifiziert. Wenn wir den [auth-client]-Client mit einem anderen Benutzer als [admin, admin] ausführen, erhalten wir das folgende Ergebnis:

1
2
3
---Réponse avec statut : 403
---Entêtes de la réponse
Erreur de communication avec le serveur : HTTP/1.0 403 Forbidden returned for "https://localhost/php7/scripts-web/08/auth-server.php".

Jetzt wissen wir, wie man sich bei einem sicheren Server authentifiziert.