Salta el contingut

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.

API-REST

Com es pot observar a la imatge, hi ha tres possibilitats d'ús:

  1. Des del frontend de la mateixa aplicació web.

  2. Des del frontend d'una altra aplicació web.

  3. 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: Vídeo API-REST.

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:

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

Doc API

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.

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