WS: Conceptes¶
Web Services¶
Tal com hem dit a la introducció un WS creat amb el llenguatge PHP estarà basat en API RESTFUL i que permetrà donar serveis de backend a l'aplicació frontend desenvolupada. D'aquesta manera el backend dona servei de Model de Dades principalment.
Com es pot observar a la imatge, hi ha tres possibilitats d'ús:
-
Des del frontend de la mateixa aplicació web.
-
Des del frontend d'una altra aplicació web.
-
Des de un altre backend.
Elements clau d'una API REST:
-
API: S’implementa usant el concepte d’API, que significa breument una interfície entre l’aplicació i el servei. La podem implementar amb qualsevol llenguatge vàlid de servidor, per exemple PHP, NodeJS, ...
-
HTTP: S’usarà amb el protocol HTTP per fer la crida a la funció basada en una URL on podrem indicar l’acció o recurs a realitzar.
-
Recurs: És la funció o acció a donar servei a dins de la API.
-
BdD: Base de Dades. Podem usar qualsevol BdD que tinguem accés des de el servidor backend, independentment del frontend.
-
JSON: Les dades que s’envien o es recullen seran en format JSON, s’envien conjuntament amb les capçaleres de dades o com a resposta a la solicitud.
-
Accions: Tindrem una sèrie d’accions, anomenades Endpoints, juntament amb el protocol HTTP, com
GET
,PUT
,POST
,DELETE
, que usarem per donar resposta a les necessitats de l’aplicació i de l’API. -
Públic / Privat: Com que les APIs les tindrem accessibles desde qualsevol dispositiu capaç de treballar amb el protocol HTTP les podem classificar de públiques o privades i que siguin gratuïtes o de pagament. Es pot fer servir un token a cada crida per identificar la mateixa nevegació del client. Es pot fer tant per cookie, com per URL amb un paràmetre i dins la ruta.
-
API-KEY / Token: És el que farem servir per autenticar o permetre l'ús de l'API per part d'un frontend o altra backend. Tindrà una relació directe entre fer oberta o tancada una API.
-
Estat de retorn: Una de les característiques importants de l’execució d’una API REST (Transferència d'un Estat Representacional) és que retorna el seu estat i podem saber si s’ha executat correctament, OK 200, o no.
-
Rutes: La lògica del codi serà similar a la de les rutes del MVC, on una funció principal controlarà tota la interfície de la API. Amb un arxiu .htaccess ho podrem redirigir
-
Documentació: Un aspecte molt important és la documentació associada a la API on definirem tota la lògica de la funció i la interfície amb les dades d’entrada i sortida.
Podeu veure-ho en aquest vídeo:
.
Primers passos creació API-REST¶
A dins del nostre servidor Apache, a la carpeta htdocs, crearem una carpeta /API.
Hi haurà dos arxius:
- .htaccess
- server.php
L'arxiu .htaccess
és el que ens premetrar controlar l'execució de la nostra API a mode controlador. Així forcarem a que sempre s'executi l'arxiu server.php
com a entraad a la lògica de la nostra API-REST.
RewriteEngine On
RewriteBase /API
options +FollowSymLinks
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* server.php/$0 [L]
I que totes les demandes HTTP sobre aquesta carpeta vagin cap a l’arxiu server.php
server.php serà l’arxiu pròpiament de la lògica de la API.
Tot seguit un exemple
<?php
class Server {
public function serve() {
$uri = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];
// retornem accés no permès
header('HTTP/1.1 403 Forbidden');
}
}
$server = new Server;
$server->serve();
?>
Tenim una classe Server per anar recollint totes les funcionalitats
recollirem la URL i el verb GET, POST, PUT, …
En aquest petit exemple el que fem és retornar un codi d’estat 403. Accés prohibit
La instrucció per tornar un estat ho fem amb header()
.
Amb el valor de la ruta que obtenim amb $_SERVER['REQUEST_URI'];
podem començar a aplicar lògica a la nostra API.
public function serve()
{
$uri = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];
//retornem un estat segons hem passat OK, PROHIBIT...
if ($uri == "/API/OK")
header('HTTP/1.1 200 Ok');
if ($uri == "/API/PROHIBIT")
header('HTTP/1.1 403 Forbidden');
}
IMPORTANT !
Si tenim la API a una carpeta /API a dins del servidor Apache de la carpeta /htdocs, $_SERVER['REQUEST_URI']; en retornarà tota la ruta complerta /API/OK, en la crida a la API.
Per tant, cal tenir-ho en compte a l'hora de programar la lògica. Més endavant ho treballarem millor.
Com recuperar la funció en la ruta¶
A dins de la funció Serve() de la classe, tenim:
$paths = explode('/', $this->paths($uri));
array_shift($paths);
$resource = array_shift($paths);
private function paths($url)
{
$uri = parse_url($url);
return $uri['path'];
}
Això ens permetrà tenir el valor de la 1a part de la ruta.
IMPORTANT !
Caldrà analitzar el format de la ruta i fer tant array_shift com faci falta per anar extraient la part que no ens cal.
Per tant podrem executar la funció associada
if ($resource == “llistaArticles” )
{
// codi associat a listar articles
}
else
{
header('HTTP/1.1 404 Not Found');
}
Això podria correspondre a una crida similar a:
http://www.lanostraapi.com/api-v2/llistaArticles
En aquest cas de l’exemple, en cas que l’acció indicada a la ruta no sigui la de “LlistaArticles”, l’API retornarà un codi d’estat de pàgina no trobada.
Com retornar l'estat d'execució d'una funció de la API¶
Bàsicament per finalitzar l’execució de la lògica d’una crida a una funció de la API el que fem és
header('HTTP/1.1 401 Unauthorized');
Amb la possibilitat de triar un dels estats següents
Aquesta informació és independent de les dades que retornarem per JSON
Per tant, podem usar aquesta lògica per exemple:
- execució correcte de la API: 200 OK
- accés no autoritzat: 401 Unauthorized
- pàgina no trobada: 404 Not Found
- mètode no permés: 405 Method Not Allowed
Com obtenim i retornem dades JSON¶
Per obtenir dades JSON enviades a un endpoint d'una API, per exemple per POST, farem:
$data = json_decode(file_get_contents('php://input'));
A $data tindrem les dades que hem passat amb JSON ja decodificades.
Ara podrem accedir a elles, per exemple:
$data->name;
Per retornar dades JSON ens cal indicar dues coses:
-
el format del que retornem
'header('Content-type: application/json');'
-
les dades pròpiament amb format JSON
'echo json_encode($resposta);'
Per exemple en la funció de retornar tots els contactes seria:
header('Content-type: application/json');
$resposta = null;
$SQL = 'SELECT * FROM contactes';
$consulta = (BdD::$connection)->prepare($SQL);
$qFiles = $consulta->execute();
if ($qFiles > 0)
{
$consulta->setFetchMode(PDO::FETCH_ASSOC);
$result = $consulta->fetchAll();
foreach($result as $fila)
{
$resposta[] = $fila;
}
}
echo json_encode($resposta);
Com podem assegurar una API privada¶
Tot i no ser l’objectiu principal, assegurar l’ús de l’API que estem creant pot ser una bona pràctica de seguretat a desenvolupar.
Podem usar diverses solucions a implementar. algunes d’elles implicaria l’ús d’un registre previ per part del client a usar-la per a poder tenir registrat com a ús permès d’accés a la API.
Els conceptes que podriem usar serien:
-
API-KEY: Clau que ens identifica com a usuari de la API.
-
Token: Valor que ens pot identificar en la utilització de la API.
Cal notar que les API-RESTFul s'han de desenvolupar sense haver de guardar cap informació d'estat al servidor (Variables de $_SESSION) , per tant tota la informació s'enviarà a l'aplicació (navegador) client. El Token pot contenir tota la informació d'estat necessària.
Implementacions possibles:
Com que la informació es desaria a la banda del client. Seria interessant guardar-la de forma segura encriptada si s'escau.
Per iniciar el desenvolupament d'aquest concepte podem implementar una possible solució que seria enviar desde el servidor una cookie amb una ‘Clau’ que s’usaria per identificar-se com a client vàlid. Evidentment aquesta part hauria de ser controlada pel servidor.
A dins de la API es podria crear una funció:
//funció que comprova si tenim la Cookie amb la KEY correcte
private function ValidarUs()
{
if (isset($_COOKIE["API-Key"]))
$codi = $_COOKIE["API-Key"]; // Aquí només mirem si s'ha passat una API-KEY.
else
$codi = "";
return ($codi==$KEY); // $KEY = "Ax4BG96wP"
}
On si hem rebut una cookie “API-Key” la compararem amb el valor que tenim a dins de la API, que podria estar associada a un valor hipotètic de la BdD usuaris-client registrat.
A la funció serve() caldria executar abans de res aquesta funció:
if ($this->ValidarUs())
{
// codi de la API
}
else
{
// No trobem una Key desada a una Cookie vàlida
header('HTTP/1.1 401 Unauthorized');
}
De manera alternativa es pot enviar aquesta informació per la URL del HTTP GET al moment de fer la crida a l’estil de:
http://domini.api/{{API-KEY}}/acció
El codi a la banda del client, és el responsable de fer la inserció de la API-KEY a la URL de la crida a la API.
Caldria adaptar el codi d’obtenció de la ruta per obtenir la API-KEY i modificar la funció
$paths = explode('/', $this->paths($uri));
array_shift($paths);
$api-key = array_shift($paths);
$resource = array_shift($paths);
if ($this->ValidarUs())
{
// resta de codi de la API
}
else
{
// No trobem una Key desada a una Cookie vàlida
header('HTTP/1.1 401 Unauthorized');
}
Documentar la nostra API¶
Respecte a la documentació tindrem 2 enfocaments:
- Documentació tècnica de desenvolupament
Dissenyarem els elements que formen part de la implementació:
- Tipus de seguretat d'accés
- Accés a la BdD
- Gestió de les rutes
- Llista de endpoints
- verb, estat de retorn
- dades json d'entrada i sortida
- lògica a aplicar
Internament al codi PHP documentarem cada una de les funcions per deixar-ho preparat per a usar PHP Documentor
També serà important el conjunt de proves unitàries que es generarà.
Ho podem fer usant el Postman i documenta-ho en un PDF.
- Documentació d'usabilitat de la API i els seus endpoints
Tal com s’ha comentat anteriorment la crida a una API implica:
- ruta de la crida: URL
- acció o verb associat: GET, POST, DELETE, …
- dades passades JSON
- dades retornades JSON
- lògica aplicada a dins, accions sobre la BdD, si s’escau
- pública o privada
Per exemple podriem documentar una API similar a aquesta de Google Maps
I aixì anar fent per a totes les funcions de la API
Alguns exemples de rutes podrien ser:
- GET /articles -> Obtenir tots els articles
- GET /article/[id] -> Obtenir l’article amb id
- POST /article -> Desar un article passat per JSON (per exemple) a BdD
- PUT /article/[id] -> Actualitzar un article a la BdD
- DELETE /article/[id] -> Eliminar l’article amb id a la BdD
La documentació de la interfície de la nostra API és important, ja que això permetrà el correcte ús dels endpoints per part d'un usuari.
Aquí utilitats com el Mkdocs o similar ens poden ajudar a organitzar-ho.