6.05.2017

Web Service – JAX-RS, что нужно знать об RESTful.



Давайте в этой статье мы рассмотрим технологию web service JAX-RS,  которая позволяет нам при помощи аннотаций превращать наш класс в RESTful веб службу, а потом  с помощью протокола HTTP формировать запросы и ответы. Я как обычно буду использовать сервер приложений wildfly-8.2.0.Final, в него встроены библиотеки apache cxf. Основной протокол по которому контактирует наш веб сервис это HTTP, по этому нам нужно собрать проект как web application, т. е. в файл с расширением *.war и соблюсти правильность развертывания проекта для сервера приложений wildfly. Я думаю надо рассказать  как появился JAX-RS,  с начало был протокол JAX-WS (Web service). У протокола JAX-WS есть параметр action который помогал выбирать и выполнять определенную операцию (operation) , т.е. посредством параметра action  мы могли выбирать одну из функций java. Функции обычно подразделялись на просмотр данных, удаление данных, добавление новых данных или редактирование информации, в простонародье это называется CRUD  операции Create,  Read, Update, Delete. Имена параметров Action при этом могли выбираться случайно, не было стандарта именования.  Как мы все знаем, web серверы используют протокол HTTP, и у него  есть 8 стандартных основных операций: GET, PUT, DELETE, POST, HEAD, OPTION, CONNECT,TRACE. Вот ими и предложили заменить параметр action. Теперь был стандарт протокола и каждой основной операции протокола HTTP, сопоставили одну из операций удаления, вставки, создания или редактирования, так появился стандарт JAX-RS или как его еще называют RESTful веб служба.  GET, HEAD используются при чтении, DELETE при удаление, PUT  вставка или изменение записей, POST  при вставке, при этом могут задействоваться такие механизмы как параметры cookies, параметры сессии, параметры запроса http GET, параметры заголовка Header  и т.д. Приступим к реализации простого приложения.  Для работы RESTful  веб службы нам нужно перенаправить все наши HTTP запросы на сервлет javax.ws.rs.core.Application. Посредством этого сервлета мы будем вызывать наши операции. Добавим в файл web.xml сервлет, в примере сервлет помечен желтым цветом:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
  <display-name>Web Application</display-name>

     <servlet-mapping>
        <servlet-name>javax.ws.rs.core.Application</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>120</session-timeout>
    </session-config>
</web-app>

   Надо сделать небольшое отступление и сказать пару слов о RESTful  веб службе, есть описания спецификаций стандарта JSR  и несколько известных реализации его на языке Java. По умолчанию эталонной считается реализация Metro  в сервере приложений Glassfish, но так же есть еще две реализации от Apache  это CXF  и Axis2. Я буду пользоваться  библиотеками  CXF,  собираю приложение посредством gradle с такой зависимостью CXF + зависимости EJB:
dependencies {
    providedCompile group: 'org.apache.cxf', name: 'cxf-rt-rs-client', version: '3.1.10'
    providedCompile 'javax:javaee-web-api:6.0'
}

Напишем интерфейс RESTful  веб сервиса Hello.java,  аннотацией @Path мы указываем путь куда будем размещать наш сервис, аннотация @GET подсказывает нам каким запросом протокола HTTP  мы сможем получить доступ к нашей операции веб сервиса(функции java) :
package com.lopanov;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path(value = "/rest")
public interface Hello {
    @GET
    @Path("/hello")
    public String getPerson();
}

И реализуем наш интефейс в классе HelloImpl:

package com.lopanov;

public class HelloImpl implements Hello  {

    @Override
    public String getPerson() {
        return "Hello";
    }

}

Собираем все в  файл hello-1.0.war  и помещаем его в каталог JBOSS_HOME/Standalone/deployments, как устанавливать и запускать wildfly я писал в одной их прошлых статей, после деплоя,  в логах сервера wildfly  появятся строчки примерно такого вида:

18:47:27,758 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-8) JBAS015876: Starting deployment of "hello-1.0.war" (runtime-name: "hello-1.0.war")
18:47:28,148 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-1) JBAS017534: Registered web context: /hello-1.0
18:47:28,288 INFO  [org.jboss.as.server] (DeploymentScanner-threads - 2) JBAS018559: Deployed "hello-1.0.war" (runtime-name : "hello-1.0.war")
18:47:36,244 INFO  [org.hibernate.validator.internal.util.Version] (default task-1) HV000001: Hibernate Validator 5.1.3.Final

И так проверим наш сервис, загрузите свой любимый web браузер и перейдите по ссылке http://127.0.0.1:8080/hello-1.0/rest/hello вы получите строку Hello.  Url  формируется путем сложения адреса серавера (http://127.0.0.1:8080), контекста web  приложения (/hello-1.0) и двух путей (@Path(value = "/rest") ) и  (@Path("/hello")) . Для того чтобы посмотреть что же происходит внутри протокола HTTP  загрузите и установите утилиту SoapUI, создайте  REST  проект, создайте GET запрос http://127.0.0.1:8080/hello-1.0/rest/hello, и выполните его. Вы получите две RAW вкладки с расшифровкой протокола HTTP. Смотрите рисунок, на вкладке  1 запрос нашей службы, на вкладке 2 ответ от нее.
 



Продолжим, есть еще один способ написать rest веб службу, если вам не нравиться возиться с файлом web.xml нужно
 просто расширить класс javax.ws.rs.core.Application. Плюс добавим нововведение, напишем новую службу которая будет 
интегрироваться с EJB. Реализуем наш класс HelloApplication.java, аннотацией @ApplicationPath(value = "rest") укажем путь
 для сервлета куда будет развертываться наше приложение:
 
package com.lopanov;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath(value = "rest")
public class HelloApplication extends Application{
}
 
Реализуем наш EJB с двумя методами которые будут нам возвращать типы данных xml и json.
Тип возвращаемой инфорамации указывается в аннотации @Produces({"application/json"}) для json 
 и @Produces({"application/xml"})для xml:
 
package com.lopanov;

import javax.ejb.Stateless;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

@Stateless
@Path
("/")
public class HelloEJB {
   
@GET @Path("/json") @Produces({"application/json"})
   
public String getJsonEjb() {
       
return new String("{\"result\":\""+"Hello Json"+"\"}");
    }


    @GET @Path("/xml") @Produces({"application/xml"})
   
public String getXmlEjb() {
       
return new String("<xml><result>" +"Hello Xml"+"</result></xml>");
    }
}
Соберем наше приложение wild_hello-1.0.war, поместим его в каталог JBOSS_HOME/Standalone/deployments для деплоя приложения, как появятся строчки в логе сервера
...
14:54:40,366 INFO  [org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeploymentUnitProcessor] (MSC service thread 1-6) JNDI bindings for session bean named HelloEJB in deployment unit deployment
 "wild_hello-1.0.war" are as follows:

        java:global/wild_hello-1.0/HelloEJB!com.lopanov.HelloEJB
        java:app/wild_hello-1.0/HelloEJB!com.lopanov.HelloEJB
        java:module/HelloEJB!com.lopanov.HelloEJB
        java:global/wild_hello-1.0/HelloEJB
        java:app/wild_hello-1.0/HelloEJB
        java:module/HelloEJB

...

14:54:41,458 INFO  [org.jboss.resteasy.spi.ResteasyDeployment] (MSC service thread 1-4) Deploying javax.ws.rs.core.Application: class com.lopanov.HelloApplication
14:54:41,474 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-4) JBAS017534: Registered web context: /wild_hello-1.0

Можно считать, что удачно у нас развернулись ejb  и web приложение, нужно сказать пару слов о ejb бинах, RESTful сервисы могут быть описаны только в @Statless  и @Singleton бинах. И так наберите путь в браузере http://127.0.0.1:8080/wild_hello-1.0/rest/xml для получения xml документа или http://127.0.0.1:8080/wild_hello-1.0/rest/json для получения json документа. Осталось только написать два простеньких клиента на java, которые будут использовать наши RESTful сервисы. Первый клиент напишем посредством класса javax.ws.rs.client.Client который входит в стандарт JAX-RS:
 
package com.lopanov;

import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.Response;

public class Main {

    public static void main(String[] args) {
        //подготавливаем нашего клиента 
        Client client = ClientBuilder.newBuilder().newClient();
        WebTarget target = client.target("http://127.0.0.1:8080/hello-1.0/rest/hello");
        Invocation.Builder builder = target.request();
        Response response = builder.get();//выполняем запрос GET
        String s = response.readEntity(String.class);//получим результат
        System.out.println(response.getStatus());//выведем код возврата протокола HTTP который нам возвратился
        System.out.println(" s = " + s);//выведем результат работы RESTful сервиса
        client.close();// закрываем соединение, если этого не сделать соединение по протоколу tcp не будет закрыто
    }
}
 
выполнив эту программу мы получим результат:
 
200
s = Hello 
Process finished with exit code 0
 
Напишем код стандартными средствами java, при помощи которого получим доступ к xml документу:
 
package com.lopanov;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

public class Main1 {
   
public static void main(String args[]) {
       
new Main1();
    }

   
public Main1() {
       
try {
            URL url =
new URL("http://127.0.0.1:8080/wild_hello-1.0/rest/xml");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod(
"GET");
            connection.setDoOutput(
true);
            connection.setRequestProperty(
"Accept", "Application/xml");
            
if (connection.getResponseCode() != 200) {
               
throw new RuntimeException();
            }
            System.
out.printf("%s\r\n", connection.getContentType());
            BufferedReader br =
new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String str = br.readLine();
           
while (str != null) {
                System.
out.printf("%s\r\n", str);
                str = br.readLine();
            }
            connection.disconnect();
        }
catch (MalformedURLException e) {
            e.printStackTrace();
        }
catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 
Думаю, комментировать код не имеет смысла, здесь мы просто открываем соединение к RESTful веб сервису, получаем результат из потока чтения и выводим его  на экран:
application/xml
Hello Xml

Process finished with exit code 0
Подведем итоги. В статье я показал вам, как можно развернуть RESTful веб сервисы двумя способами, показал каким образом вы можете выбрать тип возвращаемой информации xml или json, и в конце мы написали два простеньких клиента которые обращаются к нашим RESTful веб сервисам. В скользь я упомянул про утилиту SoapUI, которой можно пользоваться для быстрого тестирования сервисов.