17. Serviços Web
Nota: Por «serviço web», entendemos aqui qualquer aplicação web que forneça dados brutos consumidos por um cliente — um script de consola nos exemplos que se seguem. Não nos preocupamos com nenhuma tecnologia específica — REST (REpresentational State Transfer) ou SOAP (Simple Object Access Protocol), por exemplo — que forneça mais ou menos dados brutos num formato bem definido. O REST devolve JSON, enquanto o SOAP devolve XML. Cada uma destas tecnologias define com precisão a forma como o cliente deve consultar o servidor e o formato que a resposta do servidor deve assumir. Neste curso, seremos muito mais flexíveis no que diz respeito à natureza do pedido do cliente e à resposta do servidor. No entanto, os scripts escritos e as ferramentas utilizadas são semelhantes aos da tecnologia REST.
17.1. Introdução
Uma vez que os programas PHP podem ser executados por um servidor web, tal programa torna-se um programa do lado do servidor capaz de servir múltiplos clientes. Do ponto de vista do cliente, chamar um serviço web equivale a solicitar o URL desse serviço. O cliente pode ser escrito em qualquer linguagem, incluindo PHP. Neste último caso, utilizamos as funções de rede que acabámos de abordar. Também precisamos de saber como «comunicar» com um serviço web, ou seja, compreender o protocolo HTTP para a comunicação entre um servidor web e os seus clientes. Esse foi o objetivo da secção «link».
O cliente web descrito na secção «link» permitiu-nos explorar parte do protocolo HTTP.

Na sua forma mais simples, as trocas cliente/servidor decorrem da seguinte forma:
- o cliente abre uma ligação à porta 80 no servidor web;
- solicita um documento;
- o servidor web envia o documento solicitado e encerra a ligação;
- o cliente fecha então a conexão;
O documento pode ser de vários tipos: texto em formato HTML, uma imagem, um vídeo… Pode ser um documento existente (documento estático) ou um documento gerado dinamicamente por um script (documento dinâmico). Neste último caso, referimo-nos à programação web. O script para gerar documentos dinamicamente pode ser escrito em várias linguagens: PHP, Python, Perl, Java, Ruby, C#, VB.NET…
A seguir, utilizaremos scripts PHP para gerar dinamicamente documentos de texto.

- Em [1], o cliente estabelece uma ligação com o servidor, solicita um script PHP e pode ou não enviar parâmetros para esse script;
- Em [2], o servidor web executa o script PHP utilizando o interpretador PHP. O script gera um documento que é enviado ao cliente [3];
- O servidor encerra a ligação. O cliente faz o mesmo;
O servidor web pode lidar com vários clientes ao mesmo tempo.
Com o pacote de software [Laragon], o servidor web é um servidor Apache, um servidor de código aberto da Apache Foundation (http://www.apache.org/). Nas seguintes aplicações, o [Laragon] deve ser iniciado:

Isto inicia o servidor web Apache, bem como o SGBD MySQL.
Os scripts executados pelo servidor web serão escritos utilizando a ferramenta NetBeans. Até agora, escrevemos scripts PHP executados num ambiente de consola:

O utilizador utiliza a consola para solicitar a execução de um script PHP e receber os resultados.
Nas aplicações cliente/servidor que se seguem:
- o script do cliente é executado num ambiente de consola;
- o script do servidor é executado num contexto web;

O script PHP do lado do servidor não pode estar localizado em qualquer local do sistema de ficheiros. Na verdade, o servidor web procura os documentos estáticos e dinâmicos que lhe são solicitados em locais especificados pela configuração. A configuração padrão do Laragon faz com que os documentos sejam procurados na pasta <Laragon>/www, onde <Laragon> é a pasta de instalação do Laragon . Assim, se um cliente web solicitar um documento D com o URL [http://localhost/D], o servidor web servirá o documento D localizado no caminho [<Laragon>/www/D].
Nos exemplos seguintes, colocaremos os scripts do servidor na pasta [www/php7/scripts-web]. Se um script do servidor se chamar S.php, será solicitado ao servidor web através do URL [http://localhost/php7/scripts-web/S.php]. O documento [<Laragon>/www/php7/scripts-web/S.php] será então servido.

- em [1], a pasta [<laragon>/www];
- em [2], a pasta [php7/scripts-web];
Para criar scripts de servidor com o NetBeans, procederemos da seguinte forma:

- em [1-2], criamos um novo projeto
- em [3-4], selecionamos a categoria [PHP] e o projeto [Aplicação PHP]

- em [5], o nome do projeto;
- em [6], a pasta do projeto no sistema de ficheiros. Note que esta se encontra na pasta [<laragon>/www], onde deve estar;
- Em [7-8], aceite os valores predefinidos;
- Em [9-10], aceite os valores predefinidos fornecidos. Em [10], note que o URL dos scripts que iremos colocar neste projeto começará com o caminho [http://localhost/php7/scripts-web/];

- Em [11], são-lhe apresentadas estruturas web escritas em PHP. Estas estruturas são essenciais assim que a aplicação web crescer em escala;
- Em [12], pode adicionar bibliotecas PHP utilizando a ferramenta [Composer]. Utilizámos esta ferramenta duas vezes numa janela do [Terminal] do Laragon:
- para instalar a biblioteca [SwiftMailer], que permite enviar e-mails;
- para instalar a biblioteca [php-mime-mail-parser], que permite ler e-mails;
- em [13], assim que o assistente de criação do projeto for confirmado, o projeto aparece em [13] no separador Projetos;
17.2. Escrever uma página estática
Nota: Para o resto deste guia, o [Laragon] deve estar em execução.
Vamos mostrar como criar uma página HTML (HyperText Markup Language) estática utilizando o NetBeans:

- Em [1-5], criamos uma pasta chamada [01];


- Em [6-12], criamos um ficheiro HTML chamado [example-01.html];
O ficheiro [example-01.html] é gerado com o seguinte conteúdo pré-preenchido (maio de 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>
Vamos atualizar o seu conteúdo da seguinte forma:
<!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>
Alterámos o título da página (linha 4) e o seu conteúdo (linha 9).
Agora, vamos fazer com que o servidor Apache do Laragon exiba esta página HTML:

- em [1-2], fazemos com que o servidor Apache do Laragon exiba a página;
- em [3], o URL da página exibida;
- em [4], o título que modificámos;
- em [5], o conteúdo que modificámos;
A página exibida é uma página estática: pode recarregá-la quantas vezes quiser no navegador (F5) e o mesmo conteúdo é sempre exibido.
A maioria dos navegadores permite aceder aos dados trocados entre o cliente e o servidor, conforme descrito na secção «Link». No Firefox (em maio de 2019), prima F12 para aceder a estes dados:

Conforme indicado em [1], vamos recarregar a página (F5):

- em [2], o documento carregado pelo navegador: selecionamo-lo;

- em [5], o documento a ser analisado está selecionado;
- em [3-4], solicitamos a visualização das trocas cliente/servidor;
- Em [6], essas trocas;

- em [7], selecione o separador «Headers»;
- em [8], o URL solicitado pelo navegador;
- em [9], o comando enviado ao servidor é [GET http://localhost/php7/scripts-web/01/exemple-01.html HTTP/1.1];
- em [10], os cabeçalhos HTTP subsequentemente enviados pelo navegador (o cliente);
- em [11], os cabeçalhos HTTP da resposta do servidor;

- em [12-14], a resposta do servidor enviada após os cabeçalhos HTTP;
- em [14], vemos que o navegador do cliente recebeu a página HTML que criámos. Em seguida, interpretou este código para apresentar o seguinte:

17.3. Criação de uma página dinâmica em PHP
Vamos agora escrever uma página dinâmica em PHP:


- em [1-8], criamos uma página [example-01.php];
O ficheiro [example-01.php] é gerado pré-preenchido da seguinte forma (maio de 2019):
<!DOCTYPE html>
<!--
To change this license header, choose License Headers in Project Properties.
To change this template file, choose Tools | Templates
and open the template in the editor.
-->
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<?php
// put your code here
?>
</body>
</html>
Modificamos o código acima da seguinte forma:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Exemple de page dynamique</title>
</head>
<body>
<?php
// time : nb de millisecondes entre le moment présent et le 01/01/1970
// format affichage date-heure
// d : jour sur 2 chiffres
// m : mois sur 2 chiffres
// y : année sur 2 chiffres
// H : heure 0,23
// I : minutes
// s: secondes
print "<b>Date et heure du jour : </b>" . date("d/m/y H:i:s", time());
?>
</body>
</html>
Comentários
- linha 5: alterámos o título da página;
- linha 17: imprime a data e a hora atuais;
Basicamente, o script PHP acima imprime a hora atual na consola. No entanto, quando executado por um servidor web, a saída da instrução [print] — que normalmente é direcionada para a consola de execução do script — é redirecionada para a ligação que une o servidor ao seu cliente. Portanto, num contexto web, o script acima envia a hora atual como texto para o cliente, neste caso um navegador.
Vamos executar o script [example-01.php]:

- em [3], o URL solicitado ao servidor web Apache;
- em [4], o título da página que alterámos;
- em [5], o conteúdo gerado pela instrução [print];
Esta é uma página dinâmica porque, se recarregarmos a página várias vezes no navegador (F5), o seu conteúdo muda (a hora muda).
O navegador recebeu um fluxo HTML. Para o visualizar, é necessário exibir o código-fonte da página no navegador:

- para aceder ao menu [1], clique com o botão direito do rato na página no navegador;
- em [2], o URL da página [example-01.php], mas precedido por [view-source:] [3];
- em [4], o conteúdo HTML que o navegador exibiu;
É, portanto, importante lembrar que um script PHP destinado a ser executado por um servidor web deve produzir um fluxo HTML.
Vamos agora ver (F12) os cabeçalhos HTTP enviados pelo servidor ao navegador do cliente:

- em [3], um cabeçalho HTTP que não estava presente quando a página estática foi solicitada. Este cabeçalho indica que a resposta do servidor foi gerada por um script PHP;
Vimos que a resposta do servidor (a saída HTML, neste caso) pode ser gerada por um script PHP. O script também pode gerar os cabeçalhos HTTP e praticamente todos os elementos da resposta do servidor.
17.4. Noções básicas de HTML
Este capítulo não se aprofundará na programação web em PHP. Uma aplicação web MVC é desenvolvida na secção indicada. Em vez disso, este capítulo centra-se nos serviços web: páginas PHP que fornecem dados, através de um servidor web, a outros clientes PHP. No entanto, considerámos que seria útil fornecer ao leitor alguns conceitos básicos de HTML.
Um navegador web pode exibir vários documentos, sendo os mais comuns os documentos HTML (HyperText Markup Language). Estes consistem em texto formatado com tags na forma <tag>texto</tag>. Assim, o texto <b>importante</b> exibirá o texto «importante» em negrito. Existem tags independentes, como a tag <hr/>, que exibe uma linha horizontal. Não iremos abordar todas as tags que podem ser encontradas num texto HTML. Existem muitos programas de software WYSIWYG que permitem criar uma página web sem escrever uma única linha de código HTML. Estas ferramentas geram automaticamente o código HTML para um layout criado utilizando o rato e controlos predefinidos. Pode, assim, inserir (utilizando o rato) uma tabela na página e, em seguida, visualizar o código HTML gerado pelo software para descobrir as tags a utilizar na definição de uma tabela numa página web. É tão simples quanto isso. Além disso, o conhecimento de HTML é essencial, uma vez que as aplicações web dinâmicas têm de gerar elas próprias o código HTML para enviar aos clientes web. Este código é gerado programaticamente e, naturalmente, é necessário saber o que gerar para que o cliente receba a página web que deseja.
Em suma, não precisa de conhecer toda a linguagem HTML para começar a programar para a Web. No entanto, este conhecimento é necessário e pode ser adquirido utilizando construtores de páginas Web WYSIWYG, como o DreamWeaver e dezenas de outros. Outra forma de descobrir as complexidades do HTML é navegar na Web e visualizar o código-fonte de páginas que apresentam elementos interessantes com os quais ainda não se deparou.
Considere o exemplo seguinte, que destaca alguns elementos comumente encontrados num documento web, tais como:
- uma tabela;
- uma imagem;
- um link.

Um documento HTML tem geralmente a seguinte forma:
<html> <head> <title>Um título</title> ... </head> <atributos do corpo> ... </body></html>
Todo o documento está delimitado pelas tags <html>…</html>. É composto por duas partes:
- <head>…</head>: esta é a parte não exibível do documento. Fornece informações ao navegador que irá exibir o documento. Contém frequentemente a tag <title>…</title>, que define o texto a ser exibido na barra de título do navegador. Pode também conter outras tags, nomeadamente as que definem as palavras-chave do documento, que são depois utilizadas pelos motores de busca. Esta secção pode ainda conter scripts, geralmente escritos em JavaScript ou VBScript, que serão executados pelo navegador.
- <body attributes>…</body>: Esta é a secção que será exibida pelo navegador. As tags HTML contidas nesta secção indicam ao navegador o layout visual «desejado» para o documento. Cada navegador interpreta estas tags à sua maneira. Como resultado, dois navegadores podem exibir o mesmo documento web de forma diferente. Este é geralmente um dos desafios enfrentados pelos web designers.
O código HTML para o nosso documento de exemplo é o seguinte:
<!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>
Etiquetas HTML e exemplos | |
<title>Algumas tags HTML</title> (linha 5) O texto [Algumas tags HTML] aparecerá na barra de título do navegador quando o documento for exibido | |
<hr />: exibe uma linha horizontal (linha 10) | |
<table attributes>….</table>: para definir a tabela (linhas 12, 32) <thead>…</thead>: para definir os cabeçalhos das colunas (linhas 13, 19) <tbody>…</tbody>: para definir o conteúdo da tabela (linhas 20, 31) <atributos tr>…</tr>: para definir uma linha (linhas 21, 25) <td attributes>…</td>: para definir uma célula (linha 22) exemplos: <table border="1">…</table>: o atributo border define a espessura da borda da tabela <td style="text-align: center;">cell(1,2)</td> (linha 23): define uma célula cujo conteúdo será cell(1,2). Este conteúdo será centralizado horizontalmente (text-align: center). | |
<img border="0" src="images/cherrytree.jpg"/> (linha 38): define uma imagem sem borda (border="0") cujo ficheiro de origem é [images/cherrytree.jpg] no servidor web (src="images/cherrytree.jpg"). Esta ligação está localizada num documento web acessível através do URL http://localhost/php7/scripts-web/01/balises.html. Por conseguinte, o navegador irá solicitar o URL http://localhost/php7/scripts-web/01/images/cerisier.jpg para recuperar a imagem aqui referenciada. | |
<a href="http://www.polytech-angers.fr/fr/index.html">aqui</a> (linha 42): faz com que o texto «aqui» funcione como um link para a URL http://www.polytech-angers.fr/fr/index.html. | |
<body style="background-image: url(images/standard.jpg)"> (linha 8): indica que a imagem a ser utilizada como fundo da página está localizada no URL [images/standard.jpg] no servidor web. No contexto do nosso exemplo, o navegador irá solicitar o URL http://localhost/php7/scripts-web/01/images/standard.jpg para recuperar esta imagem de fundo. |
Podemos ver neste exemplo simples que, para construir todo o documento, o navegador deve fazer três pedidos ao servidor:
- http://localhost/php7/scripts-web/01/images/balises.html para recuperar o código-fonte HTML do documento
- http://localhost/php7/scripts-web/01/images/cerisier.jpg para recuperar a imagem cerisier.jpg
- http://localhost/php7/scripts-web/01/images/standard.jpg para recuperar a imagem de fundo standard.jpg
Isto é demonstrado pelo tráfego de rede entre o cliente e o servidor (F12 no navegador):

- Em [3-5], vemos os três pedidos efetuados pelo navegador;
17.5. Tornar uma página estática dinâmica
Vamos mostrar como podemos tornar a página HTML [example-01.html] dinâmica. Copie o conteúdo

Copiámos o conteúdo de [example-01.html] para o ficheiro [page-01.php]. Se executarmos [2] este script web, vemos o seguinte no navegador:

- em [3], o URL solicitado;
- em [4], o título da página;
- em [5], o conteúdo da página;
Se analisarmos o código recebido pelo navegador, encontramos o seguinte:

- em [7], o código HTML colocado no script [example-01.php]
O interpretador PHP interpretou o script [page-01.php] e produziu a mesma saída HTML que a página estática [example-01.html]. No script [page-01.php], não havia PHP, apenas HTML. Isto ensina-nos algo: quando o interpretador PHP encontra HTML num script PHP, ignora-o e envia-o tal como está para o cliente.
Agora vamos adicionar algumas instruções PHP ao script [page-01.php] para que o interpretador PHP tenha algo para fazer:
<!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>
Nas linhas 4 e 9, adicionámos código PHP para gerar dinamicamente o título e o conteúdo da página. Aqui, assumimos que a variável [$page] é um objeto que contém os dados a serem exibidos.
Se executarmos este novo código, obtemos o seguinte resultado no navegador:

- em [1], o URL solicitado;
- em [2], o título da página não pôde ser exibido porque a variável [$page] não estava definida;
- em [3], o mesmo se aplica ao conteúdo;
Agora, vamos escrever o seguinte script web [example-02.php]:

O script [example-02.php] será o seguinte:
<?php
// define the page elements to be displayed
$page=new \stdclass();
$page->title="Un nouveau titre";
$page->contents="Un nouveau contenu généré dynamiquement";
// display [page-01]
require_once "page-01.php";
- linhas 4-6: definimos o objeto [$page];
- linha 8: inclui o script [page-01.php]. O código deste script será então interpretado:
- a variável [$page] está agora definida e o interpretador PHP irá utilizá-la;
- o código HTML de [page-01.php] será enviado tal como está para o cliente;
- os resultados das operações [print] do PHP serão incluídos no fluxo de texto enviado ao cliente;
Agora, se executarmos o script web [example-02.php], obtemos o seguinte no navegador:

Se visualizarmos o conteúdo de texto recebido pelo navegador:

- o código PHP que estava em [2] e [3] foi substituído pelos resultados dos dois comandos [print];
A partir deste exemplo, podemos retirar duas conclusões importantes:
- As páginas HTML destinadas ao navegador podem ser isoladas em scripts PHP que contenham apenas código HTML e algumas partes dinâmicas geradas por código PHP. Deve haver o mínimo possível de PHP nessas páginas;
- toda a lógica que gera os dados dinâmicos incluídos nas páginas HTML deve ser isolada em scripts de PHP puro, sem código de apresentação da página (HTML, CSS, JavaScript, etc.);
Isto permite uma separação de tarefas:
- a tarefa de criar as páginas web a serem apresentadas (HTML, CSS, JavaScript, etc.);
- a tarefa da lógica da aplicação web que estamos a construir. Esta lógica pode ser implementada utilizando uma arquitetura de três camadas, exatamente como fizemos com os scripts de consola;
A seguir, iremos construir scripts web específicos;
- eles enviarão apenas dados para o cliente e nenhum elemento de apresentação (HTML, CSS, JavaScript). Serão, portanto, servidores de dados em vez de páginas web;
- Os clientes destes scripts web serão scripts de consola que irão recuperar os dados enviados pelo servidor e processá-los;
17.6. Aplicação cliente/servidor de data/hora
Estamos agora na seguinte configuração:

Iremos escrever:
- um script web [1] que envia a data e a hora atuais para o seu cliente;
- um script de consola [2] que atuará como cliente do script web: irá recuperar a data e a hora enviadas pelo script web e exibi-las na consola;

- em [1], o script web [date-time-server.php];
- em [2], o script de consola [date-time-client], que é o cliente do script web;
17.6.1. O script do servidor
Já escrevemos um script web que gera a data e a hora atuais na secção com o link. Era o seguinte script [example-01.php]:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Exemple de page dynamique</title>
</head>
<body>
<?php
// time : nb de millisecondes depuis 01/01/1970
// format affichage date-heure
// d: jour sur 2 chiffres
// m: mois sur 2 chiffres
// y : année sur 2 chiffres
// H : heure 0,23
// i : minutes
// s: secondes
print "<b>Date et heure du jour : </b>" . date("d/m/y H:i:s", time());
?>
</body>
</html>
Dissemos que íamos escrever servidores de dados: dados brutos sem marcação HTML. O script do servidor [date-time-server.php] ficará então da seguinte forma:
<?php
// set header HTP [Content-Type]
header('Content-Type: text/plain; charset=UTF-8');
//
// send date and time
// time: number of milliseconds since 01/01/1970
// date-time display format
// d: 2-digit day
// m: 2-digit month
// y: 2-digit year
// H: hour 0.23
// i : minutes
// s: seconds
print date("d/m/y H:i:s", time());
- Linha 4: Definimos o cabeçalho HTTP [Content-Type], que indica ao cliente a natureza do documento que irá receber. Até agora, o [Content-Type] era: [Content-Type: text/html; charset=UTF-8]. Aqui, informamos ao cliente que o documento é texto simples sem marcação HTML. Isto não é importante para o nosso cliente de consola, que não utilizará este cabeçalho. É mais importante para os clientes de navegador, que utilizam este cabeçalho;
Vamos executar este script do lado do servidor:

Se examinarmos a resposta do servidor no navegador (F12), vemos em [5] o cabeçalho HTTP que o script do servidor definiu e em [8], o documento de texto recebido;

17.6.2. O script do lado do cliente
Na secção anterior, desenvolvemos vários clientes HTTP. Poderíamos usá-los para recuperar o documento de texto enviado pelo script do servidor [date-time-server.php]. Não o faremos. Tal como fizemos para os protocolos SMTP e IMAP, utilizaremos uma biblioteca de terceiros, nomeadamente o componente [HttpClient] do framework Symfony [https://symfony.com/doc/master/components/http_client.html].
Tal como nas duas bibliotecas anteriores, utilizamos a ferramenta [Composer] para instalar o componente [HttpClient] do Symfony. Numa janela do [Terminal] do Laragon (consulte a secção indicada no link), introduza o seguinte comando:

- em [3], verifique se está no diretório [<laragon>/www/], onde <laragon> é o diretório de instalação do Laragon;
- em [4], o comando [composer] que instala a biblioteca [HttpClient] do Symfony;
- em [5], nada é instalado porque a biblioteca [HttpClient] já estava instalada nesta máquina;
- Em [6-7], surgem novas pastas em [<laragon>/www/vendor/symfony];
Em vez de [5], deverá ter algo semelhante ao seguinte:
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
Certifique-se de que a pasta [<laragon>/www/vendor] está incluída no [Include Path] do seu projeto (consulte a secção indicada):

Depois de fazer isso, podemos escrever o script de consola [date-time-client.php]:

O script de consola [date-time-client.php] utilizará o seguinte ficheiro JSON [config-date-time-client.json]:
- linha 2: o URL do script do servidor;
O script do cliente [date-time-client.php] será o seguinte:
<?php
// service customer date / time
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
// customer configuration
const CONFIG_FILE_NAME = "config-date-time-client.json";
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
exit;
}
// create a HTTP customer
$httpClient = HttpClient::create();
try {
// query
$response = $httpClient->request('GET', $config['url']);
// answer status
$statusCode = $response->getStatusCode();
print "---Réponse avec statut : $statusCode\n";
// we retrieve the headers
print "---Entêtes de la réponse\n";
$headers = $response->getHeaders();
foreach ($headers as $type => $value) {
print "$type: " . $value[0] . "\n";
}
// retrieve the body of the reply
$content = $response->getContent();
// we display it
print "---Réponse du serveur : [$content]\n";
} catch (TypeError | RuntimeException $ex) {
// error is displayed
print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
exit;
}
Comentários
- linha 10: tal como fizemos para as bibliotecas anteriores, carregamos o ficheiro [<laragon>/www/vendor/autoload.php];
- linha 11: declaramos a classe [HttpClient] que iremos utilizar;
- linhas 13–24: recuperamos a configuração do script do dicionário [$config];
- linha 27: criamos um objeto do tipo [HttpClient];
- linha 31: solicitamos a URL do script do servidor utilizando uma solicitação GET: [GET URL HTTP/1.1]. Esta operação é assíncrona. A execução continua na linha 33 sem esperar pela resposta;
- linha 33: o estado da resposta é recuperado. Este estado encontra-se no primeiro cabeçalho HTTP devolvido pelo servidor. Assim, se este cabeçalho for [HTTP/1.1 200 OK], o estado da resposta é 200. Esta operação é bloqueante: a execução só recomeça depois de o cliente ter recebido a resposta completa do servidor;
- linha 37: os cabeçalhos HTTP da resposta são solicitados;
- linha 42: recuperamos o documento devolvido pelo servidor: sabemos que este documento é texto.
- linhas 45–49: se ocorrer um erro, a mensagem de erro é exibida;
Quando o script do cliente é executado (o Laragon deve estar em execução para que o script do servidor esteja acessível), o seguinte resultado é exibido na consola:
---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]
Recuperamos com sucesso a data e a hora atuais na linha 8.
Talvez esteja curioso para saber o que o script do cliente enviou para o servidor. Para isso, vamos utilizar o nosso servidor TCP genérico (consulte a secção «link»):

- em [1], a pasta utilitários;
- em [2], o servidor TCP está a funcionar na porta 100;
- em [3], à espera de um comando introduzido através do teclado;
Modificamos o ficheiro de configuração do script [date-time-client.php]:
{
"url": "http://localhost:100/php7/scripts-web/02/date-time-server.php"
}
Desta vez, o cliente contacta o servidor [localhost] na porta 100. Por conseguinte, o nosso servidor TCP genérico será chamado. Quando executamos o script de consola [date-time-client.php], a consola do servidor TCP genérico altera-se da seguinte forma:

- em [3], o pedido HTTP GET construído pelo script do cliente;
- em [4], a assinatura do script da consola;
- em [5], a resposta do servidor ao script do cliente. Note-se que esta não é uma resposta HTTP válida:
- deveriam existir cabeçalhos HTTP;
- seguidos de uma linha em branco;
- depois, o documento de texto enviado ao cliente;
- em [6], encerramos a ligação com o script do cliente para que este detete que recebeu a resposta na íntegra;
No script do lado do cliente, a consola exibe o seguinte:

- em [7], o que o cliente Symfony recebeu;
17.6.3. O script do servidor – versão 2
Por predefinição, as funções PHP para escrever um script web não são orientadas a objetos. No lado do servidor, somos, portanto, obrigados a misturar classes e funções PHP tradicionais. Para alcançar um estilo de codificação mais consistente, utilizaremos a biblioteca [HttpFoundation] do framework Symfony. Ela encapsula todas as funções PHP tradicionais para um serviço web num sistema de classes e interfaces. A documentação da biblioteca está disponível em [https://symfony.com/doc/current/components/http_foundation.html] (maio de 2019).
Para instalar a biblioteca, siga estes passos num terminal Laragon (ver secção com link):

- [2-3]: Certifique-se de que está na pasta [<laragon>/www];
- [4]: o comando [composer], que irá instalar a biblioteca [HttpFoundation];
- [5]: Neste exemplo, a biblioteca já estava instalada;
Após a primeira instalação, deverá ver registos de consola semelhantes aos seguintes:
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
A segunda versão do servidor web [date-time-server-2.php] é a seguinte:
<?php
// using Symfony libraries
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpFoundation\Response;
// we set the Content-Type header
$response=new Response();
$response->headers->set("content-type","text/plain");
$response->setCharset("utf-8");
// set the content of the response
//
// send date and time
// time: number of milliseconds since 01/01/1970
// date-time display format
// d: 2-digit day
// m: 2-digit month
// y: 2-digit year
// H: hour 0.23
// i : minutes
// s: seconds
$response->setContent(date("d/m/y H:i:s", time()));
// we send the answer
$response->send();
Comentários
- Linha 7: A classe [Response] da biblioteca [HttpFoundation] do Symfony lida com toda a resposta aos clientes do serviço web;
- linha 10: criação de uma instância da classe [Response];
- linha 11: especifica que a resposta é do tipo [text/plain];
- linha 12: a resposta é texto UTF-8;
- linha 25: o corpo da resposta é definido como o conteúdo solicitado pelo cliente;
- linha 28: a resposta é enviada ao cliente;
17.6.4. O script do cliente – versão 2
O script do cliente permanece inalterado. Apenas modificamos o seu ficheiro de configuração [config-date-time-client.json]:
Os resultados são os mesmos da versão 1.
17.7. Um servidor de dados JSON
A resposta de um script web pode consistir em vários pontos de dados que podem ser organizados em matrizes e objetos. O script pode então enviar esses vários elementos dentro de uma cadeia JSON que o cliente irá descodificar.

17.7.1. O script do servidor
O script [json-server.php] utiliza a seguinte classe [Person]:
<?php
namespace Modèles;
class Personne implements \JsonSerializable {
// attributes
private $nom;
private $prénom;
private $âge;
// convert associative array to object [Person]
public function setFromArray(array $assoc): Personne {
// initialize the current object with the associative array
foreach ($assoc as $attribute => $value) {
$this->$attribute = $value;
}
// result
return $this;
}
// getters and setters
public function getNom() {
return $this->nom;
}
public function getPrénom() {
return $this->prénom;
}
public function setNom($nom) {
$this->nom = $nom;
return $this;
}
public function setPrénom($prénom) {
$this->prénom = $prénom;
return $this;
}
public function getÂge() {
return $this->âge;
}
public function setÂge($âge) {
$this->âge = $âge;
return $this;
}
// toString
public function __toString(): string {
return "Personne [$this->prénom, $this->nom, $this->âge]";
}
// implements the JsonSerializable interface
public function jsonSerialize(): array {
// render an associative array with the object's attributes as keys
// this table can then be encoded as jSON
return get_object_vars($this);
}
// convert a jSON to a [Person] object
public static function jsonUnserialize(string $json): Personne {
// we create a person from the string jSON
return (new Personne())->setFromArray(json_decode($json, true));
}
}
Comentários
- linha 5: a classe implementa a interface [JsonSerializable] do PHP. Isto requer que ela implemente o método [jsonSerialize] nas linhas 55–59. O método deve devolver um array associativo que será serializado para JSON. Ao utilizar a expressão [json_encode($person)], a função [json_encode] verifica se a classe [Person] implementa a interface [JsonSerializable]. Se for o caso, a expressão torna-se [json_encode($person→serialize())];
- Linhas 12–19: A classe não possui um construtor, mas possui um inicializador. A classe [Person] pode então ser instanciada utilizando a expressão [(new Person()) → setFromArray($array)]. Podem existir vários tipos de inicializadores, enquanto que só pode existir um construtor. Estes inicializadores permitem vários modos de instanciação da forma [(new Person())→initializer(…));
- linhas 62–65: A função estática [jsonUnserialize] permite criar um objeto [Person] a partir da sua cadeia JSON;
O script [json-server.php] será o seguinte:
<?php
// dependencies
require_once __DIR__ . "/Personne.php";
use \Modèles\Personne;
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
// set the Content-Type header and the character library used
$response = new Response();
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
// create a Person object
$personne = (new Personne())->setFromArray([
"nom" => "de la Hûche",
"prénom" => "jean-paul",
"âge" => 27]);
// an associative table
$assoc = ["attr1" => "value1",
"attr2" => [
"prenom" => "Jean-Paul",
"nom" => "de la Hûche"
]
];
// the content of the response is jSON
$response->setContent(json_encode([$personne, $assoc]));
// reply sent
$response->send();
Comentários
- linhas 4-5: importamos a classe [Person];
- linha 11: especificamos que o documento será do tipo [application/json]. Ao receber este cabeçalho, os navegadores exibirão a cadeia JSON formatada, em vez de texto simples;
- linha 12: a cadeia JSON conterá caracteres UTF-8;
- linhas 15-18: criamos um objeto [Person];
- linhas 20–25: é criada uma matriz associativa de dois níveis;
- linha 27: enviamos a cadeia JSON de uma matriz para o cliente:
- o elemento [$person] será serializado para JSON utilizando o seu método [jsonSerialize];
- o elemento [$assoc] será serializado nativamente para JSON;
Se executarmos este script do lado do servidor (o Laragon deve estar em execução), obtemos a seguinte resposta num navegador:


Comentários
- em [2], a resposta JSON formatada;
- em [4], a resposta JSON não formatada. Repare na codificação dos caracteres acentuados;
- Em [6], foi o tipo de conteúdo [application/json] enviado pelo servidor que levou o navegador a formatar a saída desta forma;
17.7.2. O cliente

O cliente [json-client.php] é configurado pelo seguinte ficheiro JSON [config-json-client.json]:
O script [json-client.php] é o seguinte:
<?php
// service customer jSON
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
require_once __DIR__ . "/Personne.php";
use \Modèles\Personne;
// customer configuration
const CONFIG_FILE_NAME = "config-json-client.json";
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
exit;
}
// create a HTTP customer
$httpClient = HttpClient::create();
try {
// query
$response = $httpClient->request('GET', $config['url']);
// answer status
$statusCode = $response->getStatusCode();
print "---Réponse avec statut : $statusCode\n";
// we retrieve the headers
print "---Entêtes de la réponse\n";
$headers = $response->getHeaders();
foreach ($headers as $type => $value) {
print "$type: " . $value[0] . "\n";
}
// retrieve the jSON body of the response
list($personne, $assoc) = json_decode($response->getContent(), true);
// a person is instantiated from an array of attributes
$personne = (new Personne())->setFromArray($personne);
// server response is displayed
print "---Réponse du serveur\n";
print "$personne\n";
print "tableau=" . json_encode($assoc, JSON_UNESCAPED_UNICODE) . "\n";
} catch (TypeError | RuntimeException $ex) {
// error is displayed
print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}
Comentários
- linhas 12-13: importar a classe [Person];
- linha 30: crie o cliente HTTP;
- linha 44: descodificação da cadeia JSON enviada pelo servidor. Sabemos que o que foi codificado é uma matriz de dois elementos que contém duas matrizes associativas;
- linha 46: criar um objeto [Person] para o exibir na linha 49;
- linha 50: o segundo array associativo é exibido. A instrução [print] não consegue exibir arrays. Por isso, convertemos este num string JSON. Para exibir corretamente os caracteres acentuados, temos de definir o segundo parâmetro como [JSON_UNESCAPED_UNICODE]. Vimos que os caracteres acentuados estão, de facto, codificados no string JSON;
A execução do script do lado do cliente produz os seguintes resultados:
---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"}}
Linhas 11 e 12: os caracteres acentuados foram recuperados corretamente.
17.8. Recuperação de variáveis de ambiente do serviço web
Um script de servidor é executado num ambiente web ao qual tem acesso. Este ambiente é armazenado no dicionário $_SERVER, uma variável global do PHP. Se utilizarmos a biblioteca [HttpFoundation], este ambiente será encontrado no campo [Request→server], onde [Request] é o pedido HTTP processado pelo script web.
17.8.1. O script de servidor
Estamos a escrever uma aplicação de servidor que envia o seu ambiente de execução aos seus clientes.

O script web [env-server.php] é o seguinte:
<?php
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
// we retrieve the request
$request = Request::createFromGlobals();
// we work out the answer
$response = new Response();
// response content is json utf-8
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
// set the jSON content of the response
$response->setContent(json_encode($request->server->all()));
// reply sent
$response->send();
- linha 9: recuperamos o objeto [Request], que encapsula todas as informações disponíveis sobre o pedido HTTP recebido pelo script web, bem como o seu ambiente de execução;
- linhas 13–14: enviamos texto simples com caracteres UTF-8 para o cliente;
- linha 16: a informação enviada ao cliente será uma cadeia de caracteres obtida pela serialização JSON do objeto [$request→server→all()]: [$request→server] representa o ambiente de execução do script web. É um objeto do tipo [ServerBag], uma espécie de dicionário. [$request→server→all()] é um dicionário verdadeiro, contendo o conteúdo do [ServerBag];
- Linha 18: A informação é enviada;
Se este script for executado a partir do NetBeans, o navegador exibe a seguinte página:

- em [2], as várias chaves do dicionário de ambiente;
- em [3], os valores dessas chaves;
17.8.2. O script do cliente

O script do cliente [env-client.php] é configurado pelo seguinte ficheiro JSON [config-env-client.json]:
O script do cliente [env-client.php] é o seguinte:
<?php
// server script environment
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
// customer configuration
const CONFIG_FILE_NAME = "config-env-client.json";
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
exit;
}
// create a HTTP customer
$httpClient = HttpClient::create();
try {
// make a request to the server
$response = $httpClient->request('GET', $config['url']);
// answer status
$statusCode = $response->getStatusCode();
print "---Réponse avec statut : $statusCode\n";
// we retrieve the headers
print "---Entêtes de la réponse\n";
$headers = $response->getHeaders();
foreach ($headers as $type => $value) {
print "$type: " . $value[0] . "\n";
}
// server response is displayed
print "---Réponse du serveur\n";
$env = json_decode($response->getContent());
foreach ($env as $key => $value) {
print "[$key]=>$value\n";
}
} catch (TypeError | RuntimeException $ex) {
// error is displayed
print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}
Comentários
- linha 42: deserializa a resposta JSON do servidor. Isto devolve um hash;
- linhas 43–45: exibe todos os valores desta matriz associativa;
O seguinte resultado é obtido na consola:
---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
Aqui está o significado de algumas das variáveis (para Windows. No Linux, seriam diferentes):
O valor xxx do cabeçalho HTTP [Host: xxx] enviado pelo cliente | |
o valor xxx do cabeçalho HTTP [User_Agent: xxx] enviado pelo cliente | |
o valor xxx do cabeçalho HTTP [Accept-Encoding: xxx] enviado pelo cliente | |
o caminho para os executáveis na máquina onde o script do servidor está a ser executado | |
o caminho do prompt de comando do DOS | |
extensões de ficheiros executáveis | |
a pasta de instalação do Windows | |
a assinatura do servidor web. Não há nada aqui. | |
o tipo de servidor web | |
O nome de Internet do servidor web | |
A porta de escuta do servidor web | |
o endereço IP do servidor web, neste caso 127.0.0.1 | |
o endereço IP do cliente. Aqui, o cliente estava na mesma máquina que o servidor. | |
a porta de comunicação do cliente | |
a raiz da árvore de diretórios dos documentos servidos pelo servidor web | |
o protocolo TCP do pedido de URL (http://localhost/php7/, etc.) | |
o endereço de e-mail do administrador do servidor web | |
o caminho completo do script do servidor | |
a porta a partir da qual o cliente efetuou o seu pedido | |
a versão do protocolo HTTP utilizada pelo servidor web | |
o método HTTP utilizado pelo cliente. Existem quatro: GET, POST, PUT, DELETE | |
os parâmetros enviados com um pedido GET /url?parâmetros | |
A URL solicitada pelo cliente. Se o navegador solicitar a URL http://machine[:port]/uri, REQUEST_URI será uri | |
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'].$_SERVER['SCRIPT_NAME'] |
17.9. Recuperação pelo servidor dos parâmetros enviados por um cliente
17.9.1. Introdução
No protocolo HTTP, um cliente dispõe de dois métodos para passar parâmetros ao servidor web:
- solicita o URL do serviço no formulário
GET url?param1=val1¶m2=val2¶m3=val3… HTTP/1.0
onde os valores válidos devem primeiro ser codificados para que determinados caracteres reservados sejam substituídos pelos seus valores hexadecimais;
- solicita o URL do serviço na forma
POST url HTTP/1.0
então, entre os cabeçalhos HTTP enviados ao servidor, inclui o seguinte cabeçalho:
O resto dos cabeçalhos enviados pelo cliente terminam com uma linha em branco. Pode então enviar os seus dados na forma
onde os valores válidos devem, tal como no método GET, ser codificados previamente. O número de caracteres enviados ao servidor deve ser N, sendo N o valor declarado no cabeçalho
O script PHP do serviço web que recupera os parâmetros parami anteriores enviados pelo cliente obtém os seus valores a partir da matriz:
- $_GET["parami"] para um pedido GET;
- $_POST["parami"] para uma solicitação POST;
Isto aplica-se às funções básicas do PHP. Se for utilizada a biblioteca [HttpFoundation], estes parâmetros serão encontrados em:
- [Request]->query->get('parami') para uma solicitação GET;
- [Request]->request->get('parami') para uma solicitação POST;
onde [Request] representa toda a informação sobre a solicitação recebida pelo script web;
17.9.2. O cliente GET – versão 1

Os scripts do cliente são configurados utilizando o seguinte ficheiro JSON [config-parameters-client.json]:
- linha 1: o URL do script web de destino para clientes GET;
- linha 2: a URL do script web de destino para clientes POST;
Os clientes GET enviam três parâmetros [last_name, first_name, age] para o servidor. O cliente [parameters-get-client.php] é o seguinte:
<?php
// client GET of a web server
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
// customer configuration
const CONFIG_FILE_NAME = "config-parameters-client.json";
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
exit;
}
// create a HTTP customer
$httpClient = HttpClient::create();
try {
// prepare the parameters
list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
// information is encoded
$parameters = "prenom=" . urlencode($prenom) .
"&nom=" . urlencode($nom) .
"&age=$age”;
// query
$response = $httpClient->request('GET', $config['url-get'] . "?$parameters");
// answer status
$statusCode = $response->getStatusCode();
print "---Réponse avec statut : $statusCode\n";
// we retrieve the headers
print "---Entêtes de la réponse\n";
$headers = $response->getHeaders();
foreach ($headers as $type => $value) {
print "$type: " . $value[0] . "\n";
}
// server response is displayed
print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
// error is displayed
print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}
Comentários
- linhas 33-35: codificação dos parâmetros enviados para o servidor. Os parâmetros [$first_name, $last_name], que podem conter caracteres UTF-8, são codificados utilizando a função [urlencode]. Todos os caracteres não alfanuméricos (conforme definido por expressões relacionais) são substituídos por %xx, onde xx é o valor hexadecimal do caractere. Os espaços são substituídos pelo sinal +;
- linha 37: o URL solicitado é $URL?$parameters, onde $parameters tem o formato name=val1&firstname=val2&age=val3;
- linha 48: o cliente irá simplesmente apresentar a resposta do servidor;
Talvez esteja curioso para ver o que o servidor recebe durante um pedido GET parametrizado. Para tal, iniciamos o nosso servidor genérico [RawTcpServer] na porta 100 da máquina local a partir de um terminal Laragon (ver secção de links):

Verifique se, em [4], se encontra efetivamente na pasta utilities.
Modificamos o ficheiro JSON [parameters-get-client.json] que configura os clientes GET e POST:
{
"url-get": "http://localhost:100/php7/scripts-web/05/parameters-server.php",
"url-post": "http://localhost/php7/scripts-web/05/parameters-server.php"
}
- Linha 2: Alterámos a porta do servidor web. Por conseguinte, será estabelecida uma ligação com o [RawTcpServer];
Executamos o cliente. Na janela [RawTcpServer], vemos as seguintes informações:

- em [1], o pedido GET enviado pelo cliente. Podemos ver claramente a codificação de determinados caracteres;
17.9.3. O servidor GET / POST

O script do servidor [parameters-server.php] é o seguinte:
<?php
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
// we retrieve the request
$request = Request::createFromGlobals();
// retrieve query parameters
$getParameters = $request->query->all();
$bodyParameters = $request->request->all();
// we work out the answer
$response = new Response();
// the content of the answer is utf-8 text
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
// response content - an array encoded in jSON
$response->setContent(json_encode([
"method" => $request->getMethod(),
"uri" => $request->getRequestUri(),
"getParameters" => $getParameters,
"bodyParameters" => $bodyParameters
], JSON_UNESCAPED_UNICODE));
// reply sent
$response->send();
Comentários
- linha 9: criação do objeto [Request] para o script web. Este objeto encapsula toda a informação que o script web recebeu do cliente;
- linha 11: o objeto [Request→query] é do tipo [ParameterBag] e recolhe os parâmetros de qualquer operação GET de um cliente. A expressão [Request→query→get("X")] recupera o parâmetro denominado X dos parâmetros GET [name=val1&firstname=val2&age=val3]. A expressão [Request→query→all()] recupera o dicionário de parâmetros GET;
- linha 12: o objeto [Request→request] é do tipo [ParameterBag] e contém os parâmetros enviados como um documento do cliente para o servidor. Diz-se também que estes parâmetros são carregados porque pertencem a um documento que o cliente envia ao servidor. A expressão [Request→request→get("X")] recupera o parâmetro denominado X dos parâmetros carregados [last_name=val1&first_name=val2&age=val3]. A expressão [Request→request→all()] recupera o dicionário de parâmetros carregados;
- linhas 17–18: o cliente é informado de que receberá JSON codificado em UTF-8;
- linhas 20–25: o servidor devolve ao cliente todos os parâmetros que recebeu, o tipo de operação [GET / POST / …] realizada pelo cliente e o URI solicitado. Este método é obtido através da expressão [$request→getMethod()]. O documento enviado ao cliente é a cadeia JSON de um tabela associativa, alguns dos quais são, por sua vez, tabelas associativas. O parâmetro [JSON_UNESCAPED_UNICODE] garante que os caracteres Unicode (como caracteres acentuados, por exemplo) sejam enviados tal como estão e não codificados;
- linha 27: a resposta é enviada ao cliente;
A execução do script do lado do cliente produz os seguintes resultados:
- linha 10:
- [método]: o método é GET;
- [uri]: os parâmetros codificados em URL da solicitação GET estão visíveis na URI solicitada;
- [getParameters]: o conjunto de parâmetros GET;
- [bodyParameters]: o conjunto de parâmetros carregados: está vazio;
17.9.4. O cliente GET – versão 2
Na versão anterior do script do cliente, codificámos nós próprios os parâmetros enviados para o servidor, para fins didáticos. O objeto [HttpClient] consegue lidar com esta tarefa sozinho. Aqui está o script correspondente [parameters-get-client-2.php]:
<?php
// client GET of a web server
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
// customer configuration
const CONFIG_FILE_NAME = "config-parameters-client.json";
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
exit;
}
// create a HTTP customer
$httpClient = HttpClient::create();
try {
// prepare the parameters
list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
// make a request to the server
$response = $httpClient->request('GET', $config['url-get'],
["query" => [
"prenom" => $prenom,
"nom" => $nom,
"age" => $age
]]);
// answer status
$statusCode = $response->getStatusCode();
print "---Réponse avec statut : $statusCode\n";
// we retrieve the headers
print "---Entêtes de la réponse\n";
$headers = $response->getHeaders();
foreach ($headers as $type => $value) {
print "$type: " . $value[0] . "\n";
}
// server response is displayed
print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
// error is displayed
print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}
Comentários
- linhas 33–37: adicionar parâmetros à solicitação GET da linha 32. O objeto [HttpClient] tratará da codificação da URL por si próprio;
17.9.5. O cliente POST
Um cliente HTTP envia a seguinte sequência de texto para o servidor web: cabeçalhos HTTP, linha em branco, documento. No cliente anterior, esta sequência era a seguinte:
Não havia nenhum documento. Existe outra forma de enviar parâmetros, conhecida como método POST. Neste caso, a sequência de texto enviada ao servidor web é a seguinte:
Desta vez, os parâmetros que foram incluídos nos cabeçalhos HTTP para o cliente GET fazem parte do documento enviado após os cabeçalhos no cliente POST.
O script do cliente POST [parameters-postclient.php] é o seguinte:
<?php
// client POST of a web server
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
// customer configuration
const CONFIG_FILE_NAME = "config-parameters-client.json";
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
exit;
}
// create a HTTP customer
$httpClient = HttpClient::create();
try {
// prepare the parameters
list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
// make a request to the server
$response = $httpClient->request('POST', $config['url-post'],
["body" => [
"prenom" => $prenom,
"nom" => $nom,
"age" => $age
]]);
// answer status
$statusCode = $response->getStatusCode();
print "---Réponse avec statut : $statusCode\n";
// we retrieve the headers
print "---Entêtes de la réponse\n";
$headers = $response->getHeaders();
foreach ($headers as $type => $value) {
print "$type: " . $value[0] . "\n";
}
// server response is displayed
print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
// error is displayed
print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}
- linha 32: temos agora uma solicitação HTTP POST;
- linhas 33–37: os parâmetros POST são chamados de corpo da solicitação POST: este é o documento enviado pelo cliente ao servidor. Aqui, são enviados três parâmetros [last_name, first_name, age];
- linha 48: exibimos a resposta JSON do servidor;
Os resultados da execução do script do cliente são os seguintes:
---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"}}]
- Linha 10: o método é [Post] e os parâmetros são do tipo [bodyParameters]. Não há [getParameters], conforme indicado pelo [uri];
Talvez esteja curioso para ver o que o servidor recebe durante um pedido POST. Para tal, lançamos o nosso [RawTcpServer] genérico na porta 100 da máquina local a partir de um terminal Laragon (ver parágrafo em link):

Verifique se, em [4], se encontra efetivamente na pasta utilities.
Modificamos o ficheiro JSON [config-parameters-client.json] que configura o cliente POST:
{
"url-get": "http://localhost:100/php7/scripts-web/05/parameters-server.php",
"url-post": "http://localhost:100/php7/scripts-web/05/parameters-server.php"
}
- Linha 3: Alterámos a porta do servidor web. Por conseguinte, será contactado o [RawTcpServer];
Executamos o cliente. Na janela [RawTcpServer], vemos as seguintes informações:

- em [6], o pedido POST;
- em [7]: o cabeçalho HTTP [Content-Length] especifica o número de bytes no documento que o cliente enviará ao servidor. O cabeçalho HTTP [Content-Type] especifica a natureza deste documento. O tipo [application/x-www-form-urlencoded] denota texto codificado por URL;
- em [8], a linha em branco que marca o fim dos cabeçalhos HTTP e o início do documento de 44 bytes. O que a captura de ecrã não mostra é o próprio documento. Trata-se da cadeia de parâmetros codificada por URL: [first_name=jean-paul&last_name=de+la+h%C3%BBche&age=45]. O leitor pode verificar que tem, de facto, 44 caracteres;
17.9.6. Um cliente POST misto
Numa solicitação POST, pode-se misturar parâmetros codificados na URL com aqueles codificados no documento enviado pelo cliente após os cabeçalhos HTTP. Aqui está um exemplo [parameters-mixte-postclient.php]:
<?php
// client POST of a web server
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
// customer configuration
const CONFIG_FILE_NAME = "config-parameters-client.json";
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
exit;
}
// create a HTTP customer
$httpClient = HttpClient::create();
try {
// prepare the parameters
list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
// make a request to the server
$response = $httpClient->request('POST', $config['url-post'],
[
// document parameters (body)
"body" => [
"prenom" => $prenom,
"nom" => $nom,
"age" => $age
],
// parameters of URL (query)
"query" => [
"prenom2" => $prenom,
"nom2" => $nom,
"age2" => $age
]]);
// answer status
$statusCode = $response->getStatusCode();
print "---Réponse avec statut : $statusCode\n";
// we retrieve the headers
print "---Entêtes de la réponse\n";
$headers = $response->getHeaders();
foreach ($headers as $type => $value) {
print "$type: " . $value[0] . "\n";
}
// server response is displayed
print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
// error is displayed
print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}
Comentários
- linha 32: um pedido POST;
- linhas 40–45: parâmetros codificados por URL na URL;
- linhas 35–39: parâmetros codificados por URL no corpo da solicitação (corpo, documento);
Após a execução, obtém-se a seguinte saída na consola:
- linha 10: podemos ver que o servidor conseguiu recuperar ambos os tipos de parâmetros;
17.9.7. Um cliente GET misto
Vamos tentar fazer o mesmo que antes com um pedido GET. O script [parameters-mixte-get-client.php] é o seguinte:
<?php
// client POST of a web server
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
// customer configuration
const CONFIG_FILE_NAME = "config-parameters-client.json";
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
exit;
}
// create a HTTP customer
$httpClient = HttpClient::create();
try {
// prepare the parameters
list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
// make a request to the server
$response = $httpClient->request('GET', $config['url-post'],
[
// document parameters (body)
"body" => [
"prenom" => $prenom,
"nom" => $nom,
"age" => $age
],
// parameters of URL (query)
"query" => [
"prenom2" => $prenom,
"nom2" => $nom,
"age2" => $age
]]);
// answer status
$statusCode = $response->getStatusCode();
print "---Réponse avec statut : $statusCode\n";
// we retrieve the headers
print "---Entêtes de la réponse\n";
$headers = $response->getHeaders();
foreach ($headers as $type => $value) {
print "$type: " . $value[0] . "\n";
}
// server response is displayed
print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
// error is displayed
print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}
Comentários
- linha 32: um pedido POST;
- linhas 40–45: parâmetros codificados por URL na URL;
- linhas 35–39: parâmetros codificados por URL no corpo da solicitação (corpo, documento);
Após a execução, obtém-se a seguinte saída na consola:
- Linha 10: Podemos ver que o servidor não recebeu quaisquer parâmetros codificados por URL no documento enviado pelo cliente. Quando analisamos os cabeçalhos HTTP enviados pelo cliente, verificamos que este enviou efetivamente um documento de 44 caracteres, mas o servidor não o processou;
Então, que método deve escolher para enviar informações ao servidor?
- O método [GET URL?param1=val1¶m2=val2&…] utiliza uma URL parametrizada que pode servir como um link. Esta é a sua principal vantagem: o utilizador pode adicionar esses links aos favoritos;
- Noutras aplicações, poderá não querer exibir os parâmetros enviados para o servidor numa URL. Por razões de segurança, por exemplo. Nesse caso, utilizará o método [POST] e incluirá os parâmetros codificados na URL num documento enviado para o servidor;
17.10. Gestão de sessões Web
Nos exemplos anteriores de cliente/servidor, o processo era o seguinte:
- o cliente abre uma ligação à porta 80 na máquina do servidor web;
- envia a sequência de texto: cabeçalhos HTTP, linha em branco, [documento];
- em resposta, o servidor envia uma sequência do mesmo tipo;
- o servidor encerra a ligação com o cliente;
- o cliente encerra a ligação ao servidor;
Se o mesmo cliente fizer uma nova solicitação ao servidor web pouco tempo depois, é estabelecida uma nova ligação entre o cliente e o servidor. O servidor não consegue determinar se o cliente que se está a ligar já visitou o site anteriormente ou se esta é uma solicitação pela primeira vez. Entre ligações, o servidor «esquece» o seu cliente. Por esta razão, diz-se que o protocolo HTTP é um protocolo sem estado. No entanto, é útil que o servidor se lembre dos seus clientes. Por exemplo, se uma aplicação for segura, o cliente enviará ao servidor um nome de utilizador e uma palavra-passe para se autenticar. Se o servidor «esquecer» o seu cliente entre conexões, o cliente teria de se autenticar a cada nova conexão, o que não é viável.
Para rastrear um cliente, o servidor procede da seguinte forma: quando um cliente faz uma solicitação inicial, o servidor inclui um identificador na sua resposta, que o cliente deve então enviar de volta a cada nova solicitação. Graças a este identificador, que é único para cada cliente, o servidor pode reconhecer um cliente. Pode então gerir uma entrada de memória para esse cliente na forma de uma entrada de memória associada de forma única ao identificador do cliente.
Tecnicamente, é assim que funciona:
- Na resposta a um novo cliente, o servidor inclui o cabeçalho HTTP Set-Cookie: Key=Identifier. Faz isso apenas na primeira solicitação;
- nas solicitações subsequentes, o cliente reenviará o seu identificador através do cabeçalho HTTP Cookie: Key=Identifier para que o servidor possa reconhecê-lo;
Poder-se-á perguntar como é que o servidor sabe que está a lidar com um novo cliente e não com um cliente recorrente. É a presença do cabeçalho HTTP Cookie nos cabeçalhos HTTP do cliente que lhe permite saber isso. No caso de um novo cliente, este cabeçalho está ausente.
Todas as ligações de um determinado cliente são designadas como uma sessão.
17.10.1. O ficheiro de configuração [php.ini]
Para que a gestão de sessões funcione corretamente com o PHP, deve verificar se está devidamente configurado. No Windows, o ficheiro de configuração é o php.ini. Dependendo do contexto de execução (consola, web), o ficheiro de configuração [php.ini] deve estar localizado em diretórios diferentes. Para os encontrar, utilize o seguinte script:
Linha 4: A função phpinfo fornece informações sobre o interpretador PHP que está a executar o script. Em particular, fornece o caminho para o ficheiro de configuração [php.ini] que está a ser utilizado.
Já utilizámos este script num ambiente de consola (consulte a secção «link»). Num ambiente web, obtemos o seguinte resultado:

