비동기 웹 서비스

본 장에서는 클라이언트의 비동기 오퍼레이션과 비동기 프로바이더를 이용한 비동기 웹 서비스 설정 방법에 대해서 설명한다.

1. 개요

서비스와 클라이언트 간의 웹 서비스 호출에서 클라이언트는 서버의 응답을 받을 때까지 그 스레드를 블록시키고 기다리고 있다. 이와 같은 비효율성을 개선하기 위해 JAX-WS 웹 서비스는 다음과 같은 비동기 오퍼레이션(Operation)을 구성한다.

  • 클라이언트 측면에서 JAX-WS API를 사용한 비동기 오퍼레이션

    JAX-WS 웹 서비스의 클라이언트를 구성하기 위해서는 호출할 서비스의 WSDL 파일을 이용한다. 서비스 WSDL 파일을 바인딩 사용자화 선언을 통해 정적인 비동기 메소드를 가진 서비스 Endpoint 인터페이스 Stub을 생성하고 그것을 구현하는 클라이언트 클래스를 구성함으로써 클라이언트의 비동기 오퍼레이션을 구성하는 방법이다.

  • 서비스 측면에서 JEUS가 제공하는 비표준적인 비동기 오퍼레이션

    웹 서비스 Endpoint를 Servlet 3.0의 비동기 처리 방식으로 동작하는 비동기 웹 서비스로 구성하는 방법이다. JAX-WS 표준은 서비스 측면의 비동기 오퍼레이션을 규정하고 있지 않다.

2. 클라이언트 비동기 오퍼레이션

본 절에서는 클라이언트 측면에서 JAX-WS API를 사용한 비동기 오퍼레이션에 대해 설명한다.

2.1. 비동기 메소드를 가진 SEI Stub 이용 방법

비동기화 wsdl:operation은 Polling과 Callback 메소드로 매핑된다. Polling 메소드는 jakarta.xml.ws.Response 인터페이스를 리턴하고, Callback 메소드는 jakarta.xml.ws.AsyncHandler 인터페이스를 리턴한다. 우선 서비스의 WSDL로부터 이러한 비동기 메소드를 가진 SEI(Service Endpoint Interface) Stub을 얻기 위해 사용하는 비동기화 바인딩 선언에 대해 알아본다.

2.1.1. 비동기화 바인딩 선언

WSDL에서 명시된 wsdl:operation element를 비동기화된 매핑으로 사용하기 위해서는 JEUS 웹 서비스의 wsimport와 같은 툴을 이용하여 비동기화 wsdl:operation 매핑에 기반한 SEI를 생성해야 한다.

본 절에서는 이러한 비동기화 wsdl:operation 매핑을 생성하는 방법에 대해 설명한다.

비동기화 wsdl:operation 매핑을 생성하기 위해서는 wsimport 툴에 의한 바인딩 사용자화 설정 작업을 해야 한다. 다음은 바인딩 설정 파일인 custom-schema.xml의 한 예이다.

비동기화 바인딩 : <custom-schema.xml>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<bindings xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    wsdlLocation="http://localhost:8088/AddNumbers/addnumbers?wsdl"
    xmlns="http://java.sun.com/xml/ns/jaxws">
    <bindings node="wsdl:definitions">
        <enableAsyncMapping>true</enableAsyncMapping>
    </bindings>
</bindings>

위와 같이 wsdl:definitions에 대해 바인딩을 설정하면 이 WSDL 문서 내의 모든 wsdl:operation element들에 대해 비동기화 설정이 된다.

다음은 custom-schema.xml 파일을 사용하는 build.xml의 한 부분이다.

비동기화 바인딩 : <build.xml>
...
<target name="build_client" depends="do-deploy-success, init">
    <antcall target="wsimport">
        <param name="package.name" value="async.client" />
        <param name="binding.file" value="-b ${src.conf}/custom-client.xml" />
        <param name="wsdl.file"
            value="http://localhost:8088/AddNumbers/addnumbers?wsdl" />
    </antcall>
    <antcall target="do-compile">
        <param name="javac.excludes" value="fromjava/server/" />
    </antcall>
</target>
...

이와 같이 바인딩 사용자 선언으로 wsimport 툴을 이용하여 Portable Artifact들을 생성하면 생성된 비동기 메소드들을 가진 SEI는 다음과 같다.

public int addNumbers(int number1, int number2)
    throws java.rmi.RemoteException;
public Response<AddNumbersResponse> addNumbers(int number1, int number2);
public Future<?> addNumbers(int number1, int number2,
                           AsyncHandler<AddNumbersResponse>);

위와 같이 Response<AddNumbersResponse>와 Future<?>를 리턴값으로 갖는 메소드가 2개 생성된다. 이는 각각 Polling 방식과 Callback 방식의 메소드인데 이들을 사용해서 클라이언트 Java 클래스를 어떻게 구성하는지에 대해 다음 절에서 설명한다.

