Salta el contingut

MP08 - UF2: API Rest i el Mòdul Headers

Què és CORS?

CORS (Cross-Origin Resource Sharing o Compartició de Recursos entre Orígens Creuats) és un mecanisme basat en capçaleres HTTP, que permet, a un servidor, indicar qualsevol domini, esquema o port amb un origen diferent al seu, des de el que un navegador hauria de permetre la càrrega de recursos.

CORS també es basa en un mecanisme pel qual els navegadors realitzen una sol·licitud de "verificació prèvia" al servidor que allotja el recurs d'origen creuat, amb la finalitat de comprovar si el servidor permetrà o no la sol·licitud real. En aquesta comprovació prèvia, el navegador envia capçaleres que indiquen el mètode HTTP i els encapçalaments que s'utilizaran en la sol·licitud real.

Un exemple de sol·licitud d'origen creuat: el codi JavaScript del front-end accedit des de https://domain-a.com utilitza XMLHttpRequest per realitzar una sol·licitud a https://domain-b.com/data.json.

Per raons de seguretat, els navegadors restringeixen les peticions HTTP d'origen creuat iniciades des dels scripts. Per exemple, XMLHttpRequest i l'API Fetch segueixen la política Same-origin. Això significa que una aplicació web que utilitzi aquestes API només pot sol·licitar recursos del mateix origen des del que es carrega l'aplicació, a menys que la resposta d'altres orígens inclogui les capçaleres CORS adequades.

Funcionament de CORS

Per saber més de CORS podem accedir aquí.

L'ajuda global de CORS la trobem aquí.

L'ajuda pel problema de CORS la trobareu aquí.

És possible que el nostre servidor php no estigui preparat per a l'execució d'algunes comandes específiques per a treballar en mode APIRest. Per això possiblement ens falta instal·lar algun mòdul i saber com funciona.

Mòdul headers

En el cas en què estiguem desenvolupant una API, és possible que només donem servei a les peticions que provenen del mateix domini on tenim la API que dona servei. Si volem permetre l'accés a la API des d'altres ubicacions, cal habilitar el mòdul headers i configurar el servidor apache, al mateix fitxer .htaccess o al fitxer pricipal, per tal de permetre-ho.

Provant un servidor de backend que proveeix una API a la web http://ipdelservidor/api/consulta.php que ens proveeix de les dades d'alguns usuaris, no podrà ser accedit des del servidor de VUE que tenim a http://ipdelservidor:8000/, ja que els seus dominis o millor dit orígens, no són els mateixos. En aquest cas només canvia el port i per tant, l'orígen ja no és el mateix.

Podem provar el següent codi en un servidor que serveixi el mateix tant pel port 80 com pel port 8000.

Visualitza el Codi client html
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <style>
    body {
      font-family: Arial;
    }

    input {
      font-size: 1.5em;
    }

    #resultats {
      width: 400px;
      height: 600px;
      font-size: 1.2em;
      overflow: auto;
      background-color: lightgray;
      margin-top: 1em;
    }

    option {
      border-bottom: 1px solid black;
      padding: 0.5em;
    }
  </style>
</head>

<body>
  <h1>CERCADOR D'USUARIS</h1>
  <input type="radio" id="port80" name="port_acces" value="80" checked> Port 80
  <input type="radio" id="port8000" name="port_acces" value="8000"> Port 8000
  <br />
  <input id="consulta">
  <span id="comptador"></span>
  <div>
    <select id="resultats" multiple></select>
  </div>
  <script>
    const $ = e => document.querySelector(e)

    function peticioDades(dades) {
      $("#resultats").innerHTML = "";

      // Creem un objecte de petició d'intercanvi de dades
      var xmlhttp = new XMLHttpRequest();

      // Associem una funció per comprovar quan la petició ha canviat d'estat
      xmlhttp.onreadystatechange = function () {

        // Si la petició s'ha completat correctament rebrem un codi 200 i serem a l'estat 4
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {

          // Tenim les dades rebudes del servidor dins de xmlhttp.responseText
          // Com que sabem que estan en format JSON convertim la resposta en
          // un objecte que guardem a la variable "resposta"
          resposta = JSON.parse(xmlhttp.responseText);

          // Cridem la funció que omplirà la llista amb les dades retornades
          omplirLlistaDades(resposta);
        }
      }

      // Indiquem el mètode (GET) i la URL del servidor que ens retornarà les dades
      let portDacces = "";
      if (document.getElementById("port8000").checked) portDacces = ":8000";
      xmlhttp.open("GET", location.protocol + "//" + location.hostname + portDacces + "/api/consulta.php?filtre=" + dades);

      // Llancem la petició assíncrona al servidor
      xmlhttp.send();
    }

    function omplirLlistaDades(dades) {
      // Actualitzem el comptador de resultats obtinguts
      $("#comptador").innerHTML = dades.length + " resultat" + (dades.length == 1 ? "" : "s");

      // Esborrem totes les línies del resultat anterior
      $("#resultats").textContent = "";

      // Creem els nous elements li
      for (dada of dades) {
        var nou = document.createElement("option");
        nou.innerHTML = dada.LAST_NAME + ", " + dada.FIRST_NAME;
        $("#resultats").appendChild(nou);
      }
    }

    $("#consulta").oninput = function () {
      peticioDades(this.value);
    }
    let port = location.port;
    if (port == "8000") document.getElementById("port8000").checked = true;
  </script>
</body>

</html>
Visualitza el Codi servidor php
<?php
$host = 'localhost';
$user = 'usuari';
$password = 'usuari';
$db = 'empresa';
// Connexió a la base de dades
$mysqli = new mysqli($host, $user, $password, $db);

// Obtenim el filtre de dades que ens pot arribar per GET
$filtre = isset( $_GET["filtre"] ) ? "WHERE (first_name LIKE '%".$_GET["filtre"]."%') OR (last_name LIKE '%".$_GET["filtre"]."%') " : "";

// Preparem la consulta
$consulta = $mysqli->query( "SELECT * FROM employees " . $filtre . "ORDER BY last_name" );

// Array on deixarem les dades que retorni la consulta
$resultat = array();

// Recorrem els resultats de la consulta
while ( $registre = mysqli_fetch_assoc( $consulta ) )
{
    // Afegim cada resultat a l'array que retornarem
    array_push($resultat, $registre);
}

// Retornem l'array de resultats en format JSON
echo json_encode($resultat);
?>

Fem la prova de preparar un servidor amb dos hostings virtuals que accediran el mateix directori on hi posarem els següents fitxers:

  • index.html: com a client

  • api/consulta.php: com a backend

Els dos hostings virtuals només els diferenciarem pel port als quals accedim: un pel port 80 i l'altre pel port 8000.

Provarem d'accedir al client, que farà una crida XMLHttpRequest a la API. Podem seleccionar utilitzar el port 80 o el port 8000. Depenent del lloc on hagim obtingut l'index.html, ens deixarà o no ens deixarà.

Podeu crear la taula necessària a la base de dades a partir de la següent script

CREATE TABLE EMPLOYEES(
    EMPLOYEE_ID INT(6),
    FIRST_NAME VARCHAR(20),
    LAST_NAME VARCHAR(25) NOT NULL,
    EMAIL VARCHAR(25) NOT NULL,
    PHONE_NUMBER VARCHAR(20),
    HIRE_DATE DATE NOT NULL,
    JOB_ID VARCHAR(10) NOT NULL,
    SALARY DECIMAL(8,2),
    COMMISSION_PCT DECIMAL(2,2),
    MANAGER_ID INT(6),
    DEPARTMENT_ID INT(4),
    PRIMARY KEY(EMPLOYEE_ID),
    CONSTRAINT EMP_EMAIL_UK UNIQUE(EMAIL));

INSERT INTO EMPLOYEES VALUES (100, 'Steven', 'King', 'SKING', '515.123.4567','1987-06-17','AD_PRES', 24000, NULL, NULL, 90);
INSERT INTO EMPLOYEES VALUES (101, 'Neena', 'Kochhar', 'NKOCHHAR', '515.123.4568', '1989-09-21','AD_VP', 17000, NULL, 100, 90); 
INSERT INTO EMPLOYEES VALUES (102, 'Lex', 'De Haan', 'LDEHAAN', '515.123.4569', '1993-01-13','AD_VP', 17000, NULL, 100, 90); 
INSERT INTO EMPLOYEES VALUES (103, 'Alexander', 'Hunold', 'AHUNOLD', '590.423.4567', '1990-01-03','IT_PROG', 9000, NULL, 102, 60); 
INSERT INTO EMPLOYEES VALUES (104, 'Bruce', 'Ernst', 'BERNST', '590.423.4568', '1991-05-21','IT_PROG', 6000, NULL, 103, 60); 
INSERT INTO EMPLOYEES VALUES (107, 'Diana', 'Lorentz', 'DLORENTZ', '590.423.5567', '1999-02-07','IT_PROG', 4200, NULL, 103, 60); 
INSERT INTO EMPLOYEES VALUES (124, 'Kevin', 'Mourgos', 'KMOURGOS', '650.123.5234', '1999-11-19','ST_MAN', 5800, NULL, 100, 50); 
INSERT INTO EMPLOYEES VALUES (141, 'Trenna', 'Rajs', 'TRAJS', '650.121.8009', '1995-10-17','ST_CLERK', 3500, NULL, 124, 50); 
INSERT INTO EMPLOYEES VALUES (142, 'Curtis', 'Davies', 'CDAVIES', '650.121.2994', '1997-01-29','ST_CLERK', 3100, NULL, 124, 50); 
INSERT INTO EMPLOYEES VALUES (143, 'Randall', 'Matos', 'RMATOS', '650.121.2874', '1998-03-15','ST_CLERK', 2600, NULL, 124, 50); 
INSERT INTO EMPLOYEES VALUES (144, 'Peter', 'Vargas', 'PVARGAS', '650.121.2004', '1998-07-09','ST_CLERK', 2500, NULL, 124, 50); 
INSERT INTO EMPLOYEES VALUES (149, 'Eleni', 'Zlotkey', 'EZLOTKEY', '011.44.1344.429018', '2000-01-29','SA_MAN', 10500, .2, 100, 80); 
INSERT INTO EMPLOYEES VALUES (174, 'Ellen', 'Abel', 'EABEL', '011.44.1644.429267', '1996-05-11','SA_REP', 11000, .3, 149, 80); 
INSERT INTO EMPLOYEES VALUES (176, 'Jonathan', 'Taylor', 'JTAYLOR', '011.44.1644.429265', '1998-03-24','SA_REP', 8600, .2, 149, 80); 
INSERT INTO EMPLOYEES VALUES (178, 'Kimberely', 'Grant', 'KGRANT', '011.44.1644.429263', '1999-05-24','SA_REP', 7000, .15, 149, NULL); 
INSERT INTO EMPLOYEES VALUES (200, 'Jennifer', 'Whalen', 'JWHALEN', '515.123.4444', '1987-09-17','AD_ASST', 4400, NULL, 101, 10); 
INSERT INTO EMPLOYEES VALUES (201, 'Michael', 'Hartstein', 'MHARTSTE', '515.123.5555', '1996-02-17','MK_MAN', 13000, NULL, 100, 20); 
INSERT INTO EMPLOYEES VALUES (202, 'Pat', 'Fay', 'PFAY', '603.123.6666', '1997-08-17','MK_REP', 6000, NULL, 201, 20); 
INSERT INTO EMPLOYEES VALUES (205, 'Shelley', 'Higgins', 'SHIGGINS', '515.123.8080', '1994-06-07','AC_MGR', 12000, NULL, 101, 110); 
INSERT INTO EMPLOYEES VALUES (206, 'William', 'Gietz', 'WGIETZ', '515.123.8181', '1994-06-07','AC_ACCOUNT', 8300, NULL, 205, 110);  

Per a permetre l'accés caldrà, doncs:

Habilitar el mòdul headers

L'ajuda del mòdul headers la trobareu aquí.

Per això, caldrà anar al servidor web i habilitar el mòdul amb les següents comandes:

a2enmod headers
service apache2 restart

Modificar la configuració del directori del backend

L'ajuda del mòdul headers d'Apache la trobareu aquí.

Podem configurar les opcions del mòdul headers tant al fitxer de configuració de l'Apache com a un fitxer .htaccess sempre que li haguem donar permís de configurar-se (AllowOverride FileInfo)

Una vegada habilitat, cal indicar des de quins orígens acceptem les peticions. Per això caldrà configurar al directori on tenim els fitxers del backend, o al fitxer .htaccess del directori, la següent directiva:

Header [condition] add|append|echo|edit|edit*|merge|set|setifempty|unset|note header [[expr=]value [replacement] [early|env=[!]varname|expr=expression]]
En el nostre cas el header serà Access-Control-Allow-Origin i si volem permetre accés des de tot arreu, la línia a afegir serà

Header set Access-Control-Allow-Origin '*'

Si, de moment només volem permetre l'accés des del nostre propi PC tal com ho tenim configurat, podem afegir la línia:

Header set Access-Control-Allow-Origin 'http://ipVPNdelClient:8080'

Per fer la prova i habilitar per totes les peticions podem posar un asterisc al lloc

cURL

cURL és un projecte de software consistent en una llibreria (libcurl) orientat a la transferència de fitxers. Suporta els protocole ftp, ftps, http, https, tftp, scp, sftp, telnet, DICT, FILE i LDAP entre altres.

cURL suporta certificats HTTPS, HTTP POST, HTTP PUT, pujades FTP, Kerberos, pujades mitjançant formulari HTTP, proxies, cookies, autenticació mitjançant usuari i contrasenya (Basic, DIgest, NTLM i Negotiate per a HTTP i kerberos 4 per a FTP), continuació de transferència de fitxers, tunneling de proxy HTTP i altres prestacions. cURL és de codi obert, programari lliure distribuït sota la Llicència MIT.

El principal propòsit i ús per a cURL és automatitzar transferències de fitxers o seqüències d'operacions no supervisades. És, per exemple, una eina vàlida per a simular les accions d'usuaris en un navegador web.

Per a instal·lar cURL i fer-lo disponible al nostre servidor caldrà executar el procés d'instal·lació

apt install curl
apt install php-curl
systemctl restart apache2
Alguns dels paràmetres podeu posar-los dins el fitxer php. Cal buscar com.
<?php
    header('Access-Control-Allow-Origin: *');
    header("Access-Control-Allow-Headers: X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method");
    header("Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE");
    header("Allow: GET, POST, OPTIONS, PUT, DELETE");
    $method = $_SERVER['REQUEST_METHOD'];
    if($method == "OPTIONS") {
        die();                   // Compte!!! No sempre ho voldrem!
    }
?>