Sunteți pe pagina 1din 15

Crearea de cereri HTTP cross-domain prin tehnica CORS

A treia tehnic ce poate fi folosit pentru cereri cross-domain din JavaScript e tehnica CORS, ce
mbin avantajele celorlalte dou tehnici (avantajul JSON-P de a oferi acces neintermediat la surse
remote i avantajul scripturilor proxy de a permite mai mult dect cereri GET). Este, aadar, metoda
optim ns a devenit posibil doar recent, nu e suportat n toate versiunile de browsere i
presupune ca proprietarii serverului remote s realizeze anumite configurri. Acestea pot fi realizate
n dou moduri: (1) la nivel de server, dac dorim ca toate scripturile executate pe acel server s se
conformeze acelorai reguli; (2) la nivel de script.

Pentru configurri la nivel de server putei consulta detalii privind mai multe tipuri de servere la
adresa https://enable-cors.org/server.html. Vom exemplifica n continuare doar cu configurri la
nivel de script.

n forma cea mai simpl, o cerere CORS nu difer cu nimic de o cerere XMLHttpRequest obinuit,
deci pagina client poate arta ca i n alte situaii discutate deja (salvai-o n site1):

<html>
<head>
<script>
function solicitare()
{
cerere=new XMLHttpRequest()
cerere.onreadystatechange=procesareRaspuns
cerere.open("GET","http://site2.com/scriptcors.php")
cerere.send(null)
}
function procesareRaspuns()
{
if (cerere.readyState==4)
if (cerere.status==200)
{
raspuns=JSON.parse(cerere.responseText)
produs=raspuns.Comanda.Produse[0].Denumire
tinta=document.getElementById("tinta")
tinta.innerHTML+=produs
}
}
</script>
</head>
<body>
<input type="button" onclick="solicitare()" value="Declanseaza cerere"/>
<div id="tinta"></div>
</body>
</html>

n scriptul server remote (n site 2, cu numele scriptcors.php) se adaug o singur linie la nceput, ce
seteaz antetul HTTP Access-Control-Allow-Origin:

<?php
header("Access-Control-Allow-Origin: *");
$Produs1=array("ID"=>"P1","Pret"=>100,"Denumire"=>"Televizor");
$Produs2=array("ID"=>"P2","Pret"=>30,"Denumire"=>"Ipod");
$Produse=array($Produs1,$Produs2);
$Raspuns=array("Comanda"=>array("Client"=>"PopIon","Produse"=>$Produse));
print json_encode($Raspuns);
?>
Robert Buchmann este interzis reproducerea i distribuirea integral sau parial a acestui material fr acordul
semnat al autorului, care s menioneze explicit destinaia, data i metoda de distribuire
Dac valoarea setat este *, nseamn c scriptul server accept cereri de la orice pagin client, din
orice domeniu! Alternativ, proprietarul serverului poate restriciona valoarea la unul sau mai multe
domenii "client" explicit indicate, eventual preluate dintr-o baz de date proprie cu "clieni agreai"
(scriptul ar putea fi un serviciu public pe baz de abonare, n care dezvoltatorii de aplicaii client ar
putea s se nscrie, domeniile lor fiind apoi concatenate n acest cmp de antet HTTP pentru a activa
permisiunea).

n practic ns ambele componente devin ceva mai complicate:

Cererile CORS nu sunt suportate de orice browser, ci doar de cele care folosesc o versiune
recent a clasei XMLHttpRequest (ce se poate testa verificnd existena proprietii nou-
introduse withCredentials) sau cele care dispun de clasa XDomainRequest (cazul Internet
Explorer);
Exist dou tipuri de cereri CORS: cereri simple i cereri de tip preflighted. n a doua
categorie intr:
o cereri bazate pe alte metode dect GET i POST
o cereri POST prin care clientul trimite alte structuri de date dect obinuitele perechi
nume=valoare (deci trimite alt Content-Type dect cel specific datelor colectate din
formulare, al crui cod MIME e application/x-www-form-urlencoded).

Primul aspect este tratat printr-o procedur mai complicat de instaniere a cererii, care testeaz cu
un IF diverse combinaii:

function cerereCORS(metoda, adresa)


{
cerere=new XMLHttpRequest()
if ("withCredentials" in cerere) cerere.open(metoda,adresa,true)
else if (typeof XDomainRequest != "undefined")
{
cerere=new XDomainRequest()
cerere.open(metoda,adresa)
}
else {cerere=null}
return cerere
}
function solicitare()
{
cerere=cerereCORS("GET","http://site2.com/scriptcors.php")
if (cerere)
{
cerere.onload=procesareRaspuns
cerere.send()
}
else alert("browserul nu suporta CORS")
}
function procesareRaspuns()
{
raspuns=JSON.parse(cerere.responseText)
produs=raspuns["Comanda"]["Produse"][0]["Denumire"]
tinta=document.getElementById("tinta")
tinta.innerHTML+=produs
}

Observaii:

Robert Buchmann este interzis reproducerea i distribuirea integral sau parial a acestui material fr acordul
semnat al autorului, care s menioneze explicit destinaia, data i metoda de distribuire
Mai nti se testeaz existena proprietii withCredentials, prezent doar n versiuni mai noi
ale clasei XMLHttpRequest (n unele browsere recente). Apoi se testeaz existena clasei
XDomainRequest (folosit de Internet Explorer). Dac niciuna din condiii nu e ndeplinit,
nseamn c browserul nu suport tehnica CORS;
n funcia solicitare(), n caz c una din aceste instanieri a avut succes, se aloc funcia de
procesare a rspunsului cu onload aceast funcie apare n loc de onreadystatechange la
browsere mai recente (aduce beneficiul c nu mai trebuie incluse testele de status i
readyState pentru a verifica succesul sosirii rspunsului).

Totui, pentru a simplifica exemplele n continuare vom utiliza doar browserul Chrome n versiunea
cea mai recent i vom folosi clasa XMLHttpRequest fr celelalte verificri.

n urmtorul exemplu vom realiza o cerere preflighted. n aceast categorie intr:

acele cereri care nu folosesc metodele GET i POST (ci PUT, DELETE etc., frecvent folosite
pentru a realiza operaii pe baze de date on-line fr a apela la interogri SQL); vom reveni
ulterior asupra acestui subiect;
cererile POST care trimit spre server structuri mai complexe dect simple perechi
nume=valoare, de exemplu cod XML sau JSON! (cu cererile simple exemplificate pn acum
am trimis spre server doar parametri simpli de forma nume=valoare, sau n-am trimis nimic ci
doar am solicitat ceva de la server).

Salvai pagina client n site1:

<html>
<head>
<script>
function solicitare()
{
studenti=[{"Nume":"Ana","Nota":10},{"Nume":"Petru","Nota":4}]
datejson=JSON.stringify(studenti)
cerere=new XMLHttpRequest()
cerere.open("POST","http://site2.com/script2cors.php")
cerere.setRequestHeader("Content-Type","application/json")
cerere.onload=procesareRaspuns
cerere.send(datejson)
}
function procesareRaspuns()
{
tinta=document.getElementById("tinta")
tinta.innerHTML+=cerere.responseText
}
</script>
</head>
<body>
<input type="button" onclick="solicitare()" value="Declanseaza cerere"/>
<div id="tinta"></div>
</body>
</html>

Observaii:

JSON.stringify e operaia invers lui JSON.parse. Practic am convertit un vector de obiecte


JavaScript ntr-un string JSON pe care l trimitem serverului;

Robert Buchmann este interzis reproducerea i distribuirea integral sau parial a acestui material fr acordul
semnat al autorului, care s menioneze explicit destinaia, data i metoda de distribuire
Am construit o cerere POST spre serverul site2. Deoarece trimitem cod JSON i nu nite
simpli parametri trebuie s anunm serverul prin setarea antetului HTTP "Content-Type".

Pe server (site2), scriptul care primete o cerere preflight trebuie s realizeze minim urmtoarele
operaii (script2cors.php):

<?php
header("Access-Control-Allow-Origin: *");

if ($_SERVER["REQUEST_METHOD"]=="OPTIONS")
{
header("Access-Control-Allow-Methods: OPTIONS,POST");
header("Access-Control-Allow-Headers: Content-Type");
header('Access-Control-Max-Age: 1');
}
if ($_SERVER["REQUEST_METHOD"]=="POST")
{
if (($_SERVER["HTTP_ORIGIN"]=="http://site1.com")&&($_SERVER["CONTENT_TYPE"]=="application/json"))
{
$dateClient=file_get_contents("php://input");
$datePHP=json_decode($dateClient);
print "Serverul confirma primirea de note pentru ".$datePHP[0]->Nume." si ".$datePHP[1]->Nume;
}
else {print "Acceptam cereri POST doar de la site1.com si doar cu continut JSON";}
}
else print "Nu acceptam cereri GET!";
?>

O cerere preflighted este de fapt o succesiune de 2 cereri: o pre-cerere de verificare generat


automat de browser (prin metoda HTTP OPTIONS) apoi cererea efectiv prin metoda specificat de
client (aici POST).

1. Prin pre-cerere browserul verific dac serverul suport tehnica CORS i l ntreab ce
metode i antete HTTP accept. Aceast pre-cerere este de tip OPTIONS, deci scriptul server
trebuie s conin un IF care s detecteze pre-cererea i s-i rspund cu informaiile
relevante. De regul aceast informaie "relevant" e o succesiune de cmpuri de antet HTTP
prin care clientul e informat asupra a ce are voie s cear:
antetul Access-Control-Allow-Methods indic browserului ce metode HTTP accept
serverul (lista trebuie s includ metoda OPTIONS necesar pre-cererii, la care se
adaug una sau mai multe metode suportate de server; n exemplul de fa serverul
mai suport doar cereri prin metoda POST ceea ce e suficient pentru nevoile paginii
client);
antetul Access-Control-Allow-Headers indic browserului ce antete HTTP accept
serverul (aici "Content-Type", prin care clientul poate anuna faptul c trimite date n
alte formate dect simpli parametri nume=valoare);
antetul Access-Control-Max-Age stabilete ct timp s se stocheze n browser (cache)
aceste informaii; n timpul dezvoltrii recomandm s fie 1 secund, pentru a putea
vedea efectul eventualelor corecturi, ns la lansarea site-ului se pun cteva ore,
astfel nct pre-cererea s nu mai fie repetat la fiecare cerere CORS (avnd n
vedere c aceste informaii nu se prea modific);
2. Dup ce pre-cererea a fost finalizat i exist o confirmare c serverul e pregtit s accepte
ce are clientul de trimis, browserul va trimite cererea efectiv. n exemplul de fa, clientul e
compatibil cu condiiile impuse de server, cci va trimite ceva prin metoda POST, ntr-un

Robert Buchmann este interzis reproducerea i distribuirea integral sau parial a acestui material fr acordul
semnat al autorului, care s menioneze explicit destinaia, data i metoda de distribuire
format anunat cu antetul Content-Type. Scriptul server trebuie s fie pregtit, ntr-o alt
ramur IF, s accepte i cererea efectiv:
remarcai IF-ul care verific faptul c a sosit o cerere POST, apoi cel care verific dac
sursa cererii e site1 iar tipul datelor e application/json (prin acest IF scriptul limiteaz
sursa la site1, chiar dac pe prima linie restricia e relaxat; prin asta ncercm s
sugerm c serverul poate pregti mai multe ramuri IF care s trateze clieni diferii
n moduri diferite);
datele sosite prin POST sunt preluate din file_get_contents("php://input") i nu din
$_POST cum suntem obinuii (asta deoarece nu mai e vorba de variabile simple, ci
de o structur complex de date);
am construit un mesaj care confirm c datele au sosit i acest mesaj e returnat
clientului pentru afiare.
3. Se pot aduga i alte IF-uri n mod similar, care s ofere reacii diferite la cereri de alte tipuri
(GET, PUT, DELETE etc.). Aici nu mai avem altele, aa c am adugat o ramur ELSE anunnd
printr-un mesaj c cereri GET nu se accept.

Testai exemplul n urmtoarele moduri:

Executai pagina client (n consola browserului putei observa c au loc 2 cereri una dup
alta);
Accesai direct scriptul server prin browser aceast tentativ va accesa scriptul prin metoda
GET, ceea ce va duce la mesajul de eroare de la finalul scriptului;
Accesai direct scriptul server prin Postman, configurnd o cerere POST ca n imagine (cu
datele incluse n seciunea Body, tipul raw). Vei primi penultimul mesaj de eroare, declanat
de faptul c originea cererii nu e site1.com!

Robert Buchmann este interzis reproducerea i distribuirea integral sau parial a acestui material fr acordul
semnat al autorului, care s menioneze explicit destinaia, data i metoda de distribuire
n sfrit, n urmtorul exemplu realizm cererea CORS i prin Jquery:

<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>
function solicitare()
{
studenti=[{"Nume":"Ana","Nota":10},{"Nume":"Petru","Nota":4}]
datejson=JSON.stringify(studenti)
configurari={url:"http://site2.com/script2cors.php",type:"POST",data:datejson,contentType:"application/json",success:procesare
Raspuns}
$.ajax(configurari)
}
function procesareRaspuns(raspuns)
{
$("#tinta").html(raspuns)
}
</script>
</head>
<body>
<input type="button" onclick="solicitare()" value="Declanseaza cerere"/>
<div id="tinta"></div>
</body>
</html>

Observaie:

Suntem nevoii s folosim $.ajax, dei Jquery ofer i varianta mai scurt $.post pentru cereri
POST. Motivul este c $.post nu permite modificarea antetului Content-Type (situaia se
poate schimba n versiuni viitoare de Jquery).
Observai i noul mod prin care am inserat rspunsul n pagin, de data aceasta realiznd o
selecie dup ID i folosind metoda html() (comparai cu modul folosit anterior, cu
appendTo).

Crearea de cereri spre servere JSON

Pn la acest punct am construit rspunsuri JSON cu ajutorul unui script PHP. Sursa acelor date ar
putea fi bazele de date MySQL, din care PHP s le preia i s le mpacheteze n JSON. Sau, dup cum
am vzut la tehnica scripturilor proxy, sursa ar putea fi alte scripturi de pe alte servere.

O situaie tot mai frecvent n ultimii ani este ca datele JSON s fie solicitate direct de la servicii de
date JSON care:

Pot fi percepute ca nite baze de date n care toat informaia e stocat n format JSON;
Accesarea datelor se realizeaz on-line prin interogri "deghizate" n cereri HTTP (de aceea,
n asociere cu aceste tehnologii vei mai ntlni termeni precum "baze de date NoSQL" sau
"Database-as-a-service"); mai exact, clienii nu vor interoga direct baza de date, ci
interacioneaz cu ea printr-o interfa ce permite cereri HTTP cu diverse combinaii de
configurri.