2.1.2. 비동기화 클라이언트 구성

비동기화 클라이언트는 Polling 메소드를 사용하거나 Callback 메소드를 사용해서 구성할 수 있다.

Polling 메소드를 사용하는 클라이언트의 구성 방법

wsimport 툴로부터 얻은 비동기 SEI의 Polling 메소드는 다음과 같다.

public Response<AddNumbersResponse> addNumbers(int number1, int number2);

다음은 이러한 Polling 메소드로 매핑된 메소드를 사용하여 구현한 클라이언트 웹 서비스 예의 일부분이다. 클라이언트 애플리케이션은 SEI의 비동기 Polling 메소드를 호출하게 되고 언제 결과값이 반환되는지를 확인할 수 있다.

jakarta.xml.ws.Response<AddNumbersResponse> resp = port.addNumbersAsync(10, 20);
while(!resp.isDone()){
}
System.out.println(resp.get().getReturn());
...

위와 같이 Polling 메소드로 매핑된 메소드는 jakarta.xml.ws.Response 타입의 객체를 리턴한다. 이는 java.util.concurrent.Future<T>로부터 상속된 isDone() 메소드를 통해서 언제 이 오퍼레이션이 완료되어 결과를 반환하는지를 결정할 수 있다.

다음은 Polling 메소드를 지원하는 클라이언트 애플리케이션 예제 코드의 일부분이다.

Polling 메소드를 사용하는 클라이언트 : <AddNumbersClient.java>
public class AddNumbersClient {

   ...

   public static void main(String[] args) {
       try {
           AddNumbersImpl port = new AddNumbersService().getAddNumbersImplPort();

           // Asynchronous polling
           Response<AddNumbersResponse> resp = port.addNumbersAsync(10, 20);
           Thread.sleep(2000);
           AddNumbersResponse output = resp.get();
           System.out.println("#############################################");
           System.out.println("### JAX-WS Webservices examples - polling ###");
           System.out.println("#############################################");
           System.out.printf("call webservices in an Asynchronous Polling way...");
           System.out.printf("result : %d\n", output.getReturn());

           ...

       } catch (Exception e) {
           e.printStackTrace();
       }
   }

    ...

}
Callback 메소드를 사용하는 클라이언트의 구성 방법

wsimport 툴로부터 얻은 비동기 SEI의 Callback 메소드는 다음과 같다.

public Future<?> addNumbers(int number1, int number2, AsyncHandler<AddNumbersResponse>);

Callback 메소드로 매핑된 메소드는 클라이언트 개발자가 추가적인 파라미터로써 jakarta.xml.ws.AsyncHandler를 구현한 핸들러 객체를 제공한다.

다음은 AsyncHandler를 구현한 핸들러 객체의 예제 코드이다.