- Em [1-2], o ficheiro [php.ini] que configura o interpretador de scripts web. Este ficheiro contém uma secção de sessão:
- linha 2: os dados da sessão do cliente são guardados num ficheiro;
- linha 3: o diretório onde os dados da sessão são guardados. Se este diretório não existir, não é reportado nenhum erro e a gestão da sessão não funciona;
- linhas 4–6: indicam que o ID da sessão é gerido pelos cabeçalhos HTTP Set-Cookie e Cookie;
- linha 7: o cabeçalho Set-Cookie terá o formato Set-Cookie: PHPSESSID=session_id;
- linha 8: uma sessão do cliente não é iniciada automaticamente. O script do servidor deve solicitá-la explicitamente utilizando a função session_start();
- linha 9: o cookie de sessão permanece válido até que o navegador do cliente seja fechado;
- linha 10: o caminho para o qual o cookie de sessão deve ser enviado. Se [session.cookie_path = /xxx], então sempre que o navegador solicitar um URL no formato [/xxx/yyy/zzz], deve enviar o cookie. Aqui, o caminho [/] indica que o cookie deve ser enviado para todos os URLs do site;
- linha 13: determinados objetos de sessão devem ser serializados para poderem ser armazenados num ficheiro. O PHP lida com esta serialização/desserialização utilizando as funções [serialize / unserialize];
- linha 16: período de tempo limite após o qual os objetos de sessão armazenados no ficheiro de sessão são considerados obsoletos;
- linha 19: duração da sessão. Após este período, é criada uma nova sessão e os objetos guardados na sessão anterior são perdidos;
17.10.2. Exemplo 1
17.10.2.1. O servidor

A gestão do ID de sessão é transparente para um serviço web. Este ID é gerido pelo servidor web. Um serviço web acede à sessão do cliente através da função session_start(). A partir desse momento, o serviço web pode ler/gravar dados na sessão do cliente através do dicionário $_SESSION. Se for utilizada a biblioteca [HttpFoundation], a sessão fica disponível através da expressão [Request→getSession].
O código seguinte [session-server.php] demonstra a gestão baseada em sessão de três contadores. A cada novo pedido, o script web incrementa estes contadores e armazena-os na sessão para que possam ser recuperados durante o pedido seguinte.
<?php
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
//
// we retrieve the request
$request = Request::createFromGlobals();
// session
$session = new Session();
$session->start();
// three counters are retrieved from the session
if ($session->has("N1")) {
// n1 counter increment
$session->set("N1", (int) $session->get("N1") + 1);
} else {
// counter N1 is not in session - create it
$session->set("N1", 0);
}
if ($session->has("N2")) {
// n2 counter increment
$session->set("N2", (int) $session->get("N2") + 1);
} else {
// counter N2 is not in session - create it
$session->set("N2", 10);
}
if ($session->has("N3")) {
// n3 counter increment
$session->set("N3", (int) $session->get("N3") + 1);
} else {
// counter N3 is not in session - create it
$session->set("N3", 100);
}
// we work out the answer
$response = new Response();
// the content of the answer is utf-8 text
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
// the answer will be the jSON of an array containing the three counters
$response->setContent(json_encode([
"N1" => $session->get("N1"),
"N2" => $session->get("N2"),
"N3" => $session->get("N3")]));
// reply sent
$response->send();
- linha 10: o objeto [$request] encapsula todas as informações sobre o pedido recebido pelo script web;
- linhas 12-13: é criada e ativada uma sessão. O objeto [Session] encapsula os dados da sessão correspondentes ao cookie de sessão enviado pelo cliente. Se o cliente não tiver enviado esse cookie, então não são armazenados dados em [Session]. O script web incluirá o cabeçalho HTTP [Set-Cookie: PHPSESSID=xxx] na sua primeira resposta. Em pedidos subsequentes, o cliente enviará o cabeçalho HTTP [Cookie: PHPSESSID=xxx] para indicar a sessão cujo conteúdo pretende utilizar. Uma sessão é a memória de um cliente;
- linha 15: verificamos se a sessão tem uma chave chamada [N1]. Este será o nome do nosso primeiro contador. Se não tiver (linha 20), definimos o seu valor como 0 e adicionamo-lo à sessão. Se tiver (linha 23), nós:
- recuperamo-lo da sessão;
- incrementamos o seu valor em 1;
- colocamo-lo de volta na sessão;
- linhas 22–35: fazemos o mesmo para os outros dois contadores, N2 e N3;
- linhas 36–40: preparamos uma resposta do tipo [application/json];
- linhas 42–45: a resposta será a cadeia JSON de um array contendo os três contadores;
- linha 48: enviamos a resposta ao cliente;
Na relação cliente/servidor, a gestão da sessão do cliente no servidor depende de ambas as partes, o cliente e o servidor:
- o servidor é responsável por enviar um identificador ao cliente durante a sua primeira solicitação
- o cliente é responsável por enviar este identificador de volta com cada nova solicitação. Se não o fizer, o servidor assumirá que se trata de um novo cliente e gerará um novo identificador para uma nova sessão.
Resultados
Utilizamos um navegador web como cliente. Por predefinição (na verdade, por configuração), o navegador reenvia efetivamente ao servidor os identificadores de sessão que o servidor lhe envia. À medida que as solicitações são feitas, o navegador receberá os três contadores enviados pelo servidor e verá os seus valores a aumentar.

- Em [2], a primeira solicitação ao serviço web;
- em [4], a quarta solicitação mostra que os contadores foram, de facto, incrementados. Os valores dos contadores são, de facto, armazenados ao longo das solicitações;
Vamos utilizar o modo de programador para visualizar os cabeçalhos HTTP trocados entre o servidor e o cliente. Fechamos o Firefox para encerrar a sessão atual com o servidor, reabrimos o navegador e ativamos o modo de programador (F12). Isto irá limpar a sessão atual do navegador, fazendo com que este inicie uma nova. Solicitamos o serviço [session-server.php]:

Em [5], vemos o ID da sessão enviado pelo servidor na sua resposta à primeira solicitação do cliente. Ele usa o cabeçalho HTTP Set-Cookie.
Vamos fazer uma nova solicitação atualizando (F5) a página no navegador da Web:

Aqui, vamos notar duas coisas:
- Em [11], o navegador da Web reenvia o ID da sessão com o cabeçalho HTTP Cookie.
- Em [12], o serviço web já não inclui este identificador na sua resposta. Cabe agora ao cliente enviá-lo em cada uma das suas solicitações.
17.10.2.2. O cliente
Vamos agora escrever um script do lado do cliente com base no script anterior do lado do servidor. Na sua gestão de sessões, deve comportar-se como o navegador web:
- na resposta do servidor à sua primeira solicitação, ele deve encontrar o ID de sessão que o servidor lhe envia. Ele sabe que o encontrará no cabeçalho HTTP Set-Cookie.
- Para cada pedido subsequente, deve enviar o identificador recebido de volta ao servidor. Fá-lo-á utilizando o cabeçalho HTTP Cookie.

