EDITING BOARD
RO
EN
×
▼ BROWSE ISSUES ▼
Issue 4

RESTful Web Services folosind Jersey

Tavi Bolog
Development Lead at Nokia
PROGRAMMING

RESTful Web Services sunt servicii web bazate pe metodele HTTP și conceptul de REST. De obicei următoare patru metode HTTP sunt folosite în definirea serviciilor RESTful:

  • POST: upload-ul unei noi resurse (creare sau modificare). Execuții repetate pot avea efecte distincte.
  • PUT: crearea unei noi resurse. Execuții repetate vor avea același efect ca și o singură execuție IDEMPOTENT.
  • GET: cititrea unei resurse fără a modifica resursa. Operația nu trebuie să fie folosită la creare de resurse.
  • DELETE: stergerea unei resurse. Execuții repetate vor avea același efect ca și o singură execuție IDEMPOTENT.

Conceptul de REST a fost introdus pentru prima dată în 2000, de Roy Fielding. Mai jos sunt câteva aspecte importante legate de REST:

  • Este un stil architectural simplu bazat pe standarde Web și HTTP (câștigând teren în fața unor model e ca și SOAP)
  • Este o arhitectură client-server bazată pe folosirea resurselor. Exemplu de resurse: carte, produs, mașină, etc.
  • Comparativ cu SOAP, REST nu este foarte strict cu tipurile de date, este mai ușor de citit folosind substantive și verbe și este mai puțin "verbose"
  • Tratarea erorilor se face conform tratării erorilor în protocolului HTTP.
  • Este o arhitectură scalabilă datorită separării de responsabilități între client și server. De exemplu, responsabilitatea unui client este să mențină starea unui utilizator, în timp ce serverul nu menține nici o stare, dar este resposabil de managementul datelor. De asemenea între request-uri serverul nu trebuie să se mențină starea clientului (stateless).
  • Clienții pot folosi tehnici de caching pentru creștrea performanței și scalabilității unei soluției.
  • Partea de server poate să conțină mai multe nivele, fără ca modificări ale acestora să afecteze în vreun fel clienții. Aceasta abordare ar putea duce la o separare de responsibilități a diferitelor nivele, ca de exemplu: load balancing, securitate, stocarea datelor, caching de resurse.
  • Resursele pot avea diferite reprezentări (JSON, XML, definit de user, etc), folosirea lor determinându-se conform protocolului HTTP (de exemplu folosind HTTP Headers)

Un serviciu RESTful definește, în mod tipic, URI-ul de bază (de exemplu: http://myserver.com/myresources), MIME-typurile pe care le consumă/produce (de exemplu: JSON, XML, definit de user, etc) și operațiile asociate (prezentate anterior).

Java și RESTful Web Services

În Java, suportul pentru servicii RESTful se numește Java API for Restful Web Services (JAX-RS) și este definit de JSR 311. JAX-RS se bazează foarte multe pe annotations după cum se va vedea în exemplul de mai jos. Jersey este o implementare open source a JAX-RS, care are calitate de producție și pe care am folosit-o în proiectele mele. Exemplul de mai jos a fost implementat folosind Jersey. De menționat că JAXB este folosit pentru suportul de JSON și XML al JAX-RS. Alte implementări ar fi: Apache CXF, Restlet, JBOss RESTEasy.

Implementarea unui client-server folosing Jersey

Să încercăm să construim o mică aplicație folosind Jersey. Aplicația va oferi o interfață pentru managementul unei resurse numite "books". Totodată voi prezentă o modalitate de a folosi serviciul Web creat, folosind suportul de client oferit to de Jersey.

Partea de server va expune o serie de metode folositoare în a administra resursa "books": reset, get, list, delete și add.

Partea de client va fi creată sub forma unui JUnit și va expune o serie de metode de test pentru a valida funcționalitatea serviciul Web.

Crearea proiectului

Proiectul a fost implementat folosind SpringSource Tool Suite (incluzând Web Serverul WMware vFabric), openJDK 7 si Maven 3 pe Ubuntu 12. Jersey are nevoie de patru librarii (și dependințele acestora):

  • jersey-server: conține implementarea de jersey pe partea de server
  • jersey-json: conține suportul de JAXB pentru Jersey
  • jersey-servlet: conține suportul pentru containere de tip servlet
  • jersey-client: conține implementarea de jersey pe partea de client

Folosirea Jersey client împreună cu Jersey server nu este o cerință absolută conform definiției Restful WebServices (s-a folosit abordarea aceasta doar pentru a exemplifica mai multe aspecte ale Jersey).

Maven și pom.xml

Maven oferă o modalitate built-in de rezolvare a dependințelor acestor librării deci este recomandată folosirea acestui tool. Pentru detalii, vezi: https://github.com/tavibolog/TodaySoftMag/blob/master/pom.xml .

Web.xml

Orice aplicația care va fi instalată într-un container de tip servlet va avea nevoie de un fisier web.xml de configurare. Aici se va specifica servlet-ul de care Jersey are nevoie pentru a se inițializa și totodată calea spre serviciile Restful oferite, se specifică pachetul Java ca valoare a parametrului com.sun.jersey.config.property.packages:

 

 

 

Jersey REST Service

com.sun.jersey.spi.container.servlet.ServletContainer

com.sun.jersey.config.property.packages

com.todaysoftmag.examples.rest

1

Jersey REST Service

/*

 

Servlet mapping specifică pattern-ul de URL pe care se va apela servlet-ul Jersey. Pentru un URL de forma:

 

http://://

//

maparea se referă la . Dacă în URL pattern folosim "/*", atunci requestul va fi de genul:

 

http://:///

Dacă în URL pattern folosim de examplu "/home", atunci requestul va fi de genul:

 

http://://home//

Definirea clasei domeniu

Clasa domeniu definește structura obiectelor folosite de către serviciile Web. În principiu clasele domeniu sunt POJO.

Annotările @XmlRootElement și @XmlElement sunt necesare pentru ca JAXB să poată fi folosit să mapeze automat date în formate XML sau JSON la POJO și vice-versa. Acest support este oferit de către JAX-RS. În cazul în care clasa domain (Book în cazul nostru) definește un constructor, atunci și constructorul default trebuie definit, el fiind folosit de JAXB în procesul de mapare.

@XmlRootElement

public class Book {

@XmlElement(name = "id")

String id;

@XmlElement(name = "name")

String name;

@XmlElement(name = "author")

String author;

}

Codul complet al clasei domeniu este aici: https://github.com/tavibolog/TodaySoftMag/blob/master/src/main/java/com/todaysoftmag/examples/rest/Book.java.

Implementarea serviciului

După cum am menționat anterior, JAX-RS folosește din plin annotări. Majoritatea annotărilor se găsesc în pachetele: javax.ws.rs.* și javax.ws.rs.code.* din dependința jersey-core. Să încercăm să le discutăm pe cele mai importante.

Pentru definirea serviciului este suficient să definim o clasă care se află în pachetul Java definit în web.xml de parametrul com.sun.jersey.config.property.packages. În cazul nostru va fi com.todaysoftmag.examples.rest.

 

package com.todaysoftmag.examples.rest;

// importurile sunt omise

@Path("/books")

public class BookService {

}

 

După cum observați definiția clasei este precedată de o annotare: @Path. Aceasta definește calea spre serviciul Web. Dacă folosim exemplu de mai sus, URL-ul va fi:

 

http://://books/

De asemenea annotarea @Path este folosită și la nivelul metodelor expuse de serviciul Web:

 

@PUT

@Path("/add")

@Consumes(MediaType.APPLICATION_JSON)

@Produces(MediaType.APPLICATION_JSON)

public Book add(Book book) {

….

}

În acest caz, valoarea definită in annotare se va adăuga ca parte a URL-ul serviciului pentru metoda respectivă. În cazul nostru, pentru a adăuga o "carte", URL-ul va fi:

http://://books/add

Această metodă mai este precedată de o serie de annotări:

  • @PUT (similară cu @GET, @POST, @DELETE): specifică metoda serviciului ("add") va răspunde la un request HTTP PUT (sau similar HTTP GET, HTTP POST, HTTP DELETE)
  • @Consumes: definește ce MIME type poate fi consumat de metoda respectivă. În acest caz, metoda "add" va accepta "application/json". Este posibil ca o metodă să accepte mai multe MIME type-uri care pot fi separate prin "," când sunt declarate în annotare.
  • @Produces: definește ce MIME type poate fi produs de metoda respective. În acest caz, metoda "add" va produce "application/json". Este posibil ca o metodă să producă mai multe MIME type-uri care pot fi separate prin "," când sunt declarate în annotare.

Există cazuri în care dorim să trimitem parametri ca și parte a URL-ului de request. JAX-RS definește o serie de annotări care pot fi folosite în aceste sens. Cele mai uzuale sunt:

  • @PathParam: folosită pentru a injecta valori din URL-ul de request în parametri metodei unui serviciu. În exemplu de mai jos, dacă URLul de request este http://://books/delete/1, metoda "delete" a serviciului va primi "1" ca valoare a parametrului String id. Această annotare supportă o annotare aditională: @DefaultValue("valoare") care poate fi folosită ca un place-holder implicit în cazul în care URL-ul de request nu conține patternul respectiv.

 

@DELETE

@Path("/delete/{id}")

public Book delete(@PathParam("id") String id) {

}

  • @QueryParam: folosită pentru a injecta parametri de request în parametri metodei unui serviciu. În exemplul de mai jos, dacă URLul de request este http://://books/list?s=abc , metoda "list" a serviciului va primi "abc" ca valoare a parametrului String search. În cazul în care parametrul "s" nu este specificat în request, metoda va primi "" ca valoare a parmetrului String search, datorită folosirii annotării @DefaultValue.

 

@GET

@Path("/list")

@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_XML})

public Book[] list(@QueryParam("s") @DefaultValue("") String search) {

}

  • @HeaderParam: folosită pentru a injecta parametri din HTTP headerul unui request în parametri unui serviciu. Mai jos este un exemplu de folosire. În cazul în care header-ul nu este trimis, valoarea parametrului se va evalua de la "null" înspre valoarea default a tipul parametrului metodei serviciului (e.g null petru String, 0 pentru int sau false pentru boolean).
  • @CookieParam: folosită pentru a injecta HTTP cookie-urile în parametric unui serviciu. Mai jos este un exemplu de folosire. În cazul în care cookie-ul nu este trimis, valoarea parametrului se va evalua de la "null" înspre valoarea default a tipul parametrului metodei serviciului ca și mai sus.

 

 

 

@POST

@Path("/reset")

public Response reset(@HeaderParam("clearOnly") boolean clearOnly , @CookieParam("credentials") String credentialsȘ) {

}

Codul complet al serviciului "Books" este aici: https://github.com/tavibolog/TodaySoftMag/blob/master/src/main/java/com/todaysoftmag/examples/rest/BookService.java

Implementarea clientului

Există diferite modalități de implementare a unui client rest: folosind pagini HTML, folosing Apache HTTP Client, folosing Jersey Client, etc. in acest articol se va prezenta folosirea clientului Jersey.

Configurarea unui client Jersey se face folosind interfața com.sun.jersey.api.client.config.ClientConfig. Configurarea clientului se referă la nume de proprietăți, funcționalități, etc. care vor putea fi folosite de către clientul Jersey. Jersey oferă o implementare default a interfeței, numită DefaultClientConfig. O inițializare a configurării clientul Jersey este foarte simplă:

 

ClientConfig config = new DefaultClient

Config();

Pasul următor este crearea clientului Jersey (clasa com.sun.jersey.api.client.Client). Este important de reținut că această operație este foarte costisitoare și necesită resurse considerabile. De aceea este recomdată refolosirea unui client Jersey.

 

Client client = Client.create(config);

Odată ce avem clientul Jersey va trebui să ne creăm un obiect WebResource (clasa com.sun.jersey.api.client.WebResource). Acesta ne va permite să executăm request-uri înspre metodele unui serviciu Web, oferind totodată capabilități de procesare a răspunsurilor.

 

WebResource resource = client.

resource(UriBuilder.fromUri("http://localhost:7000/TodaySoftMag").build());

Odată avut obiectul WebResource putem începe să îl folosim pentru a trimite request-uri. Veți identifica în exemplele de mai jos și folosirea patternul-ui Builder folosit pentru crearea request-urilor.

Să luam următorul exemplu de request și să îl explicăm:

 

Book book = resource.path("/books").path("/list").accept(MediaType.TEXT_HTML).get(Book.class);

  • Metoda "path" este folosită pentru a adauga un path URI-ului resurse Web. Dacă resursa Web este: http://localhost:7000/TodaySoftMag, adăugarea unui nou path o va transforma în http://localhost:7000/TodaySoftMag/books. Mai multe apeluri ale metodei path vor contribui la modificarea URI-ului resursei Web.
  • Metoda "accept" specifică media type-ul acceptat ca răspuns de WebResource. În cazul nostru este "text/html". Media type-urile accepate ar trebui să fie un subset al media type-urilor produse de serviciu Web și declarate de annotarea @Produces.
  • Metoda "get" execută un request HTTP GET și încercă să mapeze resultatul la o instanță a clasei Book.

Mai multe exemple găsiți în unit testul proiectului: https://github.com/tavibolog/TodaySoftMag/blob/master/src/test/java/com/todaysoftmag/examples/rest/BookServiceTest.java . Exemplele folosesc metodele WebResource prezentate mai sus, plus următoarele:

  • Metoda "post" folosită pentru a executa un HTTP POST;
  • Metoda "header" folosită pentru a adăuga un HTTP header requestului;
  • Metoda "cookie" folosită pentru a adăuga un cookie requestului;
  • Metoda "queryParam" folosită pentru a adăuga un query parametru requestului;
  • Metoda "put" pentru a executa un HTTP PUT;
  • Metoda "type" folosită pentru a seta content-type-ul requestului;
  • Metoda "delete" pentru a executa un HTTP delete.

 

În concluzie, consider că folosirea Jersey ca framework de dezvoltare și testare a serviciilor Restful este ușor de aprofundat, oferind developerilor un set extins de functionalități pentru dezvoltarea aplicațiilor.

Bibliografie

http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol

http://en.wikipedia.org/wiki/REST

http://jcp.org/aboutJava/communityprocess/final/jsr311/index.html

http://jersey.java.net/

 

Sponsors

  • comply advantage
  • ntt data
  • 3PillarGlobal
  • Betfair
  • Telenav
  • Accenture
  • Siemens
  • Bosch
  • FlowTraders
  • MHP
  • Connatix
  • UIPatj
  • MetroSystems
  • Globant
  • Colors in projects