Exist o serie de servicii JSON disponibile on-line, cu diverse limitri (de pre, de numr de cereri etc.)
ns pentru a fi ct mai flexibili, vom instala propriul server JSON gratuit, care va putea fi "interogat"

Robert Buchmann este interzis reproducerea i distribuirea integral sau parial a acestui material fr acordul
semnat al autorului, care s menioneze explicit destinaia, data i metoda de distribuire
prin cereri HTTP trimise fie din JavaScript, fie din PHP (i din orice alt limbaj ce ofer posibilitatea de a
executa cereri HTTP).

Serverul pentru care optm pentru exemplificare e JSON Server1 i trebuie instalat peste
frameworkul Node.js (un framework popular pentru lucrul server-side cu JavaScript). Paii instalrii i
pornirii sunt:

Pas1. Downloadai i instalai NodeJS de la adresa de mai jos, asigurndu-v c n timpul instalrii
sunt bifate toate componentele, inclusiv componenta NPM (aceasta se ocup de descrcarea
ulterioar a altor pachete/librrii bazate pe Node.js, printre ele numrndu-se i JSON Server).

https://nodejs.org/en/

Pas2. Executai n linia de comand Windows comanda de mai jos, ce se ocup de descrcarea i
instalarea JSON Server:

npm install g json-server

Pas3. Creai un fiier JSON care s conin datele iniiale ce le vom ncrca n baza de date. Salvai
acest fiier cu numele dateinitiale.json (n htdocs) i cu coninutul urmtor:

{
"students": [
{"id": 1,
"name": "Pop Ion"},
{"id": 2,
"name": "Pop Maria"}
],
"courses": [
{"id": 1,
"title": "Web development",
"teacher":{"name":"Popescu Ion","office":404}},
{"id": 2,
"title": "Java",
"teacher":{"name":"Ionescu Maria","office":403}},
{"id": 3,
"title": "Databases",
"teacher":{"name":"Marian Vasile","office":401}}
],
"grades": [
{"courseId":1,
"studentId":1,
"grade":7},
{"courseId":1,
"studentId":2,
"grade":5},
{"courseId":2,
"studentId":1,
"grade":5},
{"courseId":2,
"studentId":2,
"grade":5},
{"courseId":3,
"studentId":2,
"grade":10}
],
"faculty":{"name":"FSEGA","address":"str. T Mihali 58-60 Cluj Napoca"}
}

Pas4. Navigai cu linia de comand Windows pn n folderul unde ai salvat fiierul:

1
Detalii complete despre JSON Server putei gsi la https://github.com/typicode/json-server
Robert Buchmann este interzis reproducerea i distribuirea integral sau parial a acestui material fr acordul
semnat al autorului, care s menioneze explicit destinaia, data i metoda de distribuire
cd c:\xampp\htdocs

Pas5. Fiind poziionai n folderul cu fiierul, pornii JSON server preciznd totodat s stocheze
datele din fiierul JSON creat:

json-server --watch dateinitiale.json --port 4000

Opiunea --port stabilete portul localhost la care se pot trimite cererile spre JSON server.

Opiunea --watch face ca orice modificare n fiierul cu date iniiale s se transmit automat la datele
serverului. Asta nseamn c modificrile salvate n fiierul dateinitiale.json se vor sincroniza automat
cu cele din server i vor deveni imediat accesibile pentru "interogri".

Accesai n browser localhost:4000 i vei vedea pagina de ntmpinare JSON Server.

Analizai puin fiierul JSON, fcnd o paralel cu bazele de date MySQL. Ceea ce avem n fiier este
practic echivalentul a trei tabele:

students (cu cmpurile id i name),


courses (cu cmpurile id, title i teacher, ultimul avnd valori complexe de tip obiect)
grades (cu cmpurile studentId - care funcioneaz ca o "cheie strin", indicnd ID-ul uneia
din nregistrrile din students; courseId la fel, cheie strin ce stabilete o relaie cu
cursurile; grade nota pe care studentul referit a luat-o la cursul referit); practic prin
"tabelul" grades am realizat o relaie many-to-many ntre studeni i cursuri;
la acestea se mai adaug vectorul asociativ faculty, care nu e considerat neaprat tabel, ct
mai degrab ca un obiect unic cu o serie de proprieti (name, address)

Reinei:

putem considera fiecare vector de pe primul nivel ierarhic al fiierului JSON ca fiind un
"tabel" n care cheia primar obligatoriu trebuie s poarte numele "id" (cu valori unice!) iar
cheile strine poart un nume format din singularul numelui tabelului concatenat cu Id: dac
dorim o relaie cu "tabelul" students numele cheii strine trebuie s fie studentId (din acest
motiv numele tabelelor trebuie s fie n englez)
pe lng tabele, fiierul JSON poate conine pe primul nivel ierarhic i obiecte singulare ce nu
pot fi interpretate ca "tabele" (vezi faculty).

Toate entitile descoperite pe primul nivel ierarhic al fiierul JSON (adic cele 3 tabele i obiectul
faculty) sunt considerate "resurse" i vor putea fi accesate direct prin cereri HTTP. Putei trimite
cereri de tip GET direct n bara de adres a browserului, ns pentru alte tipuri de cereri folosii fie
programul Postman, fie scripturi.

ncepem cu cteva cereri GET tastate direct n browser, care practic funcioneaz ca nite interogri.

http://localhost:4000/db
(afieaz tot coninutul bazei de date)

http://localhost:4000/students
(afieaz datele tuturor studenilor)

http://localhost:4000/students?_sort=name&_order=DESC
(afieaz datele tuturor studenilor sortate descresctori dup nume)
Robert Buchmann este interzis reproducerea i distribuirea integral sau parial a acestui material fr acordul
semnat al autorului, care s menioneze explicit destinaia, data i metoda de distribuire
http://localhost:4000/faculty
(afieaz datele obiectului faculty)

http://localhost:4000/students/1
(afieaz datele studentului cu id=1)

http://localhost:4000/students?name=Pop Ion (spaiul se va substitui automat)


(afieaz datele studentului cu name="Pop Ion"; se pot aduga mai multe filtre similare separate cu
&)

http://localhost:4000/students?id_ne=2
(afieaz datele studentului cu id diferit de 2; codul _ne= se interpreteaz ca not equal)

http://localhost:4000/students?q=Maria
(afieaz datele studentului n ale crui date apare stringul "Maria")

http://localhost:4000/grades?grade_gte=6&grade_lte=9
(afieaz notele cu grade>=6 i <=9; observai codurile folosite: _gte i _lte, cci nu putem folosi
caracterele > sau < n adrese URL)

Observaie: JSON Server creeaz "tabele" doar din entitile pe care le gsete pe primul nivel
ierarhic. Entitile de pe nivele ierarhice mai adnci (cum este teacher) NU vor putea fi accesate direct
putem obine datele lor laolalt cu restul entitii din care fac parte (courses), dar nu putem
formula o cerere direct care s ne dea doar profesorii, de genul
2
http://localhost:4000/courses/1/teacher . n schimb putem implica datele profesorilor n condiii:

http://localhost:4000/courses?teacher.office=403
(afieaz datele cursului ale crui profesor are biroul 403; observai cum informaiile despre profesori
pot fi accesate cu ajutorul punctului, ns numai n scopul testrii de condiii)

Observaie: Am amintit anterior c prin cheile strine studentId i courseId avem practic o relaie ntre
"tabele". Putem beneficia de relaia respectiv n urmtoarele moduri:

http://localhost:4000/grades?_expand=student&_expand=course
(afieaz notele, expandnd informaia despre studeni i cursuri preluat din tabelele relaionate).

http://localhost:4000/students/1?_embed=grades
(afieaz datele studentului cu ID=1 incluznd notele acestora, detectate tot pe baza corespondenei
students-studentId; la fel putem obine i cursuri cu notele incluse)

http://localhost:4000/students/1/grades
(afieaz notele studentului 1, beneficiind de relaia grades-students)

http://localhost:4000/students/1/grades?courseId=2
(afieaz nota studentului 1, la cursul 2)

Observaie: Toate aceste "interogri" returneaz vectori sau obiecte COMPLETE n format JSON. Nu
putem extrage o valoare simpl (de genul "titlul cursului 1"). Valorile simple vor trebui extrase de
clientul care trimite cererea HTTP (n acele funcii de procesare a rspunsului care apar n exemplele
JavaScript).

2
Dac dorim s facem posibile astfel de interogri trebuie s mutm profesorii pe primul nivel al fiierului i s
construim relaii cu teacherId, aa cum am fcut i cu studentId i courseId. Sintaxa JSON ne permite nglobarea
multipl de obiecte unele n altele, ns JSON Server va accepta interogri directe doar pentru cele de pe primul
nivel ierarhic.
Robert Buchmann este interzis reproducerea i distribuirea integral sau parial a acestui material fr acordul
semnat al autorului, care s menioneze explicit destinaia, data i metoda de distribuire
Toate exemplele de pn acum au fost cereri GET tastate direct n browser. Pentru alte tipuri de
cereri folosii programul Postman. Se mai accept urmtoarele tipuri:

POST pentru upload de date n server (similar comenzii INSERT din SQL);
PATCH pentru editarea proprietilor unei entiti (similar comenzii UPDATE din SQL)
PUT pentru substituirea de "nregistrri" (similar unui UPDATE care modific toate cmpurile
unei nregistrri n afara cheii primare; atenie, operaia poate s i tearg cmpuri cu totul)
DELETE pentru tergere de "nregistrri"

Mai nti construii n Postman o cerere POST cu elementele:

Adresa http://localhost:4000/students
Metoda POST
Headers: Content-Type=application/json (acesta trebuie setat la ORICARE din cererile care
trimit date, i datele trebuie s fie structuri JSON complete, nu se pot trimite valori simple)
Body: raw JSON cu valoarea {"name":"Ionescu Andrei"}