O cliente [session-client] é configurado pelo seguinte ficheiro JSON [config-session-client.json]:
O código do cliente [session-client] é o seguinte:
<?php
// session management
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
// customer configuration
const CONFIG_FILE_NAME = "config-session-client.json";
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
exit;
}
// create a HTTP customer
$httpClient = HttpClient::create();
try {
// we'll make 10 requests
for ($i = 0; $i < 10; $i++) {
// make a request to the server
if (!isset($sessionCookie)) {
// no session
$response = $httpClient->request('GET', $config['url']);
} else {
// with session
$response = $httpClient->request('GET', $config['url'],
["headers" => ["Cookie" => $sessionCookie]]);
}
// answer status
$statusCode = $response->getStatusCode();
print "---Réponse avec statut : $statusCode\n";
// we retrieve the headers
print "---Entêtes de la réponse\n";
$headers = $response->getHeaders();
foreach ($headers as $type => $value) {
print "$type: " . $value[0] . "\n";
}
// retrieve the session cookie if it exists
if (isset($headers["set-cookie"])) {
// session cookie ?
foreach ($headers["set-cookie"] as $cookie) {
$match = [];
$match = preg_match("/^PHPSESSID=(.+?);/", $cookie, $champs);
if ($match) {
$sessionCookie = "PHPSESSID=" . $champs[1];
}
}
}
}
// the jSON server response is displayed
print "---Réponse du serveur : {$response->getContent()}\n";
} catch (TypeError | RuntimeException $ex) {
// error is displayed
print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}
Comentários
- linha 27: criação do cliente HTTP;
- linha 30: enviaremos a mesma solicitação ao servidor [session-server.php] 10 vezes;
- linha 32: a variável [$sessionCookie] será definida com o valor do cabeçalho HTTP [Set-Cookie] recebido pelo cliente;
- linhas 32–34: se esta variável não existir, significa que a sessão ainda não começou. Enviamos a solicitação [GET] sem o cabeçalho [Cookie];
- linhas 35–38: caso contrário, a sessão já começou e enviamos a solicitação [GET] com o cabeçalho [Cookie]. O valor deste cabeçalho será [$sessionCookie];
- linha 50: se o cabeçalho [Set-Cookie] estiver entre os cabeçalhos HTTP recebidos, então procuramos o cookie de sessão;
- linha 52: o servidor web pode enviar vários cabeçalhos [Set-Cookie]. O cookie de sessão é apenas um deles. No nosso exemplo, tem o formato específico [PHPSESSID=xxx;;]
- linhas 53–57: é utilizada uma expressão regular para encontrar o cookie de sessão;
- linha 62: assim que as 10 solicitações forem feitas, exibimos a última resposta JSON do servidor;
A execução do script do cliente faz com que o seguinte seja exibido na consola do NetBeans:
"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}
- Linha 8: Na sua primeira resposta, o servidor envia o ID da sessão. Nas respostas subsequentes, já não o envia;
- linha 41: os três contadores [N1, N2, N3] foram, de facto, incrementados 9 vezes. Durante o pedido n.º 1, foram repostos a zero;
O exemplo seguinte mostra que também é possível guardar os valores de uma matriz ou de um objeto na sessão.
17.10.3. Exemplo 2
17.10.3.1. O servidor

Vamos colocar um objeto [Person] na sessão. A definição desta classe é a seguinte:
<?php
namespace Modèles;
class Personne implements \JsonSerializable {
// attributes
private $nom;
private $prénom;
private $âge;
// convert associative array to object [Person]
public function setFromArray(array $assoc): Personne {
// initialize the current object with the associative array
foreach ($assoc as $attribute => $value) {
$this->$attribute = $value;
}
// result
return $this;
}
// getters and setters
public function getNom() {
return $this->nom;
}
public function getPrénom() {
return $this->prénom;
}
public function setNom($nom) {
$this->nom = $nom;
return $this;
}
public function setPrénom($prénom) {
$this->prénom = $prénom;
return $this;
}
public function getÂge() {
return $this->âge;
}
public function setÂge($âge) {
$this->âge = $âge;
return $this;
}
// toString
public function __toString(): string {
return "Personne [$this->prénom, $this->nom, $this->âge]";
}
// implements the JsonSerializable interface
public function jsonSerialize(): array {
// render an associative array with the object's attributes as keys
// this table can then be encoded as jSON
return get_object_vars($this);
}
// convert a jSON to a [Person] object
public static function jsonUnserialize(string $json): Personne {
// we create a person from the string jSON
return (new Personne())->setFromArray(json_decode($json, true));
}
}
O script do servidor será o seguinte:
<?php
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
require_once __DIR__ . "/Personne.php";
use \Modèles\Personne;
//
// retrieve the current query
$request = Request::createFromGlobals();
// session
$session = new Session();
$session->start();
// retrieve various data from the session
// table
if ($session->has("tableau")) {
// the array is in the session - all its values are incremented
$tableau = $session->get("tableau");
for ($i = 0; $i < count($tableau); $i++) {
$tableau[$i] += 1;
}
// put the table back in the session
$session->set("tableau", $tableau);
} else {
// the array is not in the session - we create it
$tableau = [0, 10, 100];
// we put it in the session
$session->set("tableau", $tableau);
}
// dictionary
if ($session->has("assoc")) {
// [assoc] is in the session - all its elements are incremented
$assoc = $session->get("assoc");
foreach ($assoc as $key => $value) {
$assoc[$key] = $value + 1;
}
// put $assoc in the session
$session->set("assoc", $assoc);
} else {
// [assoc] is not in the session - we create it
$assoc = ["un" => 0, "deux" => 10, "trois" => 100];
// put $assoc in the session
$session->set("assoc", $assoc);
}
// object Person
if ($session->has("personne")) {
// [person] is in the session - his age is incremented
$personne = $session->get("personne");
$personne->setÂge($personne->getÂge() + 1);
} else {
// [person] is not in the session - we create it
$personne = (new Personne())->setFromArray(
["prénom" => "Léonard", "nom" => "Hûche", "âge" => 0]);
// put $personne in the session
$session->set("personne", $personne);
}
// we work out the answer
$response = new Response();
// the content of the response is jSON utf-8
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
$response->setContent(json_encode([
"tableau" => $tableau,
"assoc" => $assoc,
"personne" => $personne], JSON_UNESCAPED_UNICODE));
// reply sent
$response->send();
Comentários
- linhas 16-17: recuperam a sessão atual e ativam-na;
- linhas 21-34: gerimos uma matriz [array] armazenada na sessão. A cada nova solicitação, os seus elementos são incrementados em 1;
- linhas 36-49: gerimos uma matriz associativa [assoc] armazenada na sessão. A cada nova solicitação, os seus elementos são incrementados em 1;
- linhas 51–61: gerimos um objeto de sessão [Person]. A cada nova solicitação, a idade desta pessoa é incrementada em 1;
- linhas 62–73: é enviada uma resposta JSON ao cliente: a cadeia JSON de um array associativo;
Vamos executar este script a partir do NetBeans. As duas primeiras solicitações produzem os seguintes resultados (prima F5 no navegador para a segunda):

- vemos que em [6-8], todos os contadores foram incrementados;
17.10.3.2. O cliente

O cliente é o mesmo do Exemplo 1 (secção de links). Apenas modificamos o seu ficheiro de configuração [config-session-client]:
{
"url": "http://localhost/php7/scripts-web/07/session-server.php"
}
A execução produz os seguintes resultados:
---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}}
- na linha [22], podemos ver que todos os contadores foram incrementados;
17.11. Autenticação
Vamos agora concentrar-nos nos serviços web destinados apenas a utilizadores específicos. O cliente deve, portanto, autenticar-se junto do serviço web antes de receber uma resposta.
17.11.1. O cliente

O código do cliente [auth-client.php] é o seguinte:
<?php
// session management
//
// error management
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
// customer configuration
const CONFIG_FILE_NAME = "config-auth-client.json";
// we retrieve the configuration
if (!file_exists(CONFIG_FILE_NAME)) {
print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
exit;
}
// create a HTTP customer
$httpClient = HttpClient::create([
'auth_basic' => ['admin', 'admin'],
// "verify_peer" => false,
// "verify_host" => false
]);
try {
// make a request to the server
$response = $httpClient->request('GET', $config['url']);
// answer status
$statusCode = $response->getStatusCode();
print "---Réponse avec statut : $statusCode\n";
// we retrieve the headers
print "---Entêtes de la réponse\n";
$headers = $response->getHeaders();
foreach ($headers as $type => $value) {
print "$type: " . $value[0] . "\n";
}
// the jSON server response is displayed
print "---Réponse du serveur : {$response->getContent()}\n";
} catch (TypeError | RuntimeException $ex) {
// error is displayed
print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}
Comentários
- linhas 27–31: passámos um parâmetro ao método estático [HttpClient::create], um hash;
- linha 28: a chave [auth_basic] tem como valor uma matriz de dois elementos [user, password]. O cliente utilizará estes elementos para se autenticar no serviço web. A chave [auth_basic] refere-se a um tipo de autenticação denominado [Basic Authorization], cujo nome deriva do cabeçalho HTTP que o cliente irá enviar. Existem outros tipos de autenticação;
- À parte este código, o cliente é idêntico aos anteriores;
Para visualizar os cabeçalhos HTTP enviados pelo cliente, iremos ligá-lo ao servidor TCP genérico [RawTcpServer], tal como já fizemos várias vezes anteriormente:

Iniciamos o cliente com a seguinte configuração [config-auth-client.json]:
{
"url": "http://localhost:100/php7/scripts-web/08/auth-server.php"
}
O [RawTcpServer] recebe então as seguintes linhas:

- Em [5], vemos o cabeçalho [Authorization: Basic XXX] enviado pelo cliente. A cadeia XXX é a cadeia [usuário:senha] codificada em Base64;
Para verificar isto, pode descodificar a cadeia recebida no site [https://www.base64decode.org/]:

17.11.2. O servidor

O servidor [auth-server.php] é o seguinte:
<?php
// dependencies
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
// authorized users
$users = ["admin" => "admin"];
//
// retrieve the current query
$request = Request::createFromGlobals();
// authentication
$requestUser = $request->headers->get('php-auth-user');
$requestPassword = $request->headers->get('php-auth-pw');
// does the user exist?
$trouvé = array_key_exists($requestUser, $users) && $users[$requestUser] === $requestPassword;
// answer preparation
$response = new Response();
// set the response status code
if (!$trouvé) {
// not found - code 401
$response->setStatusCode(Response::HTTP_UNAUTHORIZED);
$response->headers->add(["WWW-Authenticate"=> "Basic realm=".utf8_decode("\"PHP7 par l'exemple\"")]);
} else {
// found - code 200
$response->setStatusCode(Response::HTTP_OK);
}
// response has no content, only HTTP headers
$response->send();
Comentários
- linha 9: utilizadores autorizados, neste caso um único utilizador com o nome de utilizador [admin] e a palavra-passe [admin];
- linha 14: o ID do utilizador é recuperado do cabeçalho [PHP-AUTH-USER]. Este não é um cabeçalho enviado pelo cliente, mas sim um cabeçalho construído pelo PHP do servidor;
- linha 15: a palavra-passe do utilizador é recuperada do cabeçalho [PHP-AUTH-PW], um cabeçalho criado pelo PHP;
- linha 17: procuramos o utilizador que está a tentar iniciar sessão na lista de utilizadores autorizados;
- linhas 23–24: se o utilizador não for reconhecido, o seguinte é enviado ao cliente
- linha 23: o código de estado [401 Unauthorized];
- linha 24: um cabeçalho [WWW-Authenticate: Basic realm=”something”]. A maioria dos navegadores reconhece este cabeçalho e exibirá uma janela de autenticação solicitando que o utilizador inicie sessão. Os cabeçalhos HTTP devem ser codificados em ISO 8859-1. O texto do NetBeans, no entanto, é codificado em UTF-8. A função [utf8_decode] trata da conversão de UTF-8 para ISO 8859-1. Aqui, não foi necessário porque os caracteres na string [PHP7 neste exemplo] são os mesmos tanto em UTF-8 como em ISO 8859-1. A função é incluída apenas como um lembrete da codificação utilizada pelos cabeçalhos HTTP;
- linha 25: se o utilizador tiver sido reconhecido, enviamos ao cliente o código [200 OK];
Vamos solicitar a URL [auth-server.php] utilizando um navegador:

Vemos que o navegador exibe uma janela de autenticação. Em [2], vemos o valor do cabeçalho [WWW-Authenticate] enviado pelo servidor. Se analisarmos os cabeçalhos HTTP recebidos pelo navegador, encontramos o seguinte:
- Linha 1: o código de estado da resposta [401 Não autorizado];
- linha 6: o cabeçalho HTTP [WWW-Authenticate];
- linha 7: o corpo da resposta está vazio;
Se, em [3-4], digitar [admin] duas vezes, a resposta do servidor é a seguinte:
- linha 1: o código de resposta 200 OK;
- linha 6: o corpo da resposta está vazio;
Se, em [3-4], forem introduzidas credenciais incorretas, o navegador [Firefox] utilizado para o teste exibe continuamente a janela de autenticação até que sejam introduzidas as credenciais corretas. Sempre que ocorre uma ida e volta ao servidor, é recebida a mesma resposta, o que aciona a janela de autenticação do navegador.
Vamos executar o cliente [auth-client.php] com um utilizador não autorizado. A resposta do servidor é a seguinte:
---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".
- Em [1], o cliente recebeu efetivamente um código 401;
- em [3], foi lançada uma exceção no cliente. Foi o [HttpClient] do Symfony que a lançou: ele lança uma exceção quando o código de estado da resposta HTTP indica um erro do lado do servidor e o cliente tenta ler os cabeçalhos ou o conteúdo da resposta do servidor. A mensagem na linha 3 mostra que o servidor respondeu com [HTTP/1.0 401 Unauthorized] para indicar que o utilizador não foi reconhecido;
Agora vamos executar o cliente [auth-client.php] com o utilizador autorizado [‘admin’,’admin’]. A resposta do servidor é então a seguinte:
---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 :
- linha 1: o servidor respondeu [HTTP/1.2 200 OK];
- linha 7: a resposta não tem conteúdo (0 bytes);
17.11.3. Proteger a ligação cliente/servidor
Vimos que, para se autenticar junto do servidor, o cliente envia o cabeçalho:
Se esta linha for interceptada por spyware, este pode facilmente recuperar as credenciais [nome de utilizador, palavra-passe] codificadas em Base64 dentro da cadeia de caracteres [YWRtaW46YWRtaW4=]. Por este motivo, a autenticação deve ocorrer através de uma ligação segura entre o cliente e o servidor. Os URLs seguros utilizam o protocolo [HTTPS] em vez do protocolo HTTP. O protocolo [HTTPS] é o protocolo HTTP dentro de uma ligação cliente/servidor segura. Os URLs seguros têm o formato [https://chemin_document].
Nem todos os servidores web aceitam URLs neste formato. Estas devem ser modificadas para serem seguras. O servidor Apache do Laragon é um servidor seguro, mas o protocolo HTTPS não está ativado por predefinição. Deve ativá-lo no menu do Laragon:

- em [4], ative a encriptação SSL para o servidor Apache;
Depois de fazer isso, o servidor Apache é reiniciado automaticamente:

- em [1], aparece um cadeado verde: isto indica que o protocolo HTTPS foi ativado;
- em [2], aparece uma nova porta de serviço, a porta 443 neste caso. Esta é a porta de serviço para o protocolo HTTPS seguro;
Agora que temos um servidor seguro, vamos modificar o ficheiro de configuração do cliente [config-auth-client.json] da seguinte forma:
{
"url": "https://localhost:443/php7/scripts-web/08/auth-server.php"
}
Em [2], o protocolo mudou para [https] e a porta para [443].
Agora vamos executar o cliente [auth-client.php] com o utilizador autorizado [admin, admin]. A saída da consola é a seguinte:
O [HttpClient] do Symfony lançou uma exceção porque o servidor lhe enviou um certificado de confiança que o [HttpClient] não aceitou. A comunicação SSL depende de certificados de confiança emitidos por autoridades oficiais. Quando ativámos o protocolo HTTPS no servidor Apache do Laragon, foi gerado um certificado autoassinado para o servidor Apache. Um certificado autoassinado é um certificado que não foi validado por uma autoridade oficial. O cliente [HttpClient] do Symfony rejeitou este certificado autoassinado.
É possível instruir o [HttpClient] para não verificar a validade do certificado enviado pelo servidor. Isto é feito utilizando opções no método [HttpClient::create]:
// on crée un client HTTP
$httpClient = HttpClient::create([
'auth_basic' => ['admin', 'admin'],
"verify_peer" => false
]);
A linha 4 especifica que o certificado do servidor não deve ser verificado. Já tínhamos encontrado este problema no script [http-02.php] na secção com o link. Esse script utilizava a biblioteca [libcurl] para se ligar a sites HTTP e HTTPS. Tínhamos utilizado a seguinte configuração para essa biblioteca:
// Initialisation d'a cURL session
$curl = curl_init($url);
if ($curl === FALSE) {
// il y a eu une erreur
return "Erreur lors de l'initialisation de la session cURL pour le site [$site]";
}
// options de curl
$options = [
// mode verbose
CURLOPT_VERBOSE => true,
// nouvelle connexion - pas de cache
CURLOPT_FRESH_CONNECT => true,
// timeout de la requête (en secondes)
CURLOPT_TIMEOUT => $timeout,
CURLOPT_CONNECTTIMEOUT => $timeout,
// ne pas vérifier la validité des certificats SSL
CURLOPT_SSL_VERIFYPEER => false,
// suivre les redirections
CURLOPT_FOLLOWLOCATION => true,
// récupération du document demandé sous la forme d'a character string
CURLOPT_RETURNTRANSFER => true
];
// paramétrage de curl
curl_setopt_array($curl, $options);
Linha 17: A constante [CURLOPT_SSL_VERIFYPEER] controla se o certificado enviado pelo servidor deve ou não ser verificado. O cliente [HttpClient] é, na verdade, um cliente [curl] quando a extensão [curl] está ativada na configuração do PHP, como é o caso aqui. A classe instanciada por [HttpClient::create] é, então, a classe [CurlHttpClient]. As constantes [curl] estão disponíveis nesta classe, mas com nomes diferentes:
Destacámos a amarelo as constantes utilizadas pelo [CurlHttpClient].
Se agora executarmos o cliente [auth-client] com o utilizador [admin, admin], obtemos o seguinte resultado:
---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 :
O utilizador foi autenticado com sucesso. Se executarmos o cliente [auth-client] com um utilizador diferente de [admin, admin], obtemos o seguinte resultado:
Agora sabemos como nos autenticar num servidor seguro.