Els protocols d'Internet tenen més d'un model. El model OSI ens dona una organització per capes:
Com a programadors, podem involucrar-nos a diferents nivells del model:
Els protocols de nivell 4 i mig estàndards es poden veure a aquesta llista.
Unicast: adreces de classe A, B i C.
Transport TCP i UDP.
Broadcast: Adreça del host amb tot 1's.
Transport UDP. No travessen els enrutadors, i les reben totes les màquines.
Multicast: adreces de classe D.
Transport UDP. Una màquina ha d'escoltar per rebre la comunicació.
Tenim tres eines per depurar protocols HTTP: netcat, curl i l'inspector dels navegadors.
Netcat permet connectar-se a un port i fer una conversa, utilitzant les canonades. Si s'indica -u utilitza UDP, si no, TCP. Per exemple, per a accedir al servei echo de la nostra màquina:
$ nc localhost 7CURL permet obtenir la resposta d'una URL a la xarxa.
$ curl -I http://www.example.cat(GET, veure headers)HTTP/1.1 200 OKDate: Fri, 05 Apr 2019 05:03:23 GMTServer: ApacheX-Logged-In: FalseP3P: CP="NOI ADM DEV PSAi COM NAV OUR OTRo STP IND DEM"Cache-Control: no-cachePragma: no-cacheSet-Cookie: 4af180c8954a0d5a1965b5b1b23ccbc5=pc1jg8f5kqigd71iec5a5lm7k5; path=/X-Powered-By: PleskLinContent-Type: text/html; charset=utf-8$ curl http://www.example.cat(GET, contingut de la pàgina web)$ curl -v http://www.example.cat(GET, headers i contingut)$ curl -d "key1=val1&key2=val2" http://www.example.cat(POST)HTTP és un protocol de nivell aplicació per a sistemes col·laboratius i distribuïts. És el component principal de la web, gràcies a l'ús de documents d'hipertext. HTTP/1.1, la versió actual, està implementat mitjançant TCP al transport. La versió 2 ja està estandaritzada, i la 3 funcionarà sobre UDP.
La versió segura d'HTTP es diu HTTPS, o també HTTP sobre TLS, el protocol criptogràfic per a la transmissió segura.
Una sessió és una seqüència de peticions/respostes. Comença mitjançant l'establiment d'una connexió TCP a un port d'un servidor (habitualment 80). El servidor contesta habitualment amb un codi, del tipus "HTTP/1.1 200 OK", i amb un cos, que normalment conté el recurs demanat.
HTTP es un protocol sense estat, tot i que algunes aplicacions utilitzen mecanismes per emmagatzemar informació. Per exemple, les cookies.
Una petició conté, habitualment:
Mètodes de petició:
Una resposta conté:
Els codis d'estat poden ser del tipus:
Podem utilitzar el programa telnet per conectar-nos a un servidor web HTTP i enviar una comanda GET.
$ telnet www.example.cat 80Trying 91.142.215.126...Connected to www.example.cat.Escape character is '^]'.GET / HTTP/1.0Això provoca la resposta del servidor:
HTTP/1.1 200 OKDate: Fri, 05 Apr 2019 05:29:38 GMTServer: ApacheLast-Modified: Tue, 08 Sep 2015 11:03:06 GMTETag: "13226542-cd-51f3a4f816e80"Accept-Ranges: bytesContent-Length: 205X-Powered-By: PleskLinMS-Author-Via: DAVConnection: closeContent-Type: text/html<html>...</html>Connection closed by foreign host.La classe URL fa referència a un recurs a la WWW. Un recurs genèric pot tenir la següent forma.
Veiem un exemple per al protocol HTTP:
En aquest cas, tenim que:
A Java es pot construir una URL amb:
URL url = new URL(String spec)Un cop fet això, podem accedir a cada part de l'URL amb els mètodes getHost(), getPath(), getPort(), getProtocol(), getQuery(), etc.
Els dos mètodes més importants per interactuar amb l'URL són:
URLConnection openConnection(): retorna una connexió al recurs remot.InputStream openStream(): retorna un InputStream per a llegir el recurs remot.La classe URLConnection és abstracta, i si hem accedir a un recurs HTTP llavors l'objecte serà una instància de HttpURLConnection.
Per llegir una pàgina web que es trobi a l'URL d'una cadena anomenada urlText, podem fer:
URL url = new URL(urlText);BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));Per llegir un arxiu:
BufferedInputStream in = new BufferedInputStream(new URL(urlText).openStream());Amb openConnection podem accedir als mètodes del protocol HTTP i els codis d'estat que es retornen o el tipus de contingut.
Aquest és un mètode GET:
URL url = new URL(urlText);HttpURLConnection httpConn = ((HttpURLConnection) url.openConnection());httpConn.setRequestMethod("GET"); // opcional: GET és el mètode per defecteint responseCode = httpConn.getResponseCode();String contentType = httpConn.getContentType();BufferedReader in = new BufferedReader(new InputStreamReader(httpConn.getInputStream()));// falta llegir in: resposta del servidorAquest és un mètode POST:
URL url = new URL(urlText);HttpURLConnection httpConn = ((HttpURLConnection) url.openConnection());httpConn.setRequestMethod("POST");httpConn.setDoOutput(true);OutputStreamWriter out = new OutputStreamWriter(httpConn.getOutputStream());out.write("propietat1=valor1&propietat2=valor2"); // valors dels paràmetres del POSTout.close();int responseCode = httpConn.getResponseCode();String contentType = httpConn.getContentType();BufferedReader in = new BufferedReader(new InputStreamReader(httpConn.getInputStream()));// falta llegir in: resposta del servidorUn sòcol és un enllaç de doble sentit que permet comunicar dos programaris que són a la xarxa.
Aquests dos programaris fan dues funcions: la del client i la del servidor. El servidor proveeix algun servei des d'un lloc conegut (adreça IP + port), i el client accedeix a aquest servei. Aquest servei ha d'implementar un protocol ben definit, sigui un estàndard o un de dissenyat a mida.
Els números de ports són:
Com que els dos programaris treballen en l'àmbit del protocol, al codi dels dos programaris no hi ha dependències mútues. Però és habitual que qui implementa el protocol proveeixi d'una llibreria de client per poder accedir al servei. Això permet reduir el codi que un client ha d'escriure, i assegura que utilitzarà correctament el protocol. A Java, la llibreria de client es materialitza mitjançant un arxiu jar i una documentació d'ús.
Es poden utilitzar els protocols TCP o UDP. TCP està orientat a connexió, i UDP no. Això vol dir que TCP requereix un pas previ de connexió entre el client i el servidor per tal de comunicar-se. Un cop establerta la connexió, TCP garanteix que les dades arribin a l’altre extrem o indicarà que s’ha produït un error.
En general, els paquets que han de passar en l'ordre correcte, sense pèrdues, utilitzen TCP, mentre que els serveis en temps real on els paquets posteriors són més importants que els paquets més antics utilitzen UDP. Per exemple, la transferència d’arxius requereix una precisió màxima, de manera que normalment es fa mitjançant TCP, i la conferència d’àudio es fa freqüentment a través d’UDP, en què pot ser que no es notin les interrupcions momentànies.
TCP necessita uns paquets de control per a establir la connexió en tres fases: SYN, SYN + ACK i ACK. Cada paquet enviat es contesta amb un ACK. I finalment, es produeix una desconnexió des de les dues bandes amb FIN + ACK i ACK.
UDP, en canvi, només transmet els paquets de petició / resposta, sense cap control sobre la transmissió.
A continuació es pot veure la visualització del protocol ECHO amb Wireshark, tant per a la implementació TCP com la UDP.
Java té dues classes del paquet java.net que ho permeten:
Socket: implementació del sòcol client, que permeten comunicar dos programaris a la xarxa.ServerSocket: implementació del sòcol servidor, que permet escoltar peticions rebudes des de la xarxa.El funcionament es reflecteix en les següents dues imatges: primer el client demana una connexió, i després el servidor l'accepta, i s'estableix.
Aquest servidor escolta línies de text i retorna la versió en majúscules.
La comunicació comença amb la petició de connexió del client, i l'acceptació del servidor. Aquestes dues accions creen un sòcol compartit del tipus Socket, sobre el qual, tan el client com el servidor, poden utilitzar els mètodes:
getInputStream()getOutputStream()El client pot enviar cadenes de text, que el servidor convertirà a majúscules.
El protocol que ens hem inventat estableix que la comunicació s'acaba quan el client envia una línia buida. a la qual el servidor contesta amb un comiat.
Aquest podria ser un codi del servidor que implementa el protocol descrit utilitzant TCP.
ServerSocket serverSocket = new ServerSocket(PORT);Socket clientSocket = serverSocket.accept();PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));out.println("hola!"); String text;while ((text = in.readLine()).length() > 0) out.println(text.toUpperCase());out.println("adeu!"); in.close();out.close();clientSocket.close();serverSocket.close();Pots provar-ho mitjançant la comanda netcat (nc).
Com seria el protocol d'aquest servei? En pseudocodi:
A continuació, es pot veure un client que accedeix a aquest servei, implementant aquest protocol.
Socket clientSocket = new Socket(HOST, PORT);PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); String salutacio = in.readLine();System.out.println("salutacio: " + salutacio);for (String text: new String[]{"u", "dos", "tres"}) { out.println(text); String resposta = in.readLine(); System.out.println(text + " => " + resposta);}out.println();String comiat = in.readLine();System.out.println("comiat: " + comiat);in.close();out.close();clientSocket.close();Amb UDP no hi ha connexió: simplement s'envien paquets (Datagrames) amb destinació un servidor UDP. Si volem respondre, cal conèixer l'adreça i port destí, que pot obtenir-se del paquet rebut.
Un servidor es pot crear amb:
DatagramSocket socket = new DatagramSocket(PORT)El client funciona exactament igual, però el socol es crea amb:
DatagramSocket socket = new DatagramSocket();Per rebre un paquet de mida màxima MIDA:
byte[] buf = new byte[MIDA];DatagramPacket paquet = new DatagramPacket(buf, MIDA);socket.receive(paquet);// dades a paquet.getData() i origen a paquet.getAddress() i paquet.getPort()Per enviar un paquet al servidor:
InetAddress address = InetAddress.getByName(HOST);paquet = new DatagramPacket(buf, result, MIDA, address, PORT);socket.send(paquet);Per enviar un paquet de resposta a un client, hem d'utilitzar el port que hi ha al paquet que ens ha enviat prèviament:
InetAddress address = paquetRebut.getAddress();int port = paquetRebut.getPort();DatagramPacket paquetResposta = new DatagramPacket(buf, buf.length, address, port);socket.send(paquet);Com podem fer que els servidors acceptin peticions concurrents de diversos clients?
Ho vam veure a la UF "Processos i fils". Caldria atendre cada petició a un fil diferent. Per exemple, per al cas de TCP:
Executor executor = Executors.newFixedThreadPool(NFILS);ServerSocket serverSocket = new ServerSocket(PORT);while (true) { final Socket clientSocket = serverSocket.accept(); Runnable tasca = new Runnable() { public void run() { atendrePeticio(clientSocket); } }; executor.execute(tasca);}D'aquesta manera, no fem esperar nous clients quan atenem un.
Basada en les llibreries NIO. Veure aquest post.
Una aplicació pot veure's com quatre components: les dades, la lògica d'accés a dades, la lògica de l'aplicació i la presentació. Aquests components es poden distribuir de moltes formes:
L'arquitectura client / servidor més habitual, i per tant la que ens mereix més atenció, on hi ha principalment dos models:
El món està cada cop més interconnectat mitjançant APIs que proporcionen serveis. Aquests poden ser públics, per tal d'afegir valor al negoci d'una empresa.
Les APIs es diuen que són gestionades quan tenen un cicle de vida ben definit:
CREADA ➡ PUBLICADA ➡ OBSOLETA ➡ RETIRADA
Només es publiquen un cop estan ben documentades, amb les seves regles de qualitat d'ús, com la limitació d'ús.
Exemple: API Twitter
El protocol HTTP s'implementa a sobre de TCP, habitualment al port 80. Això ens permet implementar un servidor HTTP utilitzant sòcols.
Per escriure el servidor, hem de ser capaços de llegir una petició HTTP i de respondre.
request = Request-Line *(<message-header>) CRLF [<message-body>]La Request-Line té el format:
Request-Line = Method URI HTTP-VersionEls mètodes més habituals són GET/POST. La versió, HTTP/1.1. La URI és només la part del camí (path) absolut.
Els headers més habituals són:
El message-body està buit per al mètode GET, i conté els camps d'un formulari per al mètode POST.
Quan és POST, s'envia el header Content-Type amb els valors:
URLEncoder.encode(query, "UTF-8").response = Status-Line *(<message-header>) CRLF [<message-body>]El Status-Line té el format:
HTTP-Version Status-Code Reason-PhraseEls codis d'estat ja els vam veure. La Reason-Phrase és un text llegible que explica el codi.
Els headers més habituals són:
text/html; charset=UTF-8.El message-body té el contingut del recurs que s'obté.
Les cookies són un mecanisme que permet emmagatzemar parelles clau/valor al navegador des d'un servidor HTTP. Es pot utilitzar per diferents propòsits, per exemple, per identificar una sessió d'un usuari, o bé per seleccionar una preferència de visualització, com pot ser l'idioma.
Hi ha dues capçaleres associades a aquest mecanisme:
Per a esborrar una cookie, només cal enviar la cookie buida amb una data al camp "expires" antiga:
Exemple GET/POST d'un formulari HTML: la URI /form mostra un formulari, que s'omple i processa la URI /submit.
Exemple de cookie: la URI /page1 emmagatzema una cookie, que després està disponible a altres pàgines.
Exemple de redirecció: la URI /page1 es redirecciona a /page2.
Veurem dos tipus de protocols: un protocol stateful basat en text sobre TCP i un altre stateless basat en JSON/XML sobre HTTP.
SMTP és un protocol que funciona al port 25, sobre TCP. El client envia comandes, i el servidor respon amb un codi d'estat.
A continuació, veiem una conversa (C: client / S: servidor). Tota aquesta conversa es manté sobre una connexió oberta.
C: <client connects to service port 25>C: HELO snark.thyrsus.com la máquina que envia s'identificaS: 250 OK Hello snark, glad to meet you el receptor acceptaC: MAIL FROM: <esr@thyrsus.com> identificació de l'usuari que enviaS: 250 <esr@thyrsus.com>... Sender ok el receptor acceptaC: RCPT TO: cor@cpmy.com identificació del destíS: 250 root... Recipient ok el receptor acceptaC: DATAS: 354 Enter mail, end with "." on a line by itselfC: Scratch called. He wants to shareC: a room with us at Balticon.C: . final de l'enviament multi-líniaS: 250 WAA01865 Message accepted for deliveryC: QUIT l'emissor s'acomiadaS: 221 cpmy.com closing connection el receptor es desconnectaC: <client hangs up>REST (Representional State Transfer) és un estil d'arquitectura per a sistemes distribuïts. Permet establir comunicació d'aplicacions amb serveis proporcionats a la web. Per tal que una interfície es pugui anomenar RESTful, ha de cumplir una sèrie de principis:
Encara que no és obligatori, un servei RESTful sovint utilitza HTTP com a protocol. En aquest cas, els cos de les peticions i les respostes solen tenir el format XML o bé JSON.
Si ens fixem en les operacions CRUD habituals, hi ha una convenció de com utilitzar els mètodes HTTP utilitzant els codis d'estat 200, 201, 204, 400, 404:
Com que el protocol és sense estat (stateless), la autenticació/autorització ha de produir-se per cada petició. Les pràctiques recomanades inclouen utilitzar canals segurs, i no exposar mai dades a la URL. També es recomana l'ús d'Oauth.
L'ús de tokens, o paraules d'accés, és habitual als sistemes d'autenticació. El funcionament amb token és el següent: