Skip to content

17. Webservices

Opmerking: onder ‘webservice’ verstaan we hier elke webapplicatie die onbewerkte gegevens levert die door een client worden gebruikt; in de volgende voorbeelden is dat een consolescript. We richten ons niet op een specifieke technologie, zoals bijvoorbeeld REST (REpresentational State Transfer) of SOAP (Simple Object Access Protocol), die min of meer ruwe gegevens in een welomschreven formaat leveren. REST levert jSON op, terwijl SOAP XML oplevert. Elk van deze technologieën beschrijft nauwkeurig hoe de client de server moet benaderen en welke vorm het antwoord van de server moet aannemen. In deze cursus zullen we veel flexibeler omgaan met de aard van het verzoek van de client en dat van het antwoord van de server. De geschreven scripts en de gebruikte tools komen echter dicht in de buurt van die van de REST-technologie.

17.1. Introduction

Aangezien PHP-programma’s door een WEB-server kunnen worden uitgevoerd, wordt een dergelijk programma een serverprogramma dat meerdere clients kan bedienen. Vanuit het perspectief van de client komt het aanroepen van een webservice neer op het opvragen van de URL van die service. De client kan in elke willekeurige programmeertaal worden geschreven, met name in PHP. In dat laatste geval maken we dan gebruik van de netwerkfuncties die we zojuist hebben besproken. We moeten bovendien weten hoe we met een webservice moeten „communiceren”, dat wil zeggen het http-communicatieprotocol tussen een WEB-server en zijn clients begrijpen. Dat was het doel van de paragraaf met de link.

Dankzij de in de paragraaf ‘link’ beschreven webclient hebben we een deel van het protocol HTTP kunnen ontdekken.

Image

In de meest eenvoudige versie verloopt de communicatie tussen client en server als volgt:

  • de client opent een verbinding met poort 80 van de webserver;
  • de client doet een verzoek om een document;
  • de webserver verstuurt het gevraagde document en verbreekt de verbinding;
  • de client verbreekt op zijn beurt de verbinding;

Het document kan van verschillende aard zijn: een tekst in het formaat HTML, een afbeelding, een video… Het kan een bestaand document zijn (statisch document) of een document dat direct door een script wordt gegenereerd (dynamisch document). In het laatste geval spreken we van webprogrammering. Het script voor het dynamisch genereren van documenten kan in verschillende talen worden geschreven: PHP, Python, Perl, Java, Ruby, C#, VB.net…

Hierna zullen we PHP-scripts gebruiken om dynamisch tekstdocumenten te genereren.

Image

  • in [1] opent de client een verbinding met de server, vraagt een script PHP aan en stuurt al dan niet parameters naar dit script;
  • in [2] laat de webserver het script PHP uitvoeren door de interpreter PHP. Het script genereert een document dat naar de client [3] wordt verzonden;
  • de server verbreekt de verbinding. De client doet hetzelfde;

De webserver kan meerdere clients tegelijkertijd verwerken.

Bij het softwarepakket [Laragon] is de webserver een Apache-server, een open-source-server van de Apache Foundation (http://www.apache.org/). In de volgende toepassingen moet [Laragon] worden gestart:

Image

Hiermee worden zowel de Apache-webserver als SGBD en MySQL gestart.

De scripts die door de webserver worden uitgevoerd, worden geschreven met de NetBeans-tool. Tot nu toe hebben we PHP-scripts geschreven die in een consoleconttext worden uitgevoerd:

Image

De gebruiker gebruikt de console om de uitvoering van een script PHP aan te vragen en de resultaten daarvan te ontvangen.

In de volgende client/server-toepassingen:

  • wordt het clientscript uitgevoerd in een consoleconttext;
  • wordt het serverscript uitgevoerd in een webcontext;

Image

Het serverscript PHP mag zich niet zomaar ergens in het bestandssysteem bevinden. De webserver zoekt namelijk op locaties die via de configuratie zijn opgegeven naar de statische en dynamische documenten waar om wordt gevraagd. De standaardconfiguratie van Laragon zorgt ervoor dat er naar documenten wordt gezocht in de map <Laragon>/www, waarbij <Laragon> de installatiemap van Laragon is. Als een webklant dus een document D opvraagt met het pad URL [http://localhost/D], zal de webserver het document D met het pad [<Laragon>/www/D] leveren.

In de volgende voorbeelden plaatsen we de serverscripts in de map [www/php7/scripts-web]. Als een serverscript S.php heet, wordt het bij de webserver opgevraagd via de paden URL en [http://localhost/php7/scripts-web/S.php]. Het document [<Laragon>/www/php7/scripts-web/S.php] wordt dan aan de server geleverd.

Image

  • in [1], de map [<laragon>/www];
  • in [2], het dossier [php7/scripts-web];

Om serverscripts te maken met NetBeans, gaan we als volgt te werk:

Image

  • in [1-2] maken we een nieuw project aan
  • in [3-4] kiezen we de categorie [PHP] en het project [PHP Application]

Image

  • in [5], de naam van het project;
  • in [6], de projectmap in het bestandssysteem. Let op: deze bevindt zich in de map [<laragon>/www], waar hij hoort te staan;
  • in [7-8], accepteer de voorgestelde standaardwaarden;
  • in [9-10]: accepteer de voorgestelde standaardwaarden. In [10]: houd er rekening mee dat de URL van de scripts die we in dit project zullen plaatsen, begint met het pad [http://localhost/php7/scripts-web/];

Image

  • in [11] worden u webframeworks aangeboden die in PHP zijn geschreven. Deze frameworks zijn onmisbaar zodra de webapplicatie wat omvang krijgt;
  • in [12] kun je PHP-bibliotheken toevoegen met behulp van de tool [Composer]. We hebben deze tool twee keer gebruikt in een [Terminal]-venster van Laragon:
    • om de bibliotheek [SwiftMailer] te installeren, waarmee e-mails kunnen worden verzonden;
    • om de bibliotheek [php-mime-mail-parser] te installeren, waarmee e-mails kunnen worden gelezen;
  • in [13] verschijnt het project, zodra de projectaanmaakwizard is bevestigd, in [13] op het tabblad ‘Projecten’;

17.2. Een statische pagina schrijven

Opmerking: Voor het vervolg moet [Laragon] zijn gestart.

We laten zien hoe je een statische pagina HTML (HyperText Markup Language) kunt maken met behulp van NetBeans:

Image

  • in [1-5] maken we een map aan met de naam [01];

Image

Image

  • in [6-12] maken we een bestand aan met de naam HTML [exemple-01.html];

Het bestand [exemple-01.html] wordt als volgt vooraf ingevuld gegenereerd (mei 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>

Laten we de inhoud ervan als volgt aanpassen:


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

We hebben de titel van de pagina (regel 4) en de inhoud (regel 9) gewijzigd.

Laten we nu deze pagina HTML door de Apache-server van Laragon weergeven:

Image

  • in [1-2], we laten de pagina weergeven door de Apache-server van Laragon;
  • in [3], de URL van de weergegeven pagina;
  • in [4], de titel die we hebben gewijzigd;
  • in [5], de inhoud die we hebben gewijzigd;

De weergegeven pagina is een statische pagina: deze kan zo vaak als gewenst in de browser worden geladen (F5), er wordt altijd dezelfde inhoud weergegeven.

De meeste browsers bieden toegang tot de gegevens die tussen de client en de server worden uitgewisseld, zoals beschreven in de paragraaf over links. Met de Firefox-browser (mei 2019) moet je F12 invoeren om toegang te krijgen tot deze gegevens:

Image

Zoals aangegeven in [1], laden we de pagina opnieuw (F5):

Image

  • in [2], het document dat door de browser is geladen: we selecteren het;

Image

  • in [5] is het te analyseren document geselecteerd;
  • in [3-4] vragen we om de communicatie tussen client en server te bekijken;
  • in [6], deze communicatie;

Image

  • in [7] selecteren we het tabblad 'Headers';
  • in [8], de door de browser opgevraagde URL;
  • in [9] is het naar de server verzonden commando [GET http://localhost/php7/scripts-web/01/exemple-01.html HTTP/1.1];
  • in [10] zijn de headers HTTP die vervolgens door de browser (de client) worden verzonden;
  • in [11], de headers HTTP van het antwoord van de server;

Image

  • in [12-14], het antwoord van de server dat na de headers HTTP is verzonden;
  • in [14] zien we dat de clientbrowser de pagina HTML heeft ontvangen die we hebben samengesteld. Vervolgens heeft de browser deze code geïnterpreteerd om het volgende weer te geven:

Image

17.3. Een dynamische pagina maken in PHP

We schrijven nu een dynamische pagina in PHP:

Image

Image

  • in [1-8] maken we een pagina [exemple-01.php];

Het bestand [exemple-01.php] wordt als volgt voorgeschreven gegenereerd (mei 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
        // plaats hier je code
        ?>
    </body>
</html>

We passen de bovenstaande code als volgt aan:


<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Exemple de page dynamique</title>
    </head>
    <body>
        <?php
        // tijd: aantal milliseconden tussen het huidige tijdstip en 01/01/1970
        // weergaveformaat voor datum en tijd
        // d: dag in twee cijfers
        // m: maand in twee cijfers
        // y: jaar in twee cijfers
        // H: uur 0,23
        // I: minuten
        // s: seconden
        print "<b>Date et heure du jour : </b>" . date("d/m/y H:i:s", time());
        ?>
    </body>
</html>

Opmerkingen

  • regel 5: we hebben de titel van de pagina gewijzigd;
  • regel 17: schrijft de huidige datum en tijd;

In principe schrijft het bovenstaande script PHP de huidige tijd naar de console. Wanneer het echter door een webserver wordt uitgevoerd, wordt de uitvoer van de instructie [print] – die gewoonlijk naar de uitvoerconsole van het script wordt gestuurd – hier omgeleid naar de verbinding tussen de server en de client. In een webcontext stuurt het bovenstaande script de huidige tijd dus als tekst naar de client, in dit geval een browser.

Laten we het script [exemple-01.php] uitvoeren:

Image

  • in [3], het aan de Apache-webserver aangevraagde URL;
  • in [4], de titel van de pagina die we hebben gewijzigd;
  • in [5], de inhoud die is gegenereerd door de instructie [print];

We hebben hier te maken met een dynamische pagina, want als we de pagina meerdere keren opnieuw laden in de browser (F5), verandert de inhoud ervan (de tijd verandert).

De browser heeft een stream HTML ontvangen. Om deze te bekijken, moet je de broncode van de pagina in de browser weergeven:

Image

  • om het menu [1] te openen, klik je met de rechtermuisknop op de pagina in de browser;
  • in [2], de URL van de pagina [exemple-01.php], maar voorafgegaan door [view-source :] [3];
  • in [4], de inhoud HTML die de browser heeft weergegeven;

We moeten dus in gedachten houden dat een PHP-script dat bedoeld is om door een webserver te worden uitgevoerd, een HTML-stream moet genereren.

Laten we nu eens kijken (F12) naar de headers HTTP die door de server naar de clientbrowser worden verzonden:

Image

  • in [3], een header HTTP die niet aanwezig was toen de statische pagina werd opgevraagd. Deze header geeft aan dat het antwoord van de server is gegenereerd door een script PHP;

We hebben gezien dat het antwoord (hier de stream HTML) van de server kan worden gegenereerd door een script PHP. Het script kan ook de headers HTTP en vrijwel alle elementen van het antwoord van de server genereren.

17.4. Basisbeginselen van de taal HTML

Dit hoofdstuk gaat niet uitgebreid in op het programmeren in WEB. Een webapplicatie in MVC wordt ontwikkeld in de paragraaf met de link. Dit hoofdstuk richt zich eerder op webservices: PHP-pagina’s die via een webserver gegevens leveren aan andere PHP-clients. Toch leek het ons nuttig om de lezer enkele basisbegrippen van HTML mee te geven.

Een webbrowser kan verschillende documenten weergeven, waarvan het meest gangbare het HTML-document (HyperText Markup Language) is. Dit is een tekst die is opgemaakt met tags in de vorm <balise>texte</balise>. Zo zal de tekst <b>important</b> de tekst important vetgedrukt weergeven. Er bestaan ook op zichzelf staande tags, zoals de tag <hr/> die een horizontale lijn weergeeft. We zullen niet ingaan op de tags die in een HTML-tekst kunnen voorkomen. Er bestaat veel WYSIWYG-software waarmee je een WEB-pagina kunt maken zonder ook maar één regel HTML-code te schrijven. Deze tools genereren automatisch de HTML-code van een lay-out die met de muis en vooraf gedefinieerde besturingselementen is gemaakt. Zo kun je (met de muis) een tabel in de pagina invoegen en vervolgens de door de software gegenereerde HTML-code bekijken om te ontdekken welke tags je moet gebruiken om een tabel in een WEB-pagina te definiëren. Eenvoudiger kan het niet. Bovendien is kennis van de taal HTML onmisbaar, aangezien dynamische webapplicaties zelf de code HTML moeten genereren die naar de clients WEB moet worden verzonden. Deze code wordt programmatisch gegenereerd en je moet natuurlijk weten wat er moet worden gegenereerd, zodat de klant de gewenste webpagina te zien krijgt.

Kortom, het is helemaal niet nodig om de volledige HTML-taal te beheersen om te beginnen met webprogrammeren. Deze kennis is echter wel noodzakelijk en kan worden opgedaan door het gebruik van WYSIWYG-software voor het bouwen van WEB-pagina’s, zoals DreamWeaver en tientallen andere. Een andere manier om de fijne kneepjes van de taal HTML te ontdekken, is door op het web te surfen en de broncode te bekijken van pagina’s die interessante en voor jou nog onbekende kenmerken vertonen.

Laten we eens kijken naar het volgende voorbeeld, dat enkele elementen laat zien die je in een WEB-document kunt aantreffen, zoals:

  • een tabel;
  • een afbeelding;
  • een link.

Image

Een HTML-document heeft de volgende algemene vorm:

<html> <head> <title>Een titel</title> ... </head> <body-attributen> ... </body></html>

Het gehele document wordt omgeven door de tags <html>…</html>. Het bestaat uit twee delen:

  1. <head>…</head>: dit is het niet-weergegeven deel van het document. Het geeft informatie aan de browser die het document gaat weergeven. Hierin staat vaak de tag <title>…</title>, die de tekst bepaalt die in de titelbalk van de browser wordt weergegeven. Er kunnen ook andere tags in staan, met name tags die de trefwoorden van het document definiëren, trefwoorden die vervolgens door zoekmachines worden gebruikt. In dit gedeelte kunnen ook scripts voorkomen, meestal geschreven in JavaScript of VBScript, die door de browser worden uitgevoerd.
  1. <body attributen>…</body>: dit is het gedeelte dat door de browser wordt weergegeven. De tags HTML in dit gedeelte geven aan de browser aan hoe het document er visueel „uit moet zien”. Elke browser interpreteert deze tags op zijn eigen manier. Twee browsers kunnen hetzelfde webdocument dus op verschillende manieren weergeven. Dit is doorgaans een van de hoofdbrekers voor webontwerpers.

De code HTML van ons voorbeelddocument is als volgt:


<!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>
Elément
tags en voorbeelden HTML
titre du document
<title>Enkele tags HTML</title> (regel 5)
de tekst [Quelques balises HTML] verschijnt in de titelbalk van de browser die het document weergeeft
barre horizontale
<hr />: geeft een horizontale lijn weer (regel 10)
tableau
<table attributen>….</table>: om de tabel te definiëren (regels 12, 32)
<thead>…</thead>: om de kolomkoppen te definiëren (regels 13, 19)
<tbody>…</tbody>: om de inhoud van de tabel te definiëren (regel 20, 31)
<tr attributen>…</tr>: om een rij te definiëren (regels 21, 25)
<td attributen>…</td>: om een cel te definiëren (regel 22)
voorbeelden:
<table border="1">…</table>: het attribuut border bepaalt de dikte van de rand van de tabel
<td style="text-align: center;">cel(1,2)</td> (regel 23): definieert een cel waarvan de inhoud cel(1,2) is. Deze inhoud wordt horizontaal gecentreerd (text-align: center).
image
<img border="0" src="images/cerisier.jpg"/> (regel 38): definieert een afbeelding zonder rand (border="0") waarvan het bronbestand [images/cerisier.jpg] op de webserver staat (src="images/cerisier.jpg"). Deze link staat in een webdocument dat is gegenereerd met de URL http://localhost/php7/scripts-web/01/balises.html. De browser zal dus de URL http://localhost/php7/scripts-web/01/images/cerisier.jpg opvragen om de hier waarnaar wordt verwezen afbeelding te verkrijgen.
lien
<a href="http://www.polytech-angers.fr/fr/index.html">hier</a> (regel 42): zorgt ervoor dat de tekst ici als link naar de URL http://www.polytech-angers.fr/fr/index.html fungeert.
fond de page
<body style="background-image: url(images/standard.jpg)"> (regel 8): geeft aan dat de afbeelding die als achtergrond voor de pagina moet dienen, zich bevindt op de URL [images/standard.jpg] van de server WEB. In het kader van ons voorbeeld zal de browser het bestand URL http://localhost/php7/scripts-web/01/images/standard.jpg opvragen om deze achtergrondafbeelding op te halen.

Uit dit eenvoudige voorbeeld blijkt dat de browser, om het volledige document op te bouwen, drie verzoeken naar de server moet sturen:

  1. http://localhost/php7/scripts-web/01/images/balises.html om de broncode HTML van het document op te halen
  2. http://localhost/php7/scripts-web/01/images/cerisier.jpg om de afbeelding cerisier.jpg op te halen
  3. http://localhost/php7/scripts-web/01/images/standard.jpg om de achtergrondafbeelding standard.jpg te verkrijgen

Dit blijkt uit de netwerkcommunicatie tussen de client en de server (F12 in de browser):

Image

  • in [3-5] zijn de drie verzoeken van de browser te zien;

17.5. Een statische pagina dynamisch maken

Laten we eens bekijken hoe we de pagina HTML [exemple-01.html] dynamisch kunnen maken. Laten we de inhoud kopiëren

Image

We hebben de inhoud van [exemple-01.html] gekopieerd naar het bestand [page-01.php]. Als we het webscript [2] uitvoeren, krijgen we het volgende te zien in de browser:

Image

  • in [3], het opgevraagde URL;
  • in [4], de titel van de pagina;
  • in [5], de inhoud van de pagina;

Als we de door de browser ontvangen code weergeven, zien we het volgende:

Image

  • in [7] staat de code HTML in het script [exemple-01.php]

De interpreter PHP heeft het script [page-01.php] geïnterpreteerd en dezelfde stream HTML gegenereerd als de statische pagina [exemple-01.html]. In het script [page-01.php] stond geen PHP, alleen HTML. Hieruit leren we het volgende: wanneer de interpreter PHP HTML aantreft in een script PHP, laat hij dit ongemoeid en stuurt het ongewijzigd door naar de client.

Laten we nu een paar PHP-instructies in het script [page-01.php] plaatsen, zodat de interpreter PHP iets te doen heeft:


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

Op regel 4 en 9 hebben we PHP-code toegevoegd om de titel en de inhoud van de pagina dynamisch te genereren. We gaan er hier vanuit dat de variabele [$page] een object is dat de weer te geven gegevens bevat.

Als we deze nieuwe code uitvoeren, krijgen we het volgende resultaat in de browser:

Image

  • in [1], de gevraagde URL;
  • in [2] kon de paginatitel niet worden weergegeven omdat de variabele [$page] niet was gedefinieerd;
  • in [3], hetzelfde geldt voor de inhoud;

Laten we nu het volgende webscript [exemple-02.php] schrijven:

Image

Het script [exemple-02.php] ziet er als volgt uit:


<?php

// we definiëren de elementen van de weer te geven pagina
$page=new \stdclass();
$page->title="Un nouveau titre";
$page->contents="Un nouveau contenu généré dynamiquement";
// we laten [page-01] weergeven
require_once "page-01.php";
  • regels 4-6: we definiëren het object [$page];
  • regel 8: het script [page-01.php] wordt opgenomen. De code van dit script wordt op zijn beurt geïnterpreteerd:
    • de variabele [$page] is nu gedefinieerd en de interpreter PHP zal deze gebruiken;
    • de code HTML van [page-01.php] wordt ongewijzigd naar de client verzonden;
    • de resultaten van de bewerkingen PHP en [print] worden opgenomen in de tekststroom die naar de klant wordt verzonden;

Als we nu het webscript [exemple-02.php] uitvoeren, krijgen we het volgende te zien in de browser:

Image

Als we de tekstinhoud bekijken die door de browser is ontvangen:

Image

  • zijn de codes PHP, die in [2] en [3] stonden, vervangen door de resultaten van de twee opdrachten [print];

Uit dit voorbeeld kunnen we twee dingen onthouden:

  • de HTML-pagina’s die bestemd zijn voor de browser kunnen worden geïsoleerd in PHP-scripts die alleen deze HTML-code bevatten en enkele dynamische delen die worden gegenereerd door PHP-code. Er moet zo min mogelijk PHP in deze pagina’s voorkomen;
  • alle logica die de dynamische gegevens genereert die in de HTML-pagina’s zijn opgenomen, moet worden geïsoleerd in zuivere PHP-scripts, die geen code bevatten voor de weergave van de pagina’s (HTML, CSS, JavaScript…) bevatten;

Dit maakt een scheiding van taken mogelijk:

  • de taak van het genereren van de weer te geven webpagina’s (HTML, CSS, JavaScript…);
  • de taak van de logica van de webapplicatie die we bouwen. Deze logica kan worden geïmplementeerd met een drielaagse architectuur, precies zoals we dat hebben gedaan met de consolescripts;

Vervolgens gaan we specifieke webscripts bouwen;

  • deze zullen alleen gegevens naar de client sturen en geen opmaak (HTML, CSS, Javascript). Het zullen dus eerder gegevensservers zijn dan webpagina’s;
  • de clients van deze webscripts zullen consolescripts zijn die de door de server verzonden gegevens ophalen en er iets mee doen;

17.6. Client/server-toepassing voor datum en tijd

We gaan nu uit van de volgende configuratie:

Image

We gaan het volgende schrijven:

  • een webscript [1] dat de huidige datum en tijd naar zijn client verstuurt;
  • een consolescript [2] dat als client voor het webscript fungeert: het haalt de door het webscript verzonden datum en tijd op en geeft deze weer op de console;

Image

  • in [1], het webscript [date-time-server.php];
  • in [2], het consolescript [date-time-client], de client van het webscript;

17.6.1. Het serverscript

We hebben al een webscript geschreven dat de huidige datum en tijd genereert, zoals beschreven in de link hierboven. Dat was het volgende script [exemple-01.php]:


<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Exemple de page dynamique</title>
    </head>
    <body>
        <?php
        // time: aantal milliseconden sinds 01/01/1970
        // weergaveformaat voor datum en tijd
        // d: dag in twee cijfers
        // m: maand (2 cijfers)
        // y: jaar (2 cijfers)
        // H: uur 0,23
        // i: minuten
        // s: seconden
        print "<b>Date et heure du jour : </b>" . date("d/m/y H:i:s", time());
        ?>
    </body>
</html>

We hebben gezegd dat we gegevensservers zouden schrijven: ruwe gegevens zonder opmaak HTML. Het serverscript [date-time-server.php] ziet er dan als volgt uit:


<?php

// de header wordt vastgelegd HTP [Content-Type]
header('Content-Type: text/plain; charset=UTF-8');
//
// we verzenden datum en tijd
// tijd: aantal milliseconden sinds 01/01/1970
// weergaveformaat voor datum en tijd
// d: dag in twee cijfers
// m: maand in twee cijfers
// y: jaar (2 cijfers)
// H: uur 0,23
// i: minuten
// s: seconden
print date("d/m/y H:i:s", time());
  • regel 4: we stellen de header HTTP [Content-Type] in, die de client informeert over de aard van het document dat hij zal ontvangen. Tot nu toe was de [Content-Type]: [Content-Type: text/html; charset=UTF-8]. Hier geven we aan de client door dat het document platte tekst is: HTML. Dit is niet van belang voor onze console-client, die deze header niet zal gebruiken. Het is wel van belang voor client-browsers, die deze header wel gebruiken;

Laten we dit serverscript uitvoeren:

Image

Als we in de browser het antwoord van de server (F12) bekijken, zien we in [5] de header HTTP die het serverscript heeft ingesteld en in [8] het ontvangen tekstdocument;

Image

17.6.2. Het clientscript

In de paragraaf over koppelingen hebben we verschillende clients HTTP ontwikkeld. We zouden deze kunnen gebruiken om het tekstdocument op te halen dat door het serverscript [date-time-server.php] is verzonden. Dat gaan we echter niet doen. Net zoals we hebben gedaan voor de protocollen SMTP en IMAP, gaan we een bibliotheek van een derde partij gebruiken, namelijk de component [HttpClient] van het Symfony-framework [https://symfony.com/doc/master/components/http_client.html].

Net als bij de twee voorgaande bibliotheken gebruiken we de tool [Composer] om de Symfony-component [HttpClient] te installeren. In een Laragon-venster (zie paragraaf ‘link’) voeren we de volgende opdracht in:

Image

  • in [3]: controleer of u zich in de map [<laragon>/www/] bevindt, waarbij <laragon> de installatiemap van Laragon is;
  • in [4], de opdracht [composer], waarmee de Symfony-bibliotheek [HttpClient] wordt geïnstalleerd;
  • in [5] wordt er niets geïnstalleerd, omdat de bibliotheek [HttpClient] al op deze computer was geïnstalleerd;
  • bij [6-7] verschijnen er nieuwe mappen in [<laragon>/www/vendor/symfony];

In plaats van [5] zou u iets als het volgende moeten zien:


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

Zorg ervoor dat de map [<laragon>/www/vendor] deel uitmaakt van de tak [Include Path] van uw project (zie paragraaf 'link'):

Image

Nu dit is gebeurd, kunnen we het consolescript [date-time-client.php] schrijven:

Image

Het consolescript [date-time-client.php] zal het volgende bestand jSON [config-date-time-client.json] verwerken:

1
2
3
{
    "url": "http://localhost/php7/scripts-web/02/date-time-server.php"
}
  • regel 2: het URL van het serverscript;

Het clientscript [date-time-client.php] ziet er als volgt uit:


<?php

// klant van de datum-/tijddienst
//
// foutbeheer
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// afhankelijkheden
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// de configuratie van de client
const CONFIG_FILE_NAME = "config-date-time-client.json";

// de configuratie wordt opgehaald
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;
}

// een client aanmaken HTTP
$httpClient = HttpClient::create();

try {
  // de aanvraag wordt verzonden
  $response = $httpClient->request('GET', $config['url']);
  // status van het antwoord
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // de headers worden opgehaald
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // de hoofdtekst van het antwoord wordt opgehaald
  $content = $response->getContent();
  // we geven het weer
  print "---Réponse du serveur : [$content]\n";
} catch (TypeError | RuntimeException $ex) {
  // er wordt een foutmelding weergegeven
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
  exit;
}

Opmerkingen

  • regel 10: net zoals bij de vorige bibliotheken laden we het bestand [<laragon>/www/vendor/autoload.php];
  • regel 11: we definiëren de klasse [HttpClient] die we gaan gebruiken;
  • regels 13-24: we halen de configuratie van het script op uit het woordenboek [$config];
  • regel 27: we maken een object van het type [HttpClient] aan;
  • regel 31: we vragen de URL van het serverscript op met behulp van een commando GET: [GET URL HTTTP/1.1]. Deze bewerking is asynchroon. De uitvoering gaat verder in regel 33 zonder te wachten tot het antwoord is ontvangen;
  • regel 33: de status van het antwoord wordt opgevraagd. Deze status staat in de eerste header HTTP die door de server wordt teruggestuurd. Als deze header dus [HTTP/1.1 200 OK] is, is de status van het antwoord 200. Deze bewerking is blokkerend: men keert pas terug wanneer de client het volledige antwoord van de server heeft ontvangen;
  • regel 37: de headers HTTP van het antwoord worden opgevraagd;
  • regel 42: het door de server teruggestuurde document wordt opgevraagd: we weten dat dit document hier een tekst is.
  • regels 45-49: in geval van een fout wordt het foutbericht weergegeven;

Wanneer het clientscript wordt uitgevoerd (Laragon moet gestart zijn om het serverscript te kunnen bereiken), krijgt men het volgende resultaat op de console:


---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 regel 8 worden de huidige datum en tijd correct opgehaald.

Misschien ben je benieuwd wat het clientscript naar de server heeft gestuurd. Hiervoor gebruiken we onze generieke server TCP (zie paragraaf ‘link’):

Image

  • in [1], de map met hulpprogramma's;
  • in [2] wordt de server TCP gestart op poort 100;
  • in [3], wachten op een commando dat via het toetsenbord wordt ingevoerd;

We wijzigen het configuratiebestand van het script [date-time-client.php]:


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

Deze keer neemt de client contact op met de server [localhost] op poort 100. Er wordt dus een verzoek gedaan aan onze generieke server TCP. Wanneer we het consolescript [date-time-client.php] uitvoeren, verandert de console van de generieke server TCP als volgt:

Image

  • naar [3], de opdracht HTTP GET die door het clientscript is samengesteld;
  • in [4], de handtekening van het consolescript;
  • in [5], het antwoord van de server op het clientscript. Merk op dat dit geen geldig antwoord is: HTTP:
    • er zouden headers moeten zijn HTTP;
    • gevolgd door een lege regel;
    • daarna het tekstdocument dat naar de client wordt verzonden;
  • in [6] wordt de communicatie met het clientscript beëindigd, zodat het script detecteert dat het het volledige antwoord heeft ontvangen;

Aan de kant van het clientscript zien we de volgende console-uitvoer:

Image

  • in [7], wat de Symfony-client heeft ontvangen;

17.6.3. Het serverscript – versie 2

Standaard zijn de functies PHP voor het schrijven van een webscript niet objectgeoriënteerd. Aan de serverzijde moeten we daarom klassieke klassen en functies PHP door elkaar gebruiken. Om een meer uniforme schrijfstijl te verkrijgen, gaan we de bibliotheek [HttpFoundation] van het Symfony-framework gebruiken. Deze bibliotheek heeft alle klassieke PHP-functies voor een webservice ingekapseld in een systeem van klassen en interfaces. De documentatie van de bibliotheek is beschikbaar op de URL [https://symfony.com/doc/current/components/http_foundation.html] (mei 2019).

Om de bibliotheek te installeren, gaan we als volgt te werk in een Laragon-terminal (zie paragraaf ‘link’):

Image

  • [2-3]: zorg ervoor dat u zich in de map [<laragon>/www] bevindt;
  • [4]: het commando [composer] waarmee de bibliotheek [HttpFoundation] wordt geïnstalleerd;
  • [5]: in dit voorbeeld was de bibliotheek al geïnstalleerd;

Bij de eerste installatie zou u console-logs moeten zien die er ongeveer zo uitzien:


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

De tweede versie van de webserver [date-time-server-2.php] is als volgt:


<?php

// gebruik van Symfony-bibliotheken

// afhankelijkheden
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpFoundation\Response;

// de Content-Type-header wordt ingesteld
$response=new Response();
$response->headers->set("content-type","text/plain");
$response->setCharset("utf-8");

// de inhoud van het antwoord wordt ingesteld
//
// we sturen datum en tijd mee
// tijd: aantal milliseconden sinds 01/01/1970
// weergaveformaat voor datum en tijd
// d: dag in twee cijfers
// m: maand in twee cijfers
// y: jaar (2 cijfers)
// H: uur 0,23
// i: minuten
// s: seconden
$response->setContent(date("d/m/y H:i:s", time()));

// het antwoord wordt verzonden
$response->send();

Opmerkingen

  • regel 7: de klasse [Response] uit de Symfony-bibliotheek [HttpFoundation] beheert het volledige antwoord aan de clients van de webservice;
  • regel 10: aanmaken van een instantie van de klasse [Response];
  • regel 11: er wordt aangegeven dat het antwoord van het type [text/plain] is;
  • regel 12: het antwoord bestaat uit de tekst UTF-8;
  • regel 25: het antwoorddocument wordt vastgelegd, zoals gevraagd door de klant;
  • regel 28: het antwoord wordt naar de klant verzonden;

17.6.4. Het clientscript – versie 2

Het clientscript blijft ongewijzigd. Alleen het configuratiebestand [config-date-time-client.json] wordt aangepast:

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

De resultaten zijn hetzelfde als in versie 1.

17.7. Een gegevensserver jSON

Het antwoord van een webscript kan uit verschillende gegevens bestaan die in tabellen en objecten kunnen worden gegroepeerd. Het script kan deze verschillende elementen vervolgens in een string jSON verzenden, die de client vervolgens zal decoderen.

Image

17.7.1. Het serverscript

Het script [json-server.php] maakt gebruik van de volgende klasse [Personne]:


<?php

namespace Modèles;

class Personne implements \JsonSerializable {
  // attributen
  private $nom;
  private $prénom;
  private $âge;

  // conversie van een associatieve array naar een object [Personne]
  public function setFromArray(array $assoc): Personne {
    // het huidige object wordt geïnitialiseerd met de associatieve array
    foreach ($assoc as $attribute => $value) {
      $this->$attribute = $value;
    }
    // resultaat
    return $this;
  }

  // getters en 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]";
  }

  // implementeert de interface JsonSerializable
  public function jsonSerialize(): array {
    // er wordt een associatieve array geretourneerd met de attributen van het object als sleutels
    // dit array kan vervolgens worden gecodeerd in jSON
    return get_object_vars($this);
  }

  // conversie van een jSON naar een object [Personne]
  public static function jsonUnserialize(string $json): Personne {
    // er wordt een persoon aangemaakt op basis van de tekenreeks jSON
    return (new Personne())->setFromArray(json_decode($json, true));
  }

}

Opmerkingen

  • regel 5: de klasse implementeert de interface PHP [JsonSerializable]. Hierdoor moet de klasse de methode [jsonSerialize] op regels 55-59 implementeren. De methode moet een associatief array retourneren dat moet worden geserialiseerd in jSON. Wanneer de uitdrukking [json_encode($personne)] wordt gebruikt, controleert de functie [json_encode] of de klasse [Personne] de interface [JsonSerializable] implementeert. Zo ja, dan wordt de uitdrukking [json_encode($personne→serialize())];
  • regels 12-19: de klasse heeft geen constructor, maar wel een initialisator. De klasse [Personne] kan dan worden geïnstantieerd met de uitdrukking [(new Personne())→setFromArray($array)]. Er kunnen verschillende soorten initialisatoren zijn, terwijl er slechts één constructor kan zijn. Deze initialisatoren maken verschillende manieren van instantiëren van het type [(new Personne())→initialiseuri(…)] mogelijk;
  • regels 62-65: met de statische functie [jsonUnserialize] kan een object [Personne] worden aangemaakt op basis van de tekenreeks jSON;

Het script [json-server.php] ziet er als volgt uit:


<?php

// afhankelijkheden
require_once __DIR__ . "/Personne.php";
use \Modèles\Personne;
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;

// we stellen de Content-Type-header en de gebruikte tekenset in
$response = new Response();
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");

// er wordt een Person-object aangemaakt
$personne = (new Personne())->setFromArray([
  "nom" => "de la Hûche",
  "prénom" => "jean-paul",
  "âge" => 27]);
// een associatieve array
$assoc = ["attr1" => "value1",
  "attr2" => [
    "prenom" => "Jean-Paul",
    "nom" => "de la Hûche"
  ]
];
// de inhoud van het antwoord is van het type jSON
$response->setContent(json_encode([$personne, $assoc]));

// het antwoord wordt verzonden
$response->send();

Opmerkingen

  • regels 4-5: de klasse [Personne] wordt geïmporteerd;
  • regel 11: er wordt aangegeven dat het document van het type [application/json] zal zijn. Bij ontvangst van deze header zullen browsers de tekenreeks jSON opgemaakt weergeven in plaats van platte tekst;
  • regel 12: de tekenreeks jSON bevat de tekens UTF-8;
  • regels 15-18: er wordt een object [Personne] aangemaakt;
  • regels 20-25: er wordt een associatieve array met twee niveaus aangemaakt;
  • regel 27: de tekenreeks jSON uit een array wordt naar de client verzonden:
    • het element [$personne] wordt geserialiseerd tot jSON met behulp van de methode [jsonSerialize];
    • het element [$assoc] wordt standaard geserialiseerd tot jSON;

Als dit serverscript wordt uitgevoerd (Laragon moet gestart zijn), krijgt men de volgende respons in een browser:

Image

Image

Opmerkingen

  • in [2], het opgemaakte antwoord jSON;
  • in [4], het onbewerkte antwoord jSON. Let op de codering van de tekens met accenten;
  • in [6] is het de inhoudstype [application/json] dat door de server is verzonden en dat de browser ertoe heeft aangezet deze opmaak toe te passen;

17.7.2. De client

Image

De client [json-client.php] wordt geconfigureerd door het volgende bestand jSON [config-json-client.json]:

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

Het script [json-client.php] is als volgt:


<?php

// client van een service jSON
//
// foutbeheer
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// afhankelijkheden
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
require_once __DIR__ . "/Personne.php";
use \Modèles\Personne;

// de configuratie van de client
const CONFIG_FILE_NAME = "config-json-client.json";

// de configuratie wordt opgehaald
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;
}

// een klant aanmaken HTTP
$httpClient = HttpClient::create();

try {
  // de aanvraag wordt verzonden
  $response = $httpClient->request('GET', $config['url']);
  // status van het antwoord
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // de headers worden opgehaald
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // de hoofdtekst jSON van het antwoord wordt opgehaald
  list($personne, $assoc) = json_decode($response->getContent(), true);
  // we maken een persoon aan op basis van de tabel met zijn/haar kenmerken
  $personne = (new Personne())->setFromArray($personne);
  // het antwoord van de server wordt weergegeven
  print "---Réponse du serveur\n";
  print "$personne\n";
  print "tableau=" . json_encode($assoc, JSON_UNESCAPED_UNICODE) . "\n";
} catch (TypeError | RuntimeException $ex) {
  // de fout wordt weergegeven
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}


Opmerkingen

  • regels 12-13: import van de klasse [Personne];
  • regel 30: aanmaken van de client HTTP;
  • regel 44: de door de server verzonden tekenreeks jSON wordt gedecodeerd. We weten dat het gecodeerde een array met twee elementen is, bestaande uit twee associatieve arrays;
  • regel 46: er wordt een object [Personne] aangemaakt om dit vervolgens op regel 49 weer te geven;
  • regel 50: we geven de tweede associatieve array weer. De instructie [print] kan geen arrays weergeven. Daarom zetten we deze om in de tekenreeks jSON. Om de tekens met accenten correct weer te geven, moet de tweede parameter [JSON_UNESCAPED_UNICODE] worden gebruikt. We hebben gezien dat de tekens met accenten inderdaad zijn gecodeerd in de tekenreeks jSON;

Het uitvoeren van het clientscript levert de volgende resultaten op:


---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"}}

In de regels 11 en 12 zijn de tekens met accenten correct weergegeven.

17.8. Ophalen van de omgevingsvariabelen van de webservice

Een serverscript wordt uitgevoerd in een webomgeving die het kan herkennen. Deze omgeving is opgeslagen in het woordenboek $_SERVER, een globale variabele van PHP. Als we de bibliotheek [HttpFoundation] gebruiken, wordt deze omgeving gevonden in het veld [Request→server], waarbij [Request] de aanvraag is die door het webscript wordt verwerkt.

17.8.1. Het serverscript

We schrijven een servertoepassing die haar uitvoeringsomgeving naar haar clients verstuurt.

Image

Het webscript [env-server.php] ziet er als volgt uit:


<?php

// afhankelijkheden
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;

// het verzoek wordt opgehaald
$request = Request::createFromGlobals();
// het antwoord wordt opgesteld
$response = new Response();
// de inhoud van het antwoord is JSON in UTF-8
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
// de inhoud jSON van het antwoord wordt vastgelegd
$response->setContent(json_encode($request->server->all()));
// het antwoord wordt verzonden
$response->send();
  • regel 9: we halen het object van het type [Request] op, dat alle beschikbare informatie bevat over het verzoek HTTP dat door het webscript is ontvangen, evenals over de uitvoeringsomgeving ervan;
  • regels 13-14: er wordt platte tekst met UTF-8-tekens naar de client verzonden;
  • regel 16: de informatie die naar de client wordt verzonden, is een tekenreeks die is verkregen door het object [$request→server→all()] te serialiseren met jSON: [$request→server] vertegenwoordigt de uitvoeringsomgeving van het webscript. Dit is een object van het type [ServerBag], een soort woordenboek. [$request→server→all()] is daarentegen een echt woordenboek, namelijk dat van de inhoud van [ServerBag];
  • regel 18: de informatie wordt verzonden;

Als dit script vanuit NetBeans wordt uitgevoerd, geeft de browser de volgende pagina weer:

Image

  • in [2], de verschillende sleutels van het woordenboek van de omgeving;
  • in [3], de waarden van deze sleutels;

17.8.2. Het clientscript

Image

Het clientscript [env-client.php] wordt geconfigureerd door het volgende bestand: jSON [config-env-client.json]:

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

Het clientscript [env-client.php] is als volgt:


<?php

// omgeving van een serverscript
//
// foutafhandeling
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// afhankelijkheden
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// de configuratie van de client
const CONFIG_FILE_NAME = "config-env-client.json";

// de configuratie wordt opgehaald
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;
}

// een client aanmaken HTTP
$httpClient = HttpClient::create();
try {
  // er wordt een verzoek naar de server gestuurd
  $response = $httpClient->request('GET', $config['url']);
  // status van het antwoord
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // de headers worden opgehaald
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // het antwoord van de server wordt weergegeven
  print "---Réponse du serveur\n";
  $env = json_decode($response->getContent());
  foreach ($env as $key => $value) {
    print "[$key]=>$value\n";
  }
} catch (TypeError | RuntimeException $ex) {
  // de fout wordt weergegeven
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Opmerkingen

  • regel 42: het antwoord jSON van de server wordt gedeserialiseerd. Dit levert een associatieve array op;
  • regels 43-45: alle waarden van dit associatieve array worden weergegeven;

We krijgen de volgende uitvoer op de console:


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

Hieronder volgt de betekenis van enkele variabelen (voor Windows. Onder Linux zouden deze anders zijn):

HTTP_HOST
de waarde xxx van de header HTTP [Host: xxx] die door de client is verzonden
HTTP_USER_AGENT
de waarde xxx van de header HTTP [User_Agent: xxx], verzonden door de client
HTTP_ACCEPT_ENCODING
de waarde xxx van de header HTTP [Accept-Encoding: xxx] verzonden door de klant
PATH
het pad naar de uitvoerbare bestanden op de machine waarop het serverscript wordt uitgevoerd
COMSPEC
het pad naar de command-interpreter DOS
PATHEXT
de extensies van de uitvoerbare bestanden
WINDIR
de Windows-installatiemap
SERVER_SIGNATURE
de handtekening van de webserver. Hier niets.
SERVER_SOFTWARE
het type webserver
SERVER_NAME
de internetnaam van de webserver
SERVER_PORT
de luisterpoort van de webserver
SERVER_ADDR
het adres IP van de webserver, hier 127:0:0:1
REMOTE_ADDR
het adres IP van de client. In dit geval bevond de client zich op dezelfde machine als de server.
REMOTE_PORT
de communicatiepoort van de client
DOCUMENT_ROOT
de root van de boomstructuur van de documenten die door de webserver worden aangeboden
REQUEST_SCHEME
het protocol TCP van het verzoek van URL http://localhost/php7/…
SERVER_ADMIN
het e-mailadres van de beheerder van de webserver
SCRIPT_FILENAME
het volledige pad naar het serverscript
REMOTE_PORT
de poort van waaruit de client zijn verzoek heeft verzonden
SERVER_PROTOCOL
de versie van het protocol HTTP dat door de webserver wordt gebruikt
REQUEST_METHOD
de volgorde die door de klant wordt gebruikt. Er zijn er vier: GET, POST, PUT, DELETE
QUERY_STRING
de parameters die met een opdracht worden verzonden: GET /url?parameters
REQUEST_URI
de door de klant aangevraagde URL. Als de browser de URL http://machine[:port]/uri aanvraagt, krijgen we REQUEST_URI=uri
SCRIPT_NAME
$_SERVER['SCRIPT_FILENAME']=$_SERVER['DOCUMENT_ROOT'].$_SERVER['SCRIPT_NAME']

17.9. Het ophalen door de server van parameters die door een client zijn verzonden

17.9.1. Inleiding

In het protocol HTTP heeft een client twee manieren om parameters door te geven aan de server WEB:

  • hij vraagt de URL-service aan in de vorm van

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

waarbij de waarden vali eerst moeten worden gecodeerd, zodat bepaalde gereserveerde tekens worden vervangen door hun hexadecimale waarde;

  • hij vraagt de URL van de dienst in de vorm

POST url HTTP/1.0

en voegt vervolgens onder de naar de server verzonden headers HTTP de volgende header toe:


Content-length=N

De rest van de door de client verzonden headers eindigt met een lege regel. Vervolgens kan hij zijn gegevens verzenden in de vorm


val1&param2=val2&param3=val3…

waarbij de waarden vali, net als bij de methode GET, vooraf moeten worden gecodeerd. Het aantal tekens dat naar de server wordt verzonden, moet N zijn, waarbij N de waarde is die is opgegeven in de header


Content-length=N

Het script PHP van de webservice, dat de eerder door de client verzonden parameters parami ophaalt, haalt de waarden daarvan uit de tabel:

  • $_GET["parami"] voor een opdracht GET;
  • $_POST["parami"] voor een opdracht POST;

dit geldt voor de basisfuncties van PHP. Als de bibliotheek [HttpFoundation] wordt gebruikt, zijn deze parameters te vinden in:

  • [Request]->query->get('parami') voor een commando GET;
  • [Request]->request->get('parami') voor een opdracht POST;

waarbij [Request] alle informatie vertegenwoordigt over het verzoek dat door het webscript is ontvangen;

17.9.2. De client GET – versie 1

Image

De clientscripts worden geconfigureerd via het volgende bestand jSON [config-parameters-client.json]:

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"
}
  • regel 1: het URL van het doelscripts van de clients GET;
  • regel 2: de URL van het webscript dat de doelcliënt POST aanstuurt;

De clients GET sturen drie parameters [nom, prenom, age] naar de server. De client [parameters-get-client.php] is als volgt:


<?php

// GET-client van een webserver
//
// foutafhandeling
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// afhankelijkheden
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// de configuratie van de client
const CONFIG_FILE_NAME = "config-parameters-client.json";

// de configuratie wordt opgehaald
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;
}

// een client aanmaken HTTP
$httpClient = HttpClient::create();

try {
  // de instellingen worden voorbereid
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
// de gegevens worden gecodeerd
  $parameters = "prenom=" . urlencode($prenom) .
    "&nom=" . urlencode($nom) .
    "&age=$age”;
  // de aanvraag wordt verzonden
  $response = $httpClient->request('GET', $config['url-get'] . "?$parameters");
  // status van het antwoord
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // de headers worden opgehaald
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // het antwoord van de server wordt weergegeven
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  // er wordt een foutmelding weergegeven
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Opmerkingen

  • regels 33-35: codering van de naar de server verzonden parameters. De parameters [$prenom, $nom], die tekens UTF-8 kunnen bevatten, worden gecodeerd met de functie [urlencode]. Alle niet-alfanumerieke tekens (in de zin van relationele uitdrukkingen) worden vervangen door %xx, waarbij xx de hexadecimale waarde van het teken is. Spaties worden vervangen door het teken +;
  • regel 37: de gevraagde URL is $URL?$parameters, waarbij $parameters de vorm nom=val1&prenom=val2&age=val3 heeft;
  • regel 48: de client zal zich beperken tot het weergeven van het antwoord van de client;

Misschien ben je wel benieuwd wat de server ontvangt bij een geconfigureerd verzoek GET. Hiervoor starten we onze generieke server [RawTcpServer] op poort 100 van de lokale machine vanuit een Laragon-terminal (zie paragraaf ‘link’):

Image

Controleer of u in [4] daadwerkelijk in de map met hulpprogramma’s bent.

We wijzigen het bestand jSON [parameters-get-client.json] dat de clients GET en POST configureert:


{
    "url-get": "http://localhost:100/php7/scripts-web/05/parameters-server.php",
    "url-post": "http://localhost/php7/scripts-web/05/parameters-server.php"
}
  • regel 2: we hebben de poort van de webserver gewijzigd. Er wordt dus contact opgenomen met [RawTcpServer];

We voeren de client uit. In het venster van [RawTcpServer] krijgen we de volgende informatie te zien:

Image

  • in [1], de door de client verzonden, geconfigureerde opdracht GET. De codering van bepaalde tekens is duidelijk zichtbaar;

17.9.3. De server GET / POST

Image

Het serverscript [parameters-server.php] is als volgt:


<?php

// afhankelijkheden
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;

// de query wordt opgehaald
$request = Request::createFromGlobals();
// de parameters van het verzoek worden opgehaald
$getParameters = $request->query->all();
$bodyParameters = $request->request->all();

// het antwoord wordt opgesteld
$response = new Response();
// de inhoud van het antwoord is UTF-8-tekst
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
// inhoud van het antwoord – een tabel gecodeerd in jSON
$response->setContent(json_encode([
  "method" => $request->getMethod(),
  "uri" => $request->getRequestUri(),
  "getParameters" => $getParameters,
  "bodyParameters" => $bodyParameters
    ], JSON_UNESCAPED_UNICODE));
// het antwoord wordt verzonden
$response->send();

Opmerkingen

  • regel 9: aanmaak van het object [Request] van het webscript. Dit object bevat alle informatie die het webscript van de client heeft ontvangen;
  • regel 11: het object [Request→query] is van het type [ParameterBag] en bevat de parameters van de eventuele bewerking GET van een klant. Met de uitdrukking [Request→query→get(«X»)] kan de parameter met de naam X worden opgehaald uit de parameters van GET [nom=val1&prenom=val2&age=val3]. Met de uitdrukking [Request→query→all()] kan het parameterwoordenboek van GET worden opgehaald;
  • regel 12: het object [Request→request] is van het type [ParameterBag] en bevat de parameters die als document door de client naar de server worden verzonden. Van deze parameters wordt ook gezegd dat ze worden geüpload, omdat ze deel uitmaken van een document dat de client naar de server verzendt. Met de uitdrukking [Request→request→get(«X»)] kan de parameter met de naam X worden opgehaald uit de geüploade parameters [nom=val1&prenom=val2&age=val3]. Met de uitdrukking [Request→request→all()] kan het woordenboek van de geüploade parameters worden opgehaald;
  • regels 17-18: de client wordt geïnformeerd dat er jSON, gecodeerd in UTF-8, naar hem wordt verzonden;
  • regels 20-25: de server stuurt alle ontvangen parameters terug naar de client, evenals het type bewerking [GET / POST / …] dat door de client is uitgevoerd en de gevraagde URI. Deze methode wordt verkregen via de uitdrukking [$request→getMethod()]. Het document dat naar de client wordt verzonden, is de tekenreeks jSON van een associatieve array waarvan sommige waarden zelf weer associatieve arrays zijn. De parameter [JSON_UNESCAPED_UNICODE] zorgt ervoor dat Unicode-tekens (zoals bijvoorbeeld tekens met accenten) ongewijzigd en niet gecodeerd worden verzonden;
  • regel 27: het antwoord wordt naar de client verzonden;

De uitvoering van het clientscript levert de volgende resultaten op:

---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":[]}]
  • regel 10:
    • [method]: de methode is GET;
    • [uri]: we zien de URL-gecodeerde parameters van het verzoek GET in het aangevraagde URI;
    • [getParameters]: de parameterlijst van GET;
    • [bodyParameters]: de tabel met geüploade parameters: deze is leeg;

17.9.4. De client GET – versie 2

In de vorige versie van het clientscript hebben we de naar de server verzonden parameters zelf url-gecodeerd, met het oog op de uitleg. Het object [HttpClient] kan dit werk zelf uitvoeren. Dit is het volgende script [parameters-get-client-2.php]:


<?php

// GET-client van een webserver
//
// foutafhandeling
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// afhankelijkheden
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// de configuratie van de client
const CONFIG_FILE_NAME = "config-parameters-client.json";

// de configuratie wordt opgehaald
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;
}

// een client aanmaken HTTP
$httpClient = HttpClient::create();
try {
  // de instellingen voorbereiden
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
  // de aanvraag naar de server verzenden
  $response = $httpClient->request('GET', $config['url-get'],
    ["query" => [
        "prenom" => $prenom,
        "nom" => $nom,
        "age" => $age
  ]]);
  // status van het antwoord
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // de headers worden opgehaald
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // het antwoord van de server wordt weergegeven
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  // de fout wordt weergegeven
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Opmerkingen

  • regels 33-37: het toevoegen van parameters aan het verzoek GET uit regel 32. Het object [HttpClient] zorgt zelf voor de codering van URL;

17.9.5. De client POST

Een client HTTP stuurt de volgende tekstreeks naar de webserver: headers HTTP, lege regel, document. In de vorige client was deze reeks als volgt:

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

Er was geen document. Er bestaat nog een andere manier om parameters door te geven, de zogenaamde POST-methode. In dit geval is de tekstreeks die naar de webserver wordt verzonden als volgt:

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

Deze keer maken de parameters die bij de client GET in de headers HTTP waren opgenomen, bij de client POST deel uit van het document dat na de headers wordt verzonden.

Het script van de client POST [parameters-postclient.php] is als volgt:


<?php

// POST-client van een webserver
//
// foutafhandeling
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// afhankelijkheden
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// de configuratie van de client
const CONFIG_FILE_NAME = "config-parameters-client.json";

// de configuratie wordt opgehaald
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;
}

// een client aanmaken HTTP
$httpClient = HttpClient::create();
try {
  // de instellingen voorbereiden
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
  // de aanvraag naar de server verzenden
  $response = $httpClient->request('POST', $config['url-post'],
    ["body" => [
        "prenom" => $prenom,
        "nom" => $nom,
        "age" => $age
  ]]);
  // status van het antwoord
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // de headers worden opgehaald
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // het antwoord van de server wordt weergegeven
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  // de fout wordt weergegeven
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}
  • regel 32: we hebben nu een verzoek HTTP van het type POST;
  • regels 33-37: de parameters van POST worden de body van de verzoek POST genoemd: dit is het document dat door de client naar de server wordt verzonden. Hier worden drie parameters verzonden: [nom, prenom, age];
  • regel 48: het antwoord jSON van de server wordt weergegeven;

De resultaten van de uitvoering van het clientscript zijn als volgt:


---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"}}]
  • regel 10: de methode is [Post] en de parameters zijn van het type [bodyParameters]. Er zijn geen parameters van het type [getParameters], zoals blijkt uit [uri];

Misschien ben je benieuwd wat de server ontvangt bij een verzoek van het type POST. Hiervoor starten we onze generieke server [RawTcpServer] op poort 100 van de lokale machine vanuit een Laragon-terminal (zie paragraaf ‘link’):

Image

Controleer of u in [4] daadwerkelijk in de map met hulpprogramma’s bent.

We wijzigen het bestand jSON [config-parameters-client.json] dat de client POST configureert:


{
    "url-get": "http://localhost:100/php7/scripts-web/05/parameters-server.php",
    "url-post": "http://localhost:100/php7/scripts-web/05/parameters-server.php"
}
  • regel 3: we hebben de poort van de webserver gewijzigd. Er wordt dus contact opgenomen met [RawTcpServer];

We starten de client. In het venster van [RawTcpServer] krijgen we de volgende informatie te zien:

Image

  • in [6], het commando POST;
  • in [7]: de header HTTP [Content-Length] geeft het aantal bytes aan van het document dat de client naar de server zal verzenden. De header HTTP [Content-Type] geeft de aard van dit document aan. Het type [application/x-www-form-urlencoded] duidt op een URL-gecodeerde tekst;
  • in [8] is de lege regel die het einde van de headers HTTP en het begin van het document van 44 bytes aangeeft. Wat niet op de schermafbeelding te zien is, is het document zelf. Dit is de URL-gecodeerde tekenreeks van de parameters: [prenom=jean-paul&nom=de+la+h%C3%BBche&age=45]. De lezer kan controleren of deze inderdaad 44 tekens telt;

17.9.6. Een gemengde POST-client

In een POST kunnen de parameters die in de URL zijn gecodeerd, worden gecombineerd met die welke zijn gecodeerd in het door de client verzonden document na de headers HTTP. Hier volgt een voorbeeld [parameters-mixte-postclient.php]:


<?php

// POST-client van een webserver
//
// foutafhandeling
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// afhankelijkheden
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// de configuratie van de client
const CONFIG_FILE_NAME = "config-parameters-client.json";

// de configuratie wordt opgehaald
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;
}

// een client aanmaken HTTP
$httpClient = HttpClient::create();
try {
  // de instellingen voorbereiden
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
  // de aanvraag naar de server verzenden
  $response = $httpClient->request('POST', $config['url-post'],
    [
      // parameters van het document (body)
      "body" => [
        "prenom" => $prenom,
        "nom" => $nom,
        "age" => $age
      ],
      // parameters van de URL (query)
      "query" => [
        "prenom2" => $prenom,
        "nom2" => $nom,
        "age2" => $age
  ]]);
  // status van het antwoord
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // de headers worden opgehaald
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // het antwoord van de server wordt weergegeven
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  // de fout wordt weergegeven
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Opmerkingen

  • regel 32: een verzoek POST;
  • regels 40-45: de URL-gecodeerde parameters in de URL;
  • regels 35-39: de URL-gecodeerde parameters in de body (document) van het verzoek;

Bij uitvoering krijgt men de volgende console-uitvoer:

---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"}}]
  • regel 10: we zien dat de server beide soorten parameters heeft kunnen ophalen;

17.9.7. Een gemengde GET-client

We proberen hetzelfde te doen als eerder met een verzoek GET. Het script [parameters-mixte-get-client.php] is als volgt:


<?php

// POST-client van een webserver
//
// foutafhandeling
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// afhankelijkheden
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// de configuratie van de client
const CONFIG_FILE_NAME = "config-parameters-client.json";

// de configuratie wordt opgehaald
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;
}

// een klant aanmaken HTTP
$httpClient = HttpClient::create();
try {
  // de parameters worden voorbereid
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
  // de aanvraag naar de server verzenden
  $response = $httpClient->request('GET', $config['url-post'],
    [
      // parameters van het document (body)
      "body" => [
        "prenom" => $prenom,
        "nom" => $nom,
        "age" => $age
      ],
      // parameters van de URL (query)
      "query" => [
        "prenom2" => $prenom,
        "nom2" => $nom,
        "age2" => $age
  ]]);
  // status van het antwoord
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // de headers worden opgehaald
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // het antwoord van de server wordt weergegeven
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  // de fout wordt weergegeven
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Opmerkingen

  • regel 32: een verzoek POST;
  • regels 40-45: de URL-gecodeerde parameters in de URL;
  • regels 35-39: de URL-gecodeerde parameters in de body (document) van het verzoek;

Bij uitvoering krijgt men de volgende console-uitvoer:

---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":[]}]
  • regel 10: we zien dat de server geen URL-gecodeerde parameters heeft ontvangen in het door de client verzonden document. Als we kijken naar de door de client verzonden headers HTTP, zien we dat deze wel degelijk een document van 44 tekens heeft verzonden, maar dat de server dit niet heeft verwerkt;

Welke methode moet je uiteindelijk kiezen om informatie naar de server te verzenden?

  • De methode [GET URL?param1=val1&param2=val2&…] maakt gebruik van een geconfigureerde URL die als link kan dienen. Dat is het belangrijkste voordeel: de gebruiker kan dergelijke links in zijn bladwijzers opslaan;
  • in andere toepassingen wil men de naar de server verzonden parameters misschien niet in een URL weergeven. Bijvoorbeeld om veiligheidsredenen. Dan gebruikt men een [POST]-methode en plaatst men de URL-gecodeerde parameters in een document dat naar de server wordt verzonden;

17.10. Beheer van websessies

In de voorgaande client/server-voorbeelden werkte het als volgt:

  • de client opent een verbinding met poort 80 van de webserver;
  • hij verstuurt de tekstreeks: headers HTTP, lege regel, [document];
  • als reactie stuurt de server een reeks van hetzelfde type;
  • de server verbreekt de verbinding met de client;
  • de client verbreekt de verbinding met de server;

Als dezelfde client kort daarna een nieuw verzoek naar de webserver stuurt, wordt er een nieuwe verbinding tot stand gebracht tussen de client en de server. De server kan niet weten of de client die verbinding maakt al eerder is geweest of dat dit een eerste verzoek is. Tussen twee verbindingen door „vergeet“ de server zijn client. Om deze reden wordt gezegd dat het protocol HTTP een stateloos protocol is. Het is echter nuttig dat de server zijn klanten onthoudt. Als een applicatie bijvoorbeeld beveiligd is, stuurt de klant een gebruikersnaam en wachtwoord naar de server om zich te identificeren. Als de server zijn klant tussen twee verbindingen ‘vergeet’, moet de klant zich bij elke nieuwe verbinding opnieuw identificeren, wat onhaalbaar is.

Om een klant te kunnen volgen, gaat de server als volgt te werk: bij een eerste verzoek van een klant neemt hij in zijn antwoord een identificatiecode op die de klant vervolgens bij elk nieuw verzoek naar de server moet terugsturen. Dankzij deze identificatiecode, die voor elke klant uniek is, kan de server een klant herkennen. Hij kan dan een geheugen voor deze klant beheren in de vorm van een geheugen dat uniek is gekoppeld aan de identificatiecode van de klant.

Technisch gezien verloopt dit als volgt:

  • in het antwoord aan een nieuwe klant neemt de server de header HTTP Set-Cookie: MotClé=Identificatiecode op. Dit gebeurt alleen bij het eerste verzoek;
  • bij zijn volgende verzoeken stuurt de klant zijn identificatiecode terug via de header HTTP Cookie: MotClé=Identificatiecode, zodat de server hem kan herkennen;

Je kunt je afvragen hoe de server weet of hij te maken heeft met een nieuwe klant of met een klant die al eerder is geweest. Dit wordt aangegeven door de aanwezigheid van de header HTTP Cookie in de headers HTTP van de klant. Bij een nieuwe klant ontbreekt deze header.

Het geheel van verbindingen van een bepaalde klant wordt een sessie genoemd.

17.10.1. Het configuratiebestand [php.ini]

Om ervoor te zorgen dat het sessiebeheer correct werkt met PHP, moet worden gecontroleerd of dit correct is geconfigureerd. Onder Windows is het configuratiebestand php.ini. Afhankelijk van de uitvoeringscontext (console, web) moet het configuratiebestand [php.ini] in verschillende mappen worden gezocht. Om deze te achterhalen, gebruikt u het volgende script:

1
2
3
4
<?php

// informatie PHP
phpinfo();

Op regel 4 geeft de functie phpinfo informatie over de interpreter PHP die het script uitvoert. Deze functie geeft met name het pad naar het gebruikte configuratiebestand [php.ini].

We hebben dit script al eens gebruikt in een console-omgeving (zie paragraaf ‘link’). In een webomgeving krijgen we het volgende resultaat:

Image

  • in [1-2], het bestand [php.ini] dat de interpreter voor webscripts configureert. In dit bestand vinden we een sectie ‘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
  • regel 2: de gegevens van een klantsessie worden opgeslagen in een bestand;
  • regel 3: de map waarin de sessiegegevens worden opgeslagen. Als deze map niet bestaat, wordt er geen fout gemeld en werkt het sessiebeheer niet;
  • regels 4-6: geven aan dat de sessie-ID wordt beheerd door de headers HTTP, Set-Cookie en Cookie;
  • regel 7: de header Set-Cookie heeft de vorm Set-Cookie: PHPSESSID=identifiant_de_session;
  • regel 8: een clientsessie wordt niet automatisch gestart. Het serverscript moet deze expliciet aanvragen met een session_start()-instructie;
  • regel 9: de sessiecookie blijft geldig zolang de browser van de klant niet is gesloten;
  • regel 10: het pad waarvoor de sessiecookie moet worden teruggestuurd. Als [session.cookie_path = /xxx], dan moet de cookie telkens worden teruggestuurd wanneer de browser een URL van het type [/xxx/yyy/zzz] opvraagt. Hier geeft het pad [/] aan dat de cookie moet worden teruggestuurd voor elke URL van de site;
  • regel 13: bepaalde sessieobjecten moeten worden geserialiseerd om in een bestand te kunnen worden opgeslagen. PHP zorgt voor deze serialisatie/deserialisatie met de functies [serialize / unserialize];
  • regel 16: levensduur waarna de in het back-upbestand opgeslagen sessieobjecten als verouderd worden beschouwd;
  • regel 19: de levensduur van een sessie. Na afloop van deze periode wordt een nieuwe sessie aangemaakt en gaan de objecten die in de vorige sessie waren opgeslagen verloren;

17.10.2. Voorbeeld 1

17.10.2.1. De server

Image

Het beheer van de sessie-ID verloopt transparant voor een webservice. Deze ID wordt beheerd door de webserver. Een webservice heeft toegang tot de sessie van de client via de instructie session_start(). Vanaf dat moment kan de webservice gegevens in de sessie van de klant lezen en schrijven via het woordenboek $_SESSION. Als de bibliotheek [HttpFoundation] wordt gebruikt, is de sessie beschikbaar via de uitdrukking [Request→getSession].

De volgende code [session-server.php] toont het sessiebeheer van drie tellers. Bij elk nieuw verzoek verhoogt het webscript deze tellers en slaat ze op in de sessie, zodat ze bij het volgende verzoek kunnen worden opgehaald.


<?php
// afhankelijkheden
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;

//
// het verzoek wordt opgehaald
$request = Request::createFromGlobals();
// sessie
$session = new Session();
$session->start();
// drie tellers worden opgehaald uit de sessie
if ($session->has("N1")) {
  // de teller N1 wordt verhoogd
  $session->set("N1", (int) $session->get("N1") + 1);
} else {
  // de teller N1 is niet in de sessie aanwezig – deze wordt aangemaakt
  $session->set("N1", 0);
}
if ($session->has("N2")) {
  // de teller N2 wordt verhoogd
  $session->set("N2", (int) $session->get("N2") + 1);
} else {
  // de teller N2 is niet in sessie – deze wordt aangemaakt
  $session->set("N2", 10);
}
if ($session->has("N3")) {
  // de teller N3 wordt verhoogd
  $session->set("N3", (int) $session->get("N3") + 1);
} else {
  // de teller N3 is niet in sessie – deze wordt aangemaakt
  $session->set("N3", 100);
}
// het antwoord wordt opgesteld
$response = new Response();
// de inhoud van het antwoord is UTF-8-tekst
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
// het antwoord zal de jSON zijn van een tabel die de drie tellers bevat
$response->setContent(json_encode([
  "N1" => $session->get("N1"),
  "N2" => $session->get("N2"),
  "N3" => $session->get("N3")]));

// het antwoord wordt verzonden
$response->send();
  • regel 10: het object [$request] bevat alle informatie over het verzoek dat door het webscript is ontvangen;
  • regels 12-13: er wordt een sessie aangemaakt en geactiveerd. Het object [Session] bevat de sessiegegevens die overeenkomen met de door de client verzonden sessiecookie. Als de client geen dergelijke cookie heeft verzonden, worden er geen gegevens opgeslagen in [Session]. Het webscript neemt in zijn eerste antwoord de header HTTP [Set-Cookie : PHPSESSID=xxx] op. In zijn volgende verzoeken zal de client de header HTTP [Cookie : PHPSESSID=xxx] verzenden om aan te geven van welke sessie hij de inhoud wil gebruiken. Een sessie is het geheugen van een client;
  • regel 15: we controleren of de sessie een sleutel heeft met de naam [N1]. Dit wordt de naam van onze eerste teller. Als dat niet het geval is (regel 20), geven we deze de waarde 0 en voegen we hem toe aan de sessie. Als dat wel het geval is (regel 23), dan:
    • halen we deze uit de sessie;
    • verhogen we de waarde met 1;
    • zetten we hem terug in de sessie;
  • regels 22-35: we doen hetzelfde voor de twee andere tellers N2 en N3;
  • regels 36-40: we stellen een antwoord op van het type [application/json];
  • regels 42-45: het antwoord wordt de tekenreeks jSON uit een array die de drie tellers bevat;
  • regel 48: het antwoord wordt naar de client verzonden;

In de klant-serverrelatie hangt het beheer van de klantsessie op de server af van beide partijen, de klant en de server:

  • de server is verantwoordelijk voor het verzenden van een identificatiecode naar de client bij diens eerste verzoek
  • de client is verantwoordelijk voor het terugsturen van deze identificatiecode bij elk nieuw verzoek. Als hij dit niet doet, zal de server aannemen dat het om een nieuwe client gaat en een nieuwe identificatiecode genereren voor een nieuwe sessie.

Resultaten

We gebruiken een webbrowser als client. Standaard (in feite door de configuratie) stuurt deze de sessie-ID's die de server hem toestuurt, inderdaad terug naar de server. Naarmate de verzoeken vorderen, ontvangt de browser de drie tellers die door de server worden verzonden en ziet hij dat hun waarden toenemen.

Image

  • In [2], het eerste verzoek aan de webservice;
  • in [4], het vierde verzoek, blijkt dat de tellers inderdaad zijn opgevoerd. De waarden van de tellers worden inderdaad bij elk verzoek onthouden;

Laten we de ontwikkelingsmodus gebruiken om de HTTP-headers te bekijken die tussen de server en de client worden uitgewisseld. We sluiten Firefox om de huidige sessie met de server te beëindigen, openen het programma opnieuw en schakelen de ontwikkelingsmodus in (F12). Hierdoor wordt de huidige browsersessie verwijderd en start de browser een nieuwe sessie. We vragen de service [session-server.php] op:

Image

In [5] zien we de sessie-ID die door de server is verzonden in zijn antwoord op het eerste verzoek van de client. Deze maakt gebruik van de header HTTP Set-Cookie.

Laten we een nieuw verzoek doen door de pagina in de webbrowser te verversen (F5):

Image

Hierboven vallen twee dingen op:

  • in [11] stuurt de webbrowser de sessie-ID terug met de header HTTP Cookie.
  • In [12] neemt de webservice deze identificatiecode niet meer op in zijn antwoord. Het is nu aan de client om deze bij elk verzoek mee te sturen.

17.10.2.2. De client

We schrijven nu een clientscript op basis van het vorige serverscript. Bij het beheer van de sessie moet het zich gedragen als een webbrowser:

  • In het antwoord van de server op zijn eerste verzoek moet hij de sessie-ID vinden die de server hem toestuurt. Hij weet dat hij deze zal vinden in de header HTTP Set-Cookie.
  • Bij elk van zijn volgende verzoeken moet hij de ontvangen identificatiecode naar de server terugsturen. Hij doet dit met de header HTTP Cookie.

Image

De client [session-client] wordt geconfigureerd door het volgende bestand: jSON [config-session-client.json]:

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

De code van de client [session-client] is als volgt:


<?php

// sessiebeheer
//
// foutbeheer
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// afhankelijkheden
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// de configuratie van de client
const CONFIG_FILE_NAME = "config-session-client.json";

// de configuratie wordt opgehaald
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;
}

// we maken een klant aan HTTP
$httpClient = HttpClient::create();
try {
  // er worden 10 verzoeken verzonden
  for ($i = 0; $i < 10; $i++) {
    // we sturen het verzoek naar de server
    if (!isset($sessionCookie)) {
      // zonder sessie
      $response = $httpClient->request('GET', $config['url']);
    } else {
      // met sessie
      $response = $httpClient->request('GET', $config['url'],
        ["headers" => ["Cookie" => $sessionCookie]]);
    }
    // status van het antwoord
    $statusCode = $response->getStatusCode();
    print "---Réponse avec statut : $statusCode\n";
    // we halen de headers op
    print "---Entêtes de la réponse\n";
    $headers = $response->getHeaders();
    foreach ($headers as $type => $value) {
      print "$type: " . $value[0] . "\n";
    }
    // de sessiecookie wordt opgehaald, indien aanwezig
    if (isset($headers["set-cookie"])) {
      // sessiecookie?
      foreach ($headers["set-cookie"] as $cookie) {
        $match = [];
        $match = preg_match("/^PHPSESSID=(.+?);/", $cookie, $champs);
        if ($match) {
          $sessionCookie = "PHPSESSID=" . $champs[1];
        }
      }
    }
  }
  // het antwoord jSON van de server wordt weergegeven
  print "---Réponse du serveur : {$response->getContent()}\n";
} catch (TypeError | RuntimeException $ex) {
  // de fout wordt weergegeven
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}


Opmerkingen

  • regel 27: aanmaken van de client HTTP;
  • regel 30: we voeren 10 keer hetzelfde verzoek uit naar de server [session-server.php];
  • regel 32: de variabele [$sessionCookie] krijgt de waarde van de header HTTP [Set-Cookie] die door de client is ontvangen;
  • regels 32-34: als deze variabele niet bestaat, betekent dit dat de sessie nog niet is gestart. We sturen het commando [GET] zonder de header [Cookie];
  • regels 35-38: anders is de sessie gestart en wordt het commando [GET] verzonden met de header [Cookie]. De waarde van deze header is [$sessionCookie];
  • regel 50: als de header [Set-Cookie] deel uitmaakt van de ontvangen HTTP-headers, dan wordt er gezocht naar de sessiecookie;
  • regel 52: de webserver kan meerdere [Set-Cookie]-headers verzenden. De sessiecookie is slechts één daarvan. In ons voorbeeld heeft deze de vorm [PHPSESSID=xxx;];
  • regels 53-57: er wordt een reguliere expressie gebruikt om de sessiecookie te vinden;
  • regel 62: zodra de 10 verzoeken zijn verzonden, wordt het laatste antwoord jSON van de server weergegeven;

Resultaten

De uitvoering van het clientscript leidt tot de volgende weergave in de NetBeans-console:


"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}
  • regel 8: in het eerste antwoord stuurt de server de sessie-ID mee. In de volgende antwoorden wordt deze niet meer meegestuurd;
  • regel 41: de drie tellers [N1, N2, N3] zijn inderdaad 9 keer opgehoogd. Bij verzoek nr. 1 zijn ze op nul gezet;

Het volgende voorbeeld laat zien dat je ook de waarden van een array of een object in de sessie kunt opslaan.

17.10.3. Voorbeeld 2

17.10.3.1. De server

Image

We gaan een object [Personne] in de sessie opslaan. De definitie van deze klasse is als volgt:


<?php

namespace Modèles;

class Personne implements \JsonSerializable {
  // attributen
  private $nom;
  private $prénom;
  private $âge;

  // conversie van een associatieve array naar een object [Personne]
  public function setFromArray(array $assoc): Personne {
    // het huidige object wordt geïnitialiseerd met de associatieve array
    foreach ($assoc as $attribute => $value) {
      $this->$attribute = $value;
    }
    // resultaat
    return $this;
  }

  // getters en 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]";
  }

  // implementeert de interface JsonSerializable
  public function jsonSerialize(): array {
    // er wordt een associatieve array geretourneerd met de attributen van het object als sleutels
    // dit array kan vervolgens worden gecodeerd in jSON
    return get_object_vars($this);
  }

  // conversie van een jSON naar een object [Personne]
  public static function jsonUnserialize(string $json): Personne {
    // er wordt een persoon aangemaakt op basis van de tekenreeks jSON
    return (new Personne())->setFromArray(json_decode($json, true));
  }

}

Het serverscript ziet er als volgt uit:


<?php

// afhankelijkheden
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;

//
// de huidige aanvraag wordt opgehaald
$request = Request::createFromGlobals();

// sessie
$session = new Session();
$session->start();

// verschillende gegevens uit de sessie ophalen
// array
if ($session->has("tableau")) {
  // de tabel bevindt zich in de sessie – alle waarden worden met één verhoogd
  $tableau = $session->get("tableau");
  for ($i = 0; $i < count($tableau); $i++) {
    $tableau[$i] += 1;
  }
  // de array wordt teruggeplaatst in de sessie
  $session->set("tableau", $tableau);
} else {
  // de array bevindt zich niet in de sessie – deze wordt aangemaakt
  $tableau = [0, 10, 100];
  // de array wordt in de sessie geplaatst
  $session->set("tableau", $tableau);
}
// woordenboek
if ($session->has("assoc")) {
  // [assoc] bevindt zich in de sessie – alle elementen ervan worden opgewaardeerd
  $assoc = $session->get("assoc");
  foreach ($assoc as $key => $value) {
    $assoc[$key] = $value + 1;
  }
  // we plaatsen $assoc in de sessie
  $session->set("assoc", $assoc);
} else {
  // [assoc] staat niet in de sessie – het wordt aangemaakt
  $assoc = ["un" => 0, "deux" => 10, "trois" => 100];
  // $assoc wordt aan de sessie toegevoegd
  $session->set("assoc", $assoc);
}
// object Persoon
if ($session->has("personne")) {
  // [personne] bevindt zich in de sessie – de leeftijd wordt verhoogd
  $personne = $session->get("personne");
  $personne->setÂge($personne->getÂge() + 1);
} else {
  // [personne] zit niet in de sessie - we maken het aan
  $personne = (new Personne())->setFromArray(
    ["prénom" => "Léonard", "nom" => "Hûche", "âge" => 0]);
  // $personne wordt aan de sessie toegevoegd
  $session->set("personne", $personne);
}
// het antwoord wordt opgesteld
$response = new Response();
// de inhoud van het antwoord 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));

// het antwoord wordt verzonden
$response->send();

Opmerkingen

  • regels 16-17: we halen de huidige sessie op en activeren deze;
  • regels 21-34: we beheren een array [tableau] die in de sessie is opgeslagen. Bij elk nieuw verzoek worden de elementen ervan met 1 verhoogd;
  • regels 36-49: we beheren een associatieve array [assoc] die in de sessie is opgeslagen. Bij elke nieuwe aanvraag worden de elementen ervan met 1 verhoogd;
  • regels 51-61: er wordt een sessieobject [Personne] beheerd. Bij elke nieuwe aanvraag wordt de leeftijd van deze persoon met 1 verhoogd;
  • regels 62-73: er wordt een antwoord jSON naar de client gestuurd: de tekenreeks jSON uit een associatieve array;

Laten we dit script uitvoeren vanuit NetBeans. De eerste twee verzoeken leveren de volgende resultaten op (F5 in de browser voor het tweede):

Image

  • we zien dat in [6-8] alle tellers zijn opgehoogd;

17.10.3.2. De client

Image

De klant is dezelfde als in voorbeeld 1 (paragraaf ‘link’). We wijzigen alleen het configuratiebestand [config-session-client]:


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

De uitvoering levert de volgende resultaten op:


---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}}
  • in regel [22] is te zien dat alle tellers zijn opgehoogd;

17.11. Authentification

We richten ons nu op webservices die alleen voor bepaalde gebruikers bestemd zijn. De klant moet zich dan bij de webservice identificeren voordat hij een antwoord krijgt.

17.11.1. De klant

Image

De code van de klant [auth-client.php] is als volgt:


<?php

// sessiebeheer
//
// foutbeheer
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// afhankelijkheden
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// de configuratie van de client
const CONFIG_FILE_NAME = "config-auth-client.json";

// de configuratie wordt opgehaald
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;
}

// een client aanmaken HTTP
$httpClient = HttpClient::create([
    'auth_basic' => ['admin', 'admin'],
    // "verify_peer" => false,
    // "verify_host" => false
  ]);


try {
  // het verzoek wordt naar de server verzonden
  $response = $httpClient->request('GET', $config['url']);
  // status van het antwoord
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // de headers worden opgehaald
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // het antwoord jSON van de server wordt weergegeven
  print "---Réponse du serveur : {$response->getContent()}\n";
} catch (TypeError | RuntimeException $ex) {
  // de fout wordt weergegeven
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Opmerkingen

  • regels 27-31: er is een parameter doorgegeven aan de statische methode [HttpClient::create], een associatieve array;
  • regel 28: de sleutel [auth_basic] heeft als waarde een array met twee elementen, [user, password]. Met deze elementen zal de client zich bij de webservice authenticeren. De sleutel [auth_basic] verwijst naar een authenticatietype met de naam [Autorization Basic], afgeleid van de header HTTP die de client zal verzenden. Er bestaan nog andere authenticatietypen;
  • afgezien van deze code is de client identiek aan de voorgaande;

Om de door de client verzonden HTTP-headers te bekijken, gaan we de client verbinden met de generieke server TCP ([RawTcpServer]), zoals we al vele malen eerder hebben gedaan:

Image

We starten de client met de volgende configuratie:


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

De server [RawTcpServer] ontvangt dan de volgende regels:

Image

  • in [5] zien we de header [Autorization : Basic XXX] die door de client is verzonden. De tekenreeks XXX is de tekenreeks [user:password] gecodeerd in Base64;

Om hier zeker van te zijn, kunt u de ontvangen tekenreeks decoderen op de website [https://www.base64decode.org/]:

Image

17.11.2. De server

Image

De server [auth-server.php] is als volgt:


<?php

// afhankelijkheden
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;

// geautoriseerde gebruikers
$users = ["admin" => "admin"];
//
// de huidige aanvraag wordt opgehaald
$request = Request::createFromGlobals();
// authenticatie
$requestUser = $request->headers->get('php-auth-user');
$requestPassword = $request->headers->get('php-auth-pw');
// bestaat de gebruiker?
$trouvé = array_key_exists($requestUser, $users) && $users[$requestUser] === $requestPassword;
// het antwoord voorbereiden
$response = new Response();
// de statuscode van het antwoord wordt vastgesteld
if (!$trouvé) {
  // niet gevonden - code 401
  $response->setStatusCode(Response::HTTP_UNAUTHORIZED);
  $response->headers->add(["WWW-Authenticate"=> "Basic realm=".utf8_decode("\"PHP7 par l'exemple\"")]);
} else {
  // gevonden - code 200
  $response->setStatusCode(Response::HTTP_OK);
}
// het antwoord bevat geen inhoud, alleen headers HTTP
$response->send();

Opmerkingen

  • regel 9: de geautoriseerde gebruikers, in dit geval één gebruiker met login [admin] en wachtwoord [admin];
  • regel 14: de gebruikers-ID wordt opgehaald uit de header [PHP-AUTH-USER]. Dit is geen header die de client heeft verzonden, maar een header die de server PHP heeft samengesteld;
  • regel 15: het wachtwoord van de gebruiker wordt opgehaald uit de header [PHP-AUTH-PW], een header die is samengesteld door PHP;
  • regel 17: er wordt gezocht naar de gebruiker die wil inloggen in de lijst met geautoriseerde gebruikers;
  • regels 23-24: als de gebruiker niet is herkend, wordt naar de client verzonden
    • regel 23: de code [401 Unauthorized];
    • regel 24: een header [WWW-Authenticate: Basic realm=”quelque chose”]. De meeste browsers herkennen deze header en openen een authenticatievenster waarin de gebruiker wordt gevraagd zich te authenticeren. De headers HTTP moeten worden gecodeerd in ISO 8859-1. De NetBeans-teksten zijn gecodeerd in UTF-8. De functie [utf8_decode] zorgt voor de conversie van UTF-8 naar ISO 8859-1. Hier was dit niet nodig, omdat de tekens in de tekenreeks [PHP7 par l’exemple] dezelfde zijn in UTF-8 en ISO 8859-1. De functie dient alleen ter herinnering aan de codering die wordt gebruikt door de headers HTTP;
  • regel 25: als de gebruiker is herkend, wordt de code [200 OK] naar de client verzonden;

Laten we de URL [auth-server.php] opvragen met een browser:

Image

We zien dat de browser een authenticatievenster weergeeft. In [2] zien we de waarde van de header [WWW-Authenticate] die door de server is verzonden. Als we kijken naar de headers HTTP die door de browser zijn ontvangen, vinden we het volgende:

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
  • regel 1: de code [401 Unauthorized] van het antwoord;
  • regel 6: de header HTTP [WWW-Authenticate];
  • regel 7: de hoofdtekst van het antwoord is leeg;

Als je in [3-4] twee keer [admin] invoert, is het antwoord van de server als volgt:

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
  • regel 1: de responscode 200 OK;
  • regel 6: de inhoud van het antwoord is leeg;

Als je in [3-4] onjuiste inloggegevens invoert, blijft de browser [Firefox] die voor de tests wordt gebruikt, het authenticatievenster oneindig lang weergeven totdat de juiste inloggegevens zijn ingevoerd. Telkens wanneer er een heen-en-terugverkeer met de server plaatsvindt, wordt steeds hetzelfde antwoord teruggestuurd, waardoor het authenticatievenster van de browser wordt geactiveerd.

Laten we de client [auth-client.php] uitvoeren met een niet-geautoriseerde gebruiker. Het antwoord van de server is als volgt:


---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] heeft de client inderdaad een 401-code ontvangen;
  • in [3] is er een uitzondering gegenereerd in de client. Het is de Symfony-client [HttpClient] die deze uitzondering heeft gegenereerd: deze genereert een uitzondering wanneer de statuscode van het antwoord HTTP aangeeft dat er een fout aan de serverzijde is opgetreden, en de client probeert de headers of de inhoud van het serverantwoord te lezen. Uit het bericht op regel 3 blijkt dat de server met [HTTP/1.0 401 Unauthorized] heeft gereageerd om aan te geven dat de gebruiker niet werd herkend;

Laten we nu de client [auth-client.php] uitvoeren met de geautoriseerde gebruiker [‘admin’,’admin’]. Het antwoord van de server is dan als volgt:


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

  • regel 1: de server heeft [HTTP/1. 200 OK] gerespondeerd;
  • regel 7: het antwoord bevat geen inhoud (0 byte);

17.11.3. De verbinding tussen client en server beveiligen

We hebben gezien dat de client, om zich bij de server te authenticeren, de header verstuurde:

authorization: Basic YWRtaW46YWRtaW4=

Als deze regel door spyware wordt onderschept, kan deze gemakkelijk de in base64 gecodeerde inloggegevens [login, mot de passe] achterhalen in de tekenreeks [YWRtaW46YWRtaW4=]. Om deze reden moet de authenticatie plaatsvinden via een beveiligde verbinding tussen de client en de server. De beveiligde URL-verbindingen maken gebruik van het protocol [HTTPS] in plaats van het protocol HTTP. Het protocol [HTTPS] is het protocol HTTP binnen een beveiligde client/server-verbinding. Beveiligde URL hebben de vorm [https://chemin_document].

Niet alle webservers accepteren URL in deze vorm. Ze moeten worden aangepast om beveiligd te zijn. De Apache-server van Laragon is een beveiligde server, maar het protocol HTTPS is standaard niet actief. Het moet worden geactiveerd in het menu van Laragon:

Image

  • in [4], moet de versleuteling SSL van de Apache-server worden ingeschakeld;

Zodra dit is gebeurd, wordt de Apache-server automatisch opnieuw opgestart:

Image

  • naar [1], verschijnt er een groen hangslotje: dit is het teken dat het protocol HTTPS is geactiveerd;
  • In [2] verschijnt een nieuwe servicepoort, in dit geval poort 443. Dit is de servicepoort van het beveiligde protocol HTTPS;

Nu we een beveiligde server hebben, passen we het configuratiebestand [config-auth-client.json] van de client als volgt aan:


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

In [2] is het protocol gewijzigd in [https] en de poort in [443].

Laten we nu de client [auth-client.php] uitvoeren met de geautoriseerde gebruiker [admin, admin]. De console-uitvoer is als volgt:

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".

De Symfony-client [HttpClient] heeft een uitzondering gegenereerd omdat de server een vertrouwenscertificaat heeft verzonden dat [HttpClient] niet heeft geaccepteerd. De communicatie SSL vindt plaats met vertrouwenscertificaten die zijn gecertificeerd door officiële instanties. Toen het protocol HTTPS op de Apache-server van Laragon werd geactiveerd, werd er een zelfondertekend certificaat voor de Apache-server gegenereerd. Een zelfondertekend certificaat is een certificaat dat niet door een officiële instantie is gevalideerd. De Symfony-client [HttpClient] heeft dit zelfondertekende certificaat geweigerd.

Het is mogelijk om [HttpClient] te vragen de geldigheid van het door de server verzonden certificaat niet te controleren. Dit gebeurt met opties in de methode [HttpClient::create]:


// er wordt een klant aangemaakt HTTP
$httpClient = HttpClient::create([
    'auth_basic' => ['admin', 'admin'],
    "verify_peer" => false
  ]);

Op regel 4 wordt aangegeven dat het certificaat van de server niet mag worden gecontroleerd. We waren dit probleem al tegengekomen in het script [http-02.php] uit de paragraaf ‘link’. Dit script gebruikte de bibliotheek [libcurl] om verbinding te maken met de sites HTTP en HTTPS. We hadden toen de volgende configuratie voor deze bibliotheek gebruikt:


// Een sessie wordt gestart cURL
  $curl = curl_init($url);
  if ($curl === FALSE) {
    // er is een fout opgetreden
    return "Erreur lors de l'initialisation de la session cURL pour le site [$site]";
  }
  // curl-opties
  $options = [
    // uitgebreide modus
    CURLOPT_VERBOSE => true,
    // nieuwe verbinding – geen cache
    CURLOPT_FRESH_CONNECT => true,
    // time-out van het verzoek (in seconden)
    CURLOPT_TIMEOUT => $timeout,
    CURLOPT_CONNECTTIMEOUT => $timeout,
    // certificaten niet op geldigheid controleren SSL
    CURLOPT_SSL_VERIFYPEER => false,
    // omleidingen volgen
    CURLOPT_FOLLOWLOCATION => true,
    // het opgevraagde document ophalen in de vorm van een tekenreeks
    CURLOPT_RETURNTRANSFER => true
  ];

  // configuratie van curl
curl_setopt_array($curl, $options);

Op regel 17 bepaalt de constante [CURLOPT_SSL_VERIFYPEER] of het door de server verzonden certificaat al dan niet wordt gecontroleerd. De client [HttpClient] is in feite een client [curl] wanneer de extensie [curl] is geactiveerd in de configuratie van PHP, zoals hier het geval is. De klasse die door [HttpClient::create] wordt geïnstantieerd, is dan de klasse [CurlHttpClient]. De constanten van [curl] zijn beschikbaar in deze klasse, maar onder andere 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 => '', // Cookies bijhouden tijdens omleidingen
            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'],
        ];

We hebben de constanten die door [CurlHttpClient] worden gebruikt geel gemarkeerd.

Als we nu de client [auth-client] uitvoeren met de gebruiker [admin, admin], krijgen we het volgende resultaat:


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

De gebruiker is correct herkend. Als we de client [auth-client] uitvoeren met een andere gebruiker dan [admin, admin], krijgen we het volgende resultaat:

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".

Nu weten we hoe we ons bij een beveiligde server kunnen authenticeren.