Efectul este inserarea unui student nou n vectorul students, cu numele Ionescu Andrei. ID-ul nu
trebuie precizat cci cheia primar e incrementat automat de JSON-Server (avem voie s
precizm un ID, dar riscm s introducem valori duplicate aa c cel mai comod e s lsm JSON
Server s gestioneze ID-urile).

Pentru a vedea efectul nchidei i redeschidei fiierul dateinitiale.json, care e meninut


permanent sincron cu serverul! (dac serverul a fost pornit cu --watch). Dac avei deja fiierul
deschis n Notepad++ acesta va avertiza c fiierul s-a modificat, dac l avei deschis n VS Code
probabil se va actualiza automat.

Construii i o cerere DELETE pentru a terge studentul adugat:

Adresa http://localhost:4000/students/3 (remarcai ID-ul studetului, acesta a fost


generat de POST-ul anterior!)
Metoda DELETE
Headers: Content-Type nu conteaz cci nu se trimit date
Body rmne gol cci nu se trimit date

Construii i o cerere PATCH pentru a modifica datele profesorului de la cursul cu ID=2:

Adresa http://localhost:4000/courses/2
Robert Buchmann este interzis reproducerea i distribuirea integral sau parial a acestui material fr acordul
semnat al autorului, care s menioneze explicit destinaia, data i metoda de distribuire
Metoda PATCH
Headers:Content-Type=application/json
Body cu datele noi ale profesorului: {"teacher": {"name": "Moldovan Ioan","office": 409}}

Construii i o cerere PUT pentru a nlocui toate detaliile facultii nu doar valorile
proprietilor, ci chiar proprietile:

Adresa http://localhost:4000/faculty
Metoda PUT
Headers:Content-Type=application/json
Body cu datele noi ale facultii: {"country":"Romania", "domain":"Economics"}

Construii i o cerere PUT pentru a nlocui toate datele primului student, cu excepia ID-ului
(acesta nu poate fi modificat nici cu PUT, nici cu PATCH, aa c nu trebuie inclus n datele
trimise):

Adresa http://localhost:4000/students/1
Metoda PUT
Headers:Content-Type=application/json
Body cu datele noi {"nume de familie":"Popovici","prenume":"Andrei"}

Observai c nu e doar o modificare de valori, ci o substituire complet practic am introdus


proprieti noi care nu mai respect structura general a entitilor-studeni. Bazele de date de tip
JSON Server nu au o structur fix asemenea bazelor de date relaionale noi obiecte i proprieti
pot fi adugate, terse, create oricnd. Aceasta poate fi perceput pe de o parte ca flexibilitate, pe de
alt parte ca un pericol de a produce inconsistene i date invalide. Serverele comerciale ofer
mecanisme de a controla validitatea i structura - aa cum documentele XML pot fi verificate cu
ajutorul XML Schema, i structurile JSON pot fi verificate cu ajutorul JSON Schema.

Modificai numele de familie al studentului introdus cu ajutorul unei cereri PATCH:

Adresa http://localhost:4000/students/1
Metoda PATCH
Headers:Content-Type=application/json
Body cu datele noi {"nume de familie":"Popescu"}

Observai c n metoda PATCH se modific doar valorile trimise, restul se pstreaz, n timp ce cu
metoda PUT se nlocuiesc complet proprietile cu cele pe care le trimitem (i valorile trimise), deci
proprietile netrimise prin PUT dispar, n timp ce proprietile netrimise prin PATCH se pstreaz.

n final realizm o operaie PUT dintr-o pagin client, prin Jquery:

<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>
function solicitare()
{
proprietati={"first name":"Maria","last name":"Pop"}
datejson=JSON.stringify(proprietati)
configurari={url:"http://localhost:4000/students/2",type:"PUT",data:datejson,contentType:"application/json",
success:procesareRaspuns}
Robert Buchmann este interzis reproducerea i distribuirea integral sau parial a acestui material fr acordul
semnat al autorului, care s menioneze explicit destinaia, data i metoda de distribuire
$.ajax(configurari)
}
function procesareRaspuns(raspuns)
{
$("#tinta").html("s-a inserat cu succes studentul "+raspuns["first name"]+" "+raspuns["last name"])
}
</script>
</head>
<body>
<input type="button" onclick="solicitare()" value="Declanseaza cerere"/>
<div id="tinta"></div>
</body>
</html>

Observai modul de configurare a cererii ce respect indicaiile anterioare (intirea studentului 2 prin
adresa URL, metoda PUT pentru nlocuirea datelor, declararea contentType). JSON Server rspunde
implicit cu informaia care s-a adugat, deci din variabila raspuns putem accesa toate proprietile
inserate i le accesm pentru a afia un mesaj de confirmare.

La executarea scriptului verificai faptul c studentul 2 din dateinitiale.json i s-au nlocuit datele cu
cele trimise din script. Succesul scriptului ne demonstreaz i faptul c JSON Server este deja
configurat pentru cereri prin tehnica CORS.

Mai mult, JSON Server este configurat i pentru tehnica JSON-P. Putem demonstra asta trimind o
cerere GET cu parametrul callback, care s ne listeze toate cursurile din baza de date i profesorii lor:

<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>
function solicitare()
{
adresa="http://localhost:4000/courses?callback=?"
$.getJSON(adresa,function(raspuns)
{
$.each(raspuns,function(indice,curs)
{
continutDeAfisat="<div><i>"+curs.title+"</i> predat de "+curs.teacher.name+"</div>"
$(continutDeAfisat).appendTo(document.body)
}
)
}
)
}
</script>
</head>
<body>
<input type="button" onclick="solicitare()" value="Declanseaza cerere"/>
<p>Cursurile din baza de date sunt:</p>
</body>
</html>

Observai:

Adresa cererii, corespunztoare unei interogri ce solicit toate cursurile, precum i


argumentul callback=? ce activeaz n Jquery tehnica JSON-P
Metoda GET e implicit cnd folosim $.getJSON

Robert Buchmann este interzis reproducerea i distribuirea integral sau parial a acestui material fr acordul
semnat al autorului, care s menioneze explicit destinaia, data i metoda de distribuire
Funcia anonim de procesare a rspunsului folosete facilitatea $.each oferit de Jquery.
Aceasta primete ca argument un vector (n acest caz vectorul cursurilor primit de la JSON
Server) i proceseaz fiecare element al vectorului printr-o funcie anonim
o n cazul de fa, din fiecare curs gsit n vector se construiete un mesaj ce anun
titlul i profesorul identificat n datele cursului; fiecare mesaj e adugat la pagin
devenind vizibil n browser.

Ultimele exemple demonstreaz i posibilitatea de a crea pagini Web dinamice care nu mai au deloc
un script PHP pe server. Rolul bazei de date MySQL e jucat de serviciul oferit de JSON Server, rolul
interogrilor SQL este preluat de cererile HTTP iniiate direct din pagina HTML cu ajutorul Jquery (sau
XMLHttpRequest, sau alte posibiliti oferite de JavaScript). Desigur, am putea avea i un script PHP
care s acceseze JSON Server i s aplice prelucrri suplimentare asupra datelor nainte s le ofere
paginii client aceast tehnic devine util atunci cnd se interogheaz date de la mai multe surse i
e necesar un efort suplimentar de combinare a lor nainte s fie prezentate, de exemplu dac exist
totui i o baz de date MySQL proprie ale crei date trebuie combinate cumva cu cele de la servicii
cross-domain precum acest JSON Server.

Cereri asincrone n scripturi proxy PHP

Am vzut deja cum realizm cereri HTTP n PHP, cu ajutorul bibliotecii cURL. Exist o diferen
important fa de cererile HTTP realizate n JavaScript (cu Jquery sau XMLHttpRequest). Cererile
exemplificate din JavaScript sunt asincrone (de aici litera A din AJAX) n timp ce cererile cURL ntre
scripturi PHP sunt sincrone.

Prin cerere asincron nelegem o cerere care, dac rspunsul i ntrzie (de exemplu dac se
ateapt cantiti mari de date), restul scriptului continu s funcioneze, nu rmne blocat ntr-o
stare de ateptare pn la sosirea rspunsului. Aceasta e o cerin esenial n site-urile moderne,
unde clickurile utilizatorului declaneaz adesea schimburi de date cu serverul, iar acestea nu trebuie
s ntrerup funcionarea paginii (ci cel mult a poriunii de pagin care are nevoie de acele date). n
site-urile vechi clickurile care solicitau informaii de la server (butoanele submit, linkurile) aveau ca
efect rencrcarea integral a paginii, de aceea PHP genera pagini HTML ntregi i nu doar date.

n prezent cererile asincrone sunt un element obligatoriu n orice pagin client, realizate adesea cu
Jquery i alte metode studiate deja. n schimb cererile cURL ntre scripturi PHP nu sunt asincrone
dac trimitem mai multe cereri cURL din acelai script PHP, fiecare cerere va bloca temporar
execuia, pn cnd sosete rspunsul (cererile se vor executa n ordinea n care apar n script). n
PHP cererile asincrone devine importante atunci cnd avem nevoie de mai multe cereri spre mai
multe surse diferite i dorim ca acele cereri s se execute n paralel, fr a atepta unele dup altele.

Pentru a realiza cereri asincrone n PHP nu mai lucrm cu cURL, ci avem nevoie de una din librriile
care ofer aceast facilitate. Cea mai popular librrie este GuzzleHttp. Orice putem face n cURL e
posibil i n GuzzleHttp, dar ultima ofer i un mod comod de a realiza cereri paralele asincrone spre
multiple servere sau servicii.

GuzzleHttp nu este inclus n instalarea implicit pe care XAMPP o ofer pentru PHP, aa c trebuie
s o instalm. Pentru instalare avem nevoie de programul Composer, care se ocup de descrcarea i

Robert Buchmann este interzis reproducerea i distribuirea integral sau parial a acestui material fr acordul
semnat al autorului, care s menioneze explicit destinaia, data i metoda de distribuire
instalarea a diverse extensii PHP (practic Composer este pentru PHP ceea ce este NPM pentru
Node.js).

Vom realiza aadar mai nti instalarea programului Composer, apoi l vom folosi pentru a instala
extensia GuzzleHttp (iar n viitor vom mai instala i alte extensii):

Pas1. Cutai fiierul php.ini din XAMPP (la o instalare normal ar fi n C:\xampp\php). n acest fiier
asigurai-v c linia extension=php_openssl.dll nu e dezactivat prin comentariu (dac e, tergei
semnul ; din faa ei)

Pas2. Descrcai i executai Composer-Setup.exe de la adresa de mai jos:

https://getcomposer.org/download/
(n timpul instalrii vei fi ntrebai unde e instalat php, selectai calea la care a fost instalat de
XAMPP)

Pas3. Folosii linia de comand Windows pentru a naviga n site1:

cd c:\xampp\htdocs\site1

Pas4. Aflai fiind n folderul site1, unde vom crea exemplul, instalai librria GuzzleHttp cu comanda:

composer require guzzlehttp/guzzle


(comanda ar trebui s nceap s descarce fiierele Guzzle i s le stocheze pe toate ntr-un folder
numit vendor; de acolo vor trebui importate n scripturile PHP ce au nevoie de Guzzle)

Documentaia oficial GuzzleHttp poate fi consultat la adresa:

http://docs.guzzlephp.org/en/latest/

Considerm c avem n site2 acelai script pe care l-am mai folosit, cu numele datejson.php:

<?php
header("Content-type:application/json");
$Produs1=array("ID"=>"P1","Pret"=>100,"Denumire"=>"Televizor");
$Produs2=array("ID"=>"P2","Pret"=>30,"Denumire"=>"Ipod");
$Produse=array($Produs1,$Produs2);
$Raspuns=array("Comanda"=>array("Client"=>"PopIon","Produse"=>$Produse));
print json_encode($Raspuns);
?>

Creai n site 1 urmtorul script, care contacteaz scriptul de mai sus prin Guzzle:

<?php
require "vendor/autoload.php";
$client=new \GuzzleHttp\Client();
$cerere=$client->getAsync("http://site2.com/datejson.php");
$cerere->then("procesareRaspuns")->wait();

function procesareRaspuns($raspuns)
{
print $raspuns->getBody();
}
?>

Observaii:

Robert Buchmann este interzis reproducerea i distribuirea integral sau parial a acestui material fr acordul
semnat al autorului, care s menioneze explicit destinaia, data i metoda de distribuire
Cu require ne asigurm c toate funciile i clasele Guzzle vor fi importate automat cnd le
menionm n cod, fr a fi necesare operaii multiple de import/includere (trebuie s fim
ateni la calea fiierului autoload, s fie corect);
Pe linia urmtoare am instaniat clasa Client() oferit de GuzzleHttp;
Apoi am creat o cerere asincron de tip GET cu getAsync(), spre scriptul ce ofer date JSON;
Pe linia urmtoare funcia then() indicm funcia responsabil cu procesarea rspunsului;
cererea efectiv se execut nlnuind mai departe wait();
La final am construit funcia de procesare a rspunsului separat de restul codului (aa cum
procedam i n JavaScript); aceasta realizeaz doar o afiare a rspunsului integral preluat cu
getBody() (echivalentul lui responseText din JavaScript).

Dup cum spuneam, beneficiile reale ale cererilor asincrone n PHP in de executarea paralel a mai
multor cereri spre mai multe destinaii. De aceea vom folosi i o a doua destinaie baza de date
JSON Server de la exerciiile precedente. Asigurai-v c serverul este pornit i cu datele ncrcate:

json-server --watch dateinitiale.json --port 4000

Mai jos avei un exemplu de script care solicit date att de la datejson.php ct i de la JSON Server i
nu face altceva dect s le afieze:

<?php
require "vendor/autoload.php";
$client=new \GuzzleHttp\Client();
$cereri=[
"cerere1"=>$client->getAsync("http://site2.com/datejson.php"),
"cerere2"=>$client->getAsync("http://localhost:4000/students")
];
$rezultate=\GuzzleHttp\Promise\unwrap($cereri);
print $rezultate["cerere1"]->getBody();
print $rezultate["cerere2"]->getBody();
?>

Observai cum:

am construit un vector de cereri asincrone spre cele dou destinaii


am folosit funcia Promise\unwrap() pentru a le executa pe ambele n paralel i a culege
rezultatele ambelor ntr-un vector de rezultate
apoi am afiat din acest vector toate datele sosite.

GuzzleHttp poate construi i cereri sincrone ca i cURL, de diferite tipuri (POST, PUT, PATCH etc.), cu
o sintax destul de comod. Documentaia oficial GuzzleHttp, unde gsii numeroase exemple de
cereri Guzzle, poate fi consultat la adresa:

http://docs.guzzlephp.org/en/latest/quickstart.html

Robert Buchmann este interzis reproducerea i distribuirea integral sau parial a acestui material fr acordul
semnat al autorului, care s menioneze explicit destinaia, data i metoda de distribuire