Changes

Jax-rs 2.0

15,076 bytes added, 16:25, 26 April 2019
Service osztály definiálása
A második metódus (getBook) a ''GET:<app root>/rest/book/'' URL-en érhető el. Vár egy PATH paramétert a /book/ után, valamint egy title nevű QUERY paramétert, tehát a teljes request valami ilyesmi lesz: ''GET:<app root>/rest/book/12345?title=adam''
==Hibakezelés==
Ez egy sarkalatos pontja a REST interfész implementációnak. A @GET/POST/PUT annotációkkal ellátott metódusoknál csak azt tudjuk definiálni, hogy mi legyen a válasz típusa happy ágon, mikor a kérést rendeltetés szerűen ki tudta szolgálni a szerver, tehát a HTTP státusz kódja a válasznak a 200-as tartományba fog esni. Ha van visszatérési érték, akkor feltehetően ez 200 lesz, ha nincs, akkor 204, ezt automatikusan kezeli a JAX-RS.
===Path paraméterek kezelése===
<br>
===Query paraméterek kezelése===
<br>
 
===Header paraméterek kezelése===
 
<source lang="java">
@GET
@Path("/book/")
public Book getBook(@HeaderParam("Authorization") String token) {
...
 
}
</source>
 
<br>
 
==Response manuális összeállítása==
A @GET/POST/PUT annotációkkal ellátott metódusoknál a visszatérési objektum típus meghatározására két lehetőségünk van.
 
* Ha rest metódusnak egy POJO a visszatérési értéke (vagy egy abból képzett típus) akkor sikeres futás estében, a metódus visszatérése után a JAX-RS ebből automatikusan REST választ fog generálni, a POJO-t JSON/XML-re fogja konvertálni, és be is állítja a HTTP státuszt, nekünk ezzel ebben az esetben semmi dolgunk. Viszont több okból is meg van kötve a kezünk: Ha a metódus sikeresen elfut, akkor a JAX-RS ezt mindig sikeres futásnak fogja tekinteni, és a HTTP státuszt mindig 200-ra fogja állítani, nem tudunk beavatkozni a válasz elkészítésébe.
<source lang="java">
@GET
@Path("/contract")
public Contract getContract() {
Contract contract = new Contract();
return contract;
}
</source>
 
* Ha a service metódusnak nem egy POJO a visszatérési értéke, hanem a javax.ws.rs.core.Response, akkor teljes kontrolunk van a válasz összeállításában, viszont mindent mind sikeres, mind sikertelen ágon nekünk kell kézzel a válaszba belerakni a megfelelő response JAVA objektumot és beállítani a HTTP státuszt. <br>
https://dennis-xlc.gitbooks.io/restful-java-with-jax-rs-2-0-en/cn/part1/chapter7/complex_responses.html
Alább láthatunk egy példát a teljesen manuális response összeállítására. Ha a header-t és azon belül a sütiket is állítani akarjuk, akkor muszáj ezt a megközelítést használni.
<source lang="java">
@GET
@Path("/contract2")
public Response getContract() {
 
Contract contract = new Contract();
ResponseBuilder builder = Response.ok(contract);
builder.language("en").header("Some-Header", "some value").cookie(new NewCookie("adamCooke", "value"));
 
return builder.build();
}
</source>
A státuszt kétféle képen határozhatjuk meg A Resonse osztálynak többféle metódusa van, ami beállítja a státuszt: ok=200, created=204 ..., vagy mi állítjuk be kézzel: .status(int)
Viszont mi van akkor ha hiba történik a feldolgozásban. A kliensnek semmilyen körülmények közözz nem szabad egy java stack trace-t visszaadni, minden körülmények között egy szabályos REST választ kell visszaküldeni, ahol a HTTP státusz megfelelően ki van töltve, és lehetőleg egy egységesített ERROR objektumban benne van a hiba leírása.
* 400-as hibák: A feldogozás az input adatok miatt nem sikerült, pl hiányzik valami, vagy nincs joga a kliensek a kérést végrehajtania
* 500-as hibák: Az input paraméterek megfelelőek voltak, de a szerver oldalon nem várt hiba történt. (pl nem tudtunk egy szükséges háttérrendszerhez csatlakozni)
<source lang="java">
public Response getContract() {
...
return Response.status(Status.OK).type(MediaType.APPLICATION_JSON).build();
}
</source>
<br>==Hibakezelés== ===Áttekintés===Ez egy sarkalatos pontja a REST interfész implementációnak, ugyanis a kliensnek minden esetben vissza kell adni egy szabványos REST választ, ahol hiba esetén lehetőleg megmondjuk, hogy mi történt, tehát minden hibát le kell valahogy kezelni. Úgy fogjuk megoldani, hogy hiba esetén egyedi response objektumot dobjuk, hogy hiba esetén egy saját Exception-t dobunk, amit egy Exception provider-el feldolgozunk, és egyedi hiba objektumot készítünk belőle a válaszba.  Ahhoz hogy a hibaágakat is kezelni tudjuk, létre kell hozzunk egyedi Exception osztályokat, és hozzájuk tartozó response mapper-eket(Provider), amik az Exception eldobása estén megkapják a vezérlést, és az Exception alapján össze tudják állítani a választ. Ehhez három osztályt kell létrehozzunk:
# ErrorResponse osztály, egy egységesített struktúra, amit minden nem 200-as válaszban vár a kliens. (pl hibakódok, szöveges leírás)
# Saját Exception implementáció (WebServiceException), amibe minden olyan infónak szerepelnie kell, ami alapján az ErrorResponse osztály elkészíthető.
# Exception mapper provider osztály, ami megkapja a vezérlést ha WebServiceException dobódott, és a benne található információk alapján példányosítja a ErrorResponse osztályt, majd beleteszi a REST válaszba, amit a JAX-RS JSON formátumra fog hozni.
<br>
===Jax-RS provider típusok===
A JAX-RS-ben 3 típusa létezik a provider-ekenek, melyekkel három különféle feladatot láthatunk el:
* Entity provider
https://docs.huihoo.com/jersey/2.13/message-body-workers.html<br>
The Entity provider API contains 2 interfaces. One for handling inbound entity representation-to-Java de-serialization - '''MessageBodyReader<T>''' and the other one for handling the outbound entity Java-to-representation serialization - '''MessageBodyWriter<T>'''. A MessageBodyReader<T>, as the name suggests, is an extension that supports reading the message body representation from an input stream and converting the data into an instance of a specific Java type. A MessageBodyWriter<T> is then responsible for converting a message payload from an instance of a specific Java type into a specific representation format that is sent over the wire to the other party as part of an HTTP message exchange. Both of these providers can be used to provide message payload serialization and de-serialization support on the server as well as the client side.
* Context provider
https://www.logicbig.com/tutorials/java-ee-tutorial/jax-rs/context-resolver.html<br>
Context providers supply a context object to resource classes or to other providers.A context provider class must implement the ContextResolver<T>interface.
* Exception provider
These providers map a checked or runtime exception to an instance of Response. They implement ExceptionMapper<T>. Számunkra ez a lényeges. Itt fogjuk az exception-t
 
 
<br><br>
 
===Megvalósítás===
 
 
Elsőként definiáljuk azt az osztályt ami alapján a NEM 200-as státusz esetén a válasz objektumot létre akarjuk hozni. Létrehoztunk egy Facory metódust is a JSON<->JAVA konverzióra, amit '''@JsonCreator''' annotációval láttunk el (a metódusnak az 'of' nevet adtuk). A '''@JsonProperty'''-val megadtuk az egyez mezők JSON nevét és Jáva típusát.
<source lang="java">
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
 
public class ErrorMessageResponse implements Serializable {
 
private String correlationId;
private String message;
 
@JsonCreator
public static ErrorMessageResponse of
(@JsonProperty("correlationId") String correlationId,
@JsonProperty("messages") String message) {
return new ErrorMessageResponse(correlationId, message);
}
//Getters and setters and constructor...
}
</source>
A HTTP válaszba JSON alakban ez így fog kinézni:
<pre>
{"correlationId":"XXX", "message":"111"}
</pre>
 
 
 
Hozzunk létre egy egyedi Exception implementációt, amit minden kezelt hiba estén mi fogunk dobni a webservice metódusok belsejében. Ezt az exception-t fogja elkapni a mapper osztályunk, és alakítja majd át ErrorMessageResponse objektumra. Az exception-t származtassuk le az IllegalArgumentException-böl, és a konstruktorban töltsük ki az ős konstruktorát is.
<source lang="java">
public class WebServiceException extends IllegalArgumentException {
//HTTP status
private int status;
 
private String correlationId;
private String responseMessage;
 
 
public WebServiceException(String correlationId, String responseMessage, int status, String message, Throwable cause) {
super(message, cause);
...
}
 
//getters...
}
</source>
 
 
És végezetül nézzük meg az Exception provider osztályt aminek implementálni kell a ExceptionMapper<T> interfészt és a toResponse metódust, amivel az Exception alapján előállítjuk a válasz objektomt.
<source lang="java">
import javax.ws.rs.core.MediaType;
@Override
public Response toResponse(WebServiceException exception) {
ErrorMessageResponse emr = new ErrorMessageResponse(exception.getCorrelationId(), exception.getResonseMessage()); 
return Response.status(exception.getStatus())
.entity(exception.getErrorMessageResponse()emr)
.type(MediaType.APPLICATION_JSON).
build();
}
</source>
 
 
Használata:
<source lang="java">
@GET
@Path("/contract")
public Contract getContract() {
try {
Contract contract = new Contract();
} catch (Exception e) {
throw new WebServiceException("111", "service-error",404, e.getMessage(), e);
 
}
return contract;
}
</source>
 
A válaszban ez így fog megjelenni:
<pre>
response code: HTTP 404
response boy: {"correlationId":"111", "message":"service-error"}
</pre>
 
==Cookie kezelés==
https://www.logicbig.com/tutorials/java-ee-tutorial/jax-rs/cookie-param.html
 
 
=JAX-RS kliens=
https://dennis-xlc.gitbooks.io/restful-java-with-jax-rs-2-0-en/cn/part1/chapter8/building_and_invoking_requests.html
 
==Kliens bemutatása==
Maven dependecia:
<source lang="java">
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>2.28</version>
</dependency>
</source>
 
A JAX-RS kliens használata magáért beszél, elég intuitív. A ClientBuilder-el csinálunk egy globális kliens példányt, majd a klienssel készítünk sorban egy target-et, hozzáadunk path-t, request-et, metódust stb... A post(), put() metódusok két paramétert várnak, az első a request body objektum (ha van ilyen az interfészen), a második a válasz típusa. A get() csak egy paramétert vár, a válasz típusát. A JSON<->Java mappelést a JAX-RS automatikusan el fogja végezni.
 
 
===Request és Response megadása===
Nézzünk egy példát ahol a kliens meghívja a POST:login interfészt, a request body JSON stukturáját a LoginRequest objektum írja le, a választ pedig a LoginResponse.
<source lang="java">
public class LoginResponse {
private String user;
private String pass;
//getters and setters ...
}
 
public class LoginResponse {
private String userId;
private String token;
//getters and setters ...
}
</source>
 
Ekkor a request így fog kinézni:
<pre>
$ curl -XPOST 'localhost:8080/rest/login?' -H 'Content-Type: application/json' -d'
{ "user":"adam", "pass":"1234" }
</pre>
 
 
<source lang="java">
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
 
 
private static Client client = ClientBuilder.newClient();
 
LoginRequst loginRequest = new LoginRequest();
 
LoginResponse loginResponse = client.target(baseUrl).path("login").
request(MediaType.APPLICATION_JSON).post(Entity.json(loginRequest), LoginResponse.class);
</source>
 
A válaszban az alábbi JSON fog visszaérkezni:
<pre>
{ "userId":"11111", "token":"XXXXXX"}
</pre>
Amit a JAX-RS mappelni fog egy LoginResponse objektumba.
 
 
===Templét használata a request-ben===
A request URL-ben használhatunk PATH és request paramétereket is, amikhez a JAX-RS kliens behelyettesítő metódusokat biztosít. A PAHT paramétereket a resolveTemplate() metódussal, míg a QUERY paramétereket a queryParam() metódussal helyettesíthetjük be:
<source lang="java">
 
WebTarget target = client.target("http://commerce.com/customers/{id}")
.resolveTemplate("id", "123")
.queryParam("verbose", true);
 
</source>
 
 
===Listák kezelése a válaszban===
Ha az interfész válaszában egy POJO listát kapunk vissza, akkor a válasz típusának a megadását az alábbiak szerint kell megadni: ű
<source lang="java">
Client client = ClientBuilder.newClient();
List<Book> books = client.target(url)
.request(MediaType.APPLICATION_JSON).get(new GenericType<List<Book>>(){});
</source>
 
==Hibakezelés==
 
A hibák kezelésére három megközelítést fogunk alkalmazni, amik egybevágnak a server implementációnál tárgyalt megközelítésekkel.
# Exception kezelés: A get(), put(), post().. metódusoknál továbbra is megadjuk a válasz objektum típusát. 200 -as válasz esetében a JAX-RS automatikusan mappelni fogja a választ a megadott objektumba, így ha nem volt hiba, egy Java objektumban megkapjuk a választ. Ha bármilyen hiba dobódik az interfész hívása közben, akkor a JAX-RS kivétel típusából tudhatjuk, hogy milyen hiba történt. A kivételből ki tudjuk szedni a válasz body-ban esetlegesen küldött objektumot.
# A get(), post().. HTTP metódusokban nem mondjuk meg a válasz típusát. Ekkor a kliens a HTTP státusztól függetlenül egy '''javax.ws.rs.core.Response''' objektummal fog visszatérni ha volt hiba hanem. Ekkor a Response-ból nekünk kell kiszedni a válasz objektumot a '''readEntity(Class<T> entityType)''' metódussal és a HTTP státuszt a '''getStatus()''' metódussal.
# A harmadik megközelítés egy változata a másodiknak. Ahelyett, hogy a readEntity() hívással a JAX-RS -e bíznánk a parszolást, a JSON/XML String reprezentációját kérjük el a válaszból, és azt manuálisan parszoljuk.
 
 
===Exception kezelés===
https://dennis-xlc.gitbooks.io/restful-java-with-jax-rs-2-0-en/cn/part1/chapter7/exception_handling.html<br>
Talán ez a legtisztább megközelítés, megadjuk a HTTP metódusban a válasz objektum típusát, amit sikeres futás esetén (HTTP 200) meg fogunk kapni a megadott JAVA objektumban out-of-the-box.
 
 
<source lang="java">
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Response;
 
private static Client client = ClientBuilder.newClient();
 
LoginRequst loginRequest = new LoginRequest();
try {
LoginRequst loginRequest = client.target(baseUrl).path("login").
request(MediaType.APPLICATION_JSON).post(Entity.json(loginRequest), LoginRequst.class);
 
// 400-as hibák
} catch (ClientErrorException e) {
 
ErrorMessageResponse errorMessageResponse = e.getResponse().readEntity(ErrorMessageResponse.class);
int status = e.getResponse().getStatus();
 
// 500-as hibák
} catch (ServerErrorException e) {
...
// 300-as redirekt hibák
} catch (RedirectionException e) {
...
}
</source>
 
 
 
{| class="wikitable"
! style="font-weight:bold; background-color:#c0c0c0;" | Base Exception
! style="font-weight:bold; background-color:#c0c0c0;" | Status code range
! style="font-weight:bold; background-color:#c0c0c0;" | Description
|-
| ClientErrorException
| 4XX
| Client side error
|-
| ServerErrorException
| 5XX
| Server side error
|-
| RedirectionException
| 3XX
| Redirect
|}
 
 
 
{| class="wikitable"
! style="font-weight:bold; background-color:#c0c0c0;" | Exception
! style="font-weight:bold; background-color:#c0c0c0;" | Status code
! style="font-weight:bold; background-color:#c0c0c0;" | Description
|-
| BadRequestException
| 400
| Malformed message
|-
| NotAuthorizedException
| 401
| Authentication failure
|-
| ForbiddenException
| 403
| Not permitted to access
|-
| NotFoundException
| 404
| Couldn’t find resource
|-
| NotAllowedException
| 405
| HTTP method not supported
|-
| NotAcceptableException
| 406
| Client media type requested not supported
|-
| NotSupportedException
| 415
| Client posted media type not supported
|-
| InternalServerErrorException
| 500
| General server error
|-
| ServiceUnavailableException
| 503
| Server is temporarily unavailable or busy
|}
 
===Response objektum használata===
 
<source lang="java">
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Response;
 
private static Client client = ClientBuilder.newClient();
 
 
LoginRequst loginRequest = new LoginRequest();
 
Response response = client.target(baseUrl).path("login").
request(MediaType.APPLICATION_JSON).post(Entity.json(loginRequest));
 
int status = e.getResponse().getStatus();
 
if (status < 300) {
LoginResponse loginResponse = response.readEntity(LoginResponse.class);
 
} else {
ErrorMessageResponse errorMessageResponse = response.readEntity(ErrorMessageResponse.class);
}
</source>
 
 
===Response as String===
 
<source lang="java">
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.8</version>
</dependency>
</source>
 
<source lang="java">
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Response;
 
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
 
private static Client client = ClientBuilder.newClient();
private static ObjectMapper objectMapper = new ObjectMapper();
 
LoginRequst loginRequest = new LoginRequest();
 
Response response = client.target(baseUrl).path("login").
request(MediaType.APPLICATION_JSON).post(Entity.json(loginRequest));
 
String responseString = response.readEntity(String.class);
int status = e.getResponse().getStatus();
 
LoginResponse loginResponse = objectMapper.readValue(responseString,LoginResponse.class);
</source>