Callback 메소드 사용하는 클라이언트 핸들러 객체 : <AddNumbersClient.java>
public class AddNumbersClient {
    ...
    static class AddNumbersCallbackHandler implements
           AsyncHandler<AddNumbersResponse> {

        private AddNumbersResponse output;

        public void handleResponse(Response<AddNumbersResponse> response) {
            try {
                output = response.get();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        AddNumbersResponse getResponse() {
            return output;
        }
    }
}

위와 같이 추가적인 AsyncHandler를 구현한 핸들러 객체는 런타임에 서버로부터 그 웹 서비스 오퍼레이션의 결과를 얻을 수 있을 때 handleResponse라는 메소드를 호출하게 되고, 클라이언트 애플리케이션은 getResponse() 메소드를 통해 그 결과값을 얻을 수 있다.

다음은 위에서 구현한 핸들러 객체를 이용하여 SEI의 비동기 Callback 메소드를 이용하는 클라이언트 애플리케이션 예제 코드의 일부분이다. 클라이언트 애플리케이션은 SEI의 비동기 Callback 메소드를 호출하게 되고 언제 결과값이 반환되는지를 확인할 수 있다.

AddNumbersCallbackHandler callbackHandler = new AddNumbersCallbackHandler();
Future<?> resp = port.addNumbersAsync(number1, number2, callbackHandler);
while(!resp.isDone()){

}
System.out.println(callbackHandler .getResponse().getReturn());

위와 같이 Callback 메소드로 매핑된 메소드는 javax.util.concurrent.Future 타입의 객체를 리턴한다. 이는 isDone() 메소드를 통해서 언제 이 오퍼레이션이 완료되어 결과를 반환하는지를 결정할 수 있다.

다음은 SEI의 비동기 Callback 메소드를 이용하는 클라이언트 애플리케이션 예제 코드의 일부분이다.

Callback 메소드를 이용하는 클라이언트 : <AddNumbersClient.java>
public class AddNumbersClient {

  public static void main(String[] args) {
    try {
        AddNumbersImpl port = new AddNumbersService().getAddNumbersImplPort();
          ...

        // Asynchronous callback
        AddNumbersCallbackHandler callbackHandler = new AddNumbersCallbackHandler();
        Future<?> response = port.addNumbersAsync(10, 20, callbackHandler);
        Thread.sleep(2000);

        output = callbackHandler.getResponse();
        System.out.println("#############################################");
        System.out.println("### JAX-WS Webservices examples - callback ###");
        System.out.println("#############################################");
        System.out.printf("call webservices in an Asynchronous Callback way...");
        System.out.printf("result: %d\n", output.getReturn());
     } catch (Exception e) {
         e.printStackTrace();
     }
  }
    ...
}

2.1.3. 비동기화 클라이언트 실행

본 절에서는 구현한 클래스들 및 기타 설정 파일들을 가지고 핸들러 프레임워크를 실행하는 방법에 대해서 설명한다.

다음과 같이 비동기화 오퍼레이션을 설정한 웹 서비스를 생성하여 JEUS에 deploy한다.

$ ant build deploy

위의 과정이 모두 실행되어 서비스가 정상적으로 deploy되면 클라이언트를 빌드한다.

핸들러 프레임워크를 설정한 클라이언트를 생성하고 클라이언트로부터 서비스를 호출한다.

콘솔에 다음과 같이 입력하면 Polling 방식과 Callback 방식으로 2번의 응답를 정상적으로 받는 모습을 확인할 수 있다.

$ ant run

...

run:
     [java] #############################################
     [java] ### JAX-WS Webservices examples - polling ###
     [java] #############################################
     [java] call webservices in an Asynchronous Polling way...result : 30
     [java] #############################################
     [java] ### JAX-WS Webservices examples - callback ###
     [java] #############################################
     [java] call webservices in an Asynchronous Callback way...result: 30

...

BUILD SUCCESSFUL

2.2. 디스패치 인터페이스 이용하는 방법

디스패치 인터페이스를 이용하여 클라이언트의 비동기 오퍼레이션을 사용하는 것은 기본적으로 비동기 메소드를 가진 SEI Stub 이용하는 것과 개념은 동일하다. 다만 생성된 디스패치 객체에서 제공하는 invokeAsync 메소드를 사용한다.

다음은 invokeAsync 메소드를 사용하는 예제이다.

Response<T> response = dispatch.invokeAsync(T);
Future<?> response = dispatch.invokeAsync(T, AsyncHandler);

위에서와 같이 invokeAsync(T)는 Polling 방식의 비동기를 지원하는 메소드이고, invokeAsync(T, AsyncHandelr)는 Callback 방식의 비동기를 지원하는 메소드이다. AsyncHandler는 Callback 방식으로 사용하기 위해 사용자 핸들러를 구현한 것이다.

3. 비동기 웹 서비스

JEUS JAX-WS는 Servlet 3.0 비동기 처리 기반의 비동기 웹 서비스 구성 방법을 제공한다. 웹 서비스 Endpoint는 요청을 받고 응답을 보낼 때까지 동기화되어 서블릿 컨테이너가 할당한 요청 처리 스레드를 점유하고, 서비스에서의 처리 시간이 길어진다. 이로 인하여 다른 요청들이 대기하는 상황이 자주 발생할 수 있다. 이 경우 처리 시간이 긴 서비스에는 별도의 스레드를 할당하고, 요청 처리 스레드는 컨테이너에 반환하여 대기 요청을 효율적으로 처리하게 할 수 있다.

본 절에서는 비동기 웹 서비스의 설정방법에 대해서 설명한다.

3.1. 비동기 웹 서비스 설정

JEUS는 JAX-WS 웹 서비스를 비동기 웹 서비스로 구성하기 위해서는 다음과 같은 방법을 제공한다.

  • @jeus.webservices.jaxws.api.AsyncWebService Annotation을 사용하는 경우

    다음은 Annotation을 사용하여 웹 서비스를 비동기 웹 서비스로 설정하는 예제이다.

    비동기 웹 서비스 설정 : <AddNumbersImpl.java>
    @WebService(serviceName="AddNumbers")
    @AsyncWebService
    public class AddNumbersImpl {
    
        public int addNumbers(int number1, int number2) {
            return number1 + number2;
        }
    }
  • web.xml의 <async-supported>를 사용하는 경우

    이미 구현된 웹 서비스는 web.xml에 Servlet 3.0이 지원하는 async-supported 사용하여 비동기 웹 서비스로 설정할 수 있다.

    비동기 웹 서비스 설정 : <web.xml>
    <web-app>
        <servlet>
            <servlet-name>AddNumbers</servlet-name>
            <servlet-class>fromwsdl.server.AddNumbersImpl</servlet-class>
            <async-supported>true</async-supported>
        </servlet>
        <servlet-mapping>
            <servlet-name>AddNumbers</servlet-name>
            <url-pattern>/addnumbers</url-pattern>
        </servlet-mapping>
    </web-app>