제24장 JAX-RPC 웹 서비스 SOAP 메시지 핸들러 생성

내용 목차

24.1. SAAJ 사용
24.1.1. SOAP 메시지 생성
24.1.2. SAAJ 문서 다루기
24.1.3. SAAJ를 이용한 SOAP 메시지 전송
24.2. SOAP 메시지 핸들러의 생성
24.2.1. 메시지 핸들러의 생성
24.2.2. 메시지 핸들러와 핸들러 체인의 설계
24.2.3. 핸들러 인터페이스의 구현
24.2.4. Jakarta EE 웹 서비스 DD 파일 작성
24.2.5. 클라이언트에서 SOAP 메시지 핸들러의 사용
24.2.6. 파일 송수신하는 웹 서비스와 클라이언트 예제

본 장에서는 SAAJ 프로그래밍 모델과 JAX-RPC와 연동하여 사용하는 메시지 핸들러에 대해서 설명한다.

SAAJ(SOAP with Attachments API for Java)를 사용하면 SOAP 메시지를 직접 생성하고 읽고 다루는 작업을 할 수 있다. 여기에서는 SAAJ의 프로그래밍 모델에 대해서 간략히 언급하고 API 등에 관련된 자세한 내용은 여기에서 다루지 않는다. 보다 자세한 내용을 필요로 할 경우에는 SAAJ 스펙을 참고한다.

SAAJ는 Attachment가 없는 간단한 XML과도 같은 단순한 SOAP 메시지에서부터 MIME Attachment를 가지는 더 복잡한 SOAP 메시지를 다루는 작업까지 가능하다. SAAJ는 Abstract Factory 패턴을 기본으로 하며 MessageFactory에서 메시지(SOAPMessage)를 생성한다. SOAPMessage는 SOAP 문서를 나타내는 SOAPPart와 MIME Attachment를 나타내는 0개 이상의 AttachmentPart 객체를 포함한다.


SAAJ는 SOAP 문서를 생성하고 다루기 위해 사용할 수 있는 많은 인터페이스를 제공한다.

SOAP 문서는 element와 속성으로 구성된 XML 인스턴스이다. 편의를 위해 SOAP 문서의 주요한 부분들은 SAAJ와 대응하는 타입을 갖는다. envelope는 SOAPEnvelope, Header는 SOAPHeader, Body는 SOAPBody와 대응한다. SOAPElement 타입은 SOAP namespace에 속하지 않는 응용 프로그램에 종속적인(application-specific) element와 대응한다.

메시지 핸들러는 JAX-RPC 클라이언트와 웹 서비스 Endpoint에 의해 송수신되는 SOAP 메시지를 직접 다룰수 있게 하며, 정적으로 생성된 Stub, 동적으로 생성된 프록시, DII, Java 클래스 Endpoint, EJB Endpoint와 함께 사용될 수 있다.

메시지 핸들러의 가장 중요한 목적은 JAX-RPC 클라이언트와 웹 서비스 Endpoint가 송수신하는 SOAP 메시지의 Header 블록을 더하고, 읽고, 다루는 메커니즘을 제공하는 것이다.

메시지 핸들러는 Jakarta EE 컨테이너에 의해서 다루어진다. JAX-RPC 클라이언트 API를 사용하여 SOAP 메시지를 전송하려 할 때는 JAX-RPC 런타임이 웹 서비스로 연결된 네트워크에 내보내기 전에 메시지 핸들러 체인을 통해 SOAP 메시지를 가공하게 된다. 마찬가지로 SOAP 응답 메시지가 JAX-RPC 클라이언트에 의해 수신될 때에는 클라이언트 응용 프로그램으로 결과가 리턴되기 전에 이전에 거쳐 나갔던 동일한 메시지 핸들러 연결고리를 거쳐서 가공된다.

이러한 핸들러는 여러 가지로 사용할 수 있다. 그 중에서 보안에 관련된 경우를 살펴보면 클라이언트에서 암호화된 SOAP 메시지를 송신하는 경우 암호화 작업을 위한 핸들러를 사용해서 메시지를 가공하여 송신할 수 있고, 웹 서비스는 그 요청 메시지를 암호 해독을 위한 핸들러를 이용하여 해독한 다음 그 데이터를 웹 서비스를 구현한 Back-end로 보내게 된다. 이에 대한 SOAP 메시지 응답은 이 과정을 역으로 수행하게 된다.

또 다른 예는 SOAP 메시지의 Header 부분에 저장된 정보에 접근하는 경우이다. SOAP Header에 웹 서비스의 특정한 정보를 저장할 수 있고, 핸들러로 하여금 그 정보를 다룰 수 있게 할 수 있다.

SOAP 메시지 핸들러는 웹 서비스의 요청과 응답에서 모두 SOAP 메시지를 가로채서 가공할 수 있다. 또 웹 서비스 Endpoint와 웹 서비스를 호출하는 클라이언트에서 모두 핸들러를 생성할 수 있다.

다음은 javax.xml.rpc.handler API의 주요 클래스와 인터페이스에 대한 설명이다.

클래스와 인터페이스설명
javax.xml.rpc.handler. HandlerSOAP 요청 메시지와 응답 메시지, fault 메시지를 다루는 메소드를 포함하는 주요 인터페이스이다.
javax.xml.rpc.handler. HandlerChain핸들러의 리스트를 나타내는 인터페이스로 리스트가 가지는 element는 java.xml.rpc.handler.Handler 타입이다.
javax.xml.rpc.handler. HandlerRegistry프로그래밍 레벨에서 핸들러의 설정을 조정할 수 있도록 지원하는 인터페이스이다.
javax.xml.rpc.handler. HandlerInfowebservices.xml에 정의되는 핸들러의 초기 파라미터와 같은 핸들러의 정보들을 포함하는 클래스이다.
javax.xml.rpc.handler. MessageContext핸들러에 의해서 처리되는 메시지의 내용을 추상화한 인터페이스로, 핸들러 체인 내에서 핸들러는 메시지 처리에 관계되는 상태 정보를 공유할 수 있다.
javax.xml.rpc.handler.soap. SOAPMessageContextSOAP 요청과 응답 메시지에 접근할 수 있는 메소드를 제공하는 MessageContext의 하위 인터페이스이다.
javax.xml.rpc.handler. GenericHandler핸들러 인터페이스를 구현한 추상클래스로서 SOAP 메시지 핸들러 개발자는 이 클래스를 상속받아서 구현하면 된다.
javax.xml.soap.SOAPMessageSOAP 메시지의 요청과 응답을 포함하는 클래스이다.

다음은 메시지 핸들러와 핸들러 체인을 추가함으로써 웹 서비스를 갱신하는 절차이다.

  1. 메시지 핸들러와 핸들러 체인의 설계

  2. javax.xml.rpc.handler.Handler 인터페이스를 구현하는 Java 클래스의 생성 및 핸들러 체인의 구현

  3. Java 코드의 컴파일

  4. 웹 서비스 DD(webservices.xml)의 작성

  5. 웹 서비스 패키징과 배치

웹 서비스 클라이언트에서도 SOAP 메시지 핸들러를 사용할 수 있으며 이는 본 장의 뒷부분에 언급된다.

SOAP 메시지 핸들러 클래스는 javax.xml.rpc.handler.Handler 인터페이스를 직접 구현하거나 이를 구현한 javax.xml.rpc.handler.GenericHandler를 상속받아서 구현한다.

메시지 핸들러 클래스는 핸들러 인터페이스의 다음과 같은 함수들을 구현해야 한다.

함수설명
init()

HandlerInfo 객체는 webservices.xml에 정의된 초기화 정보 같은 SOAP 메시지 핸들러에 대한 정보를 포함하고 있다. HandlerInfo.getHandlerConfig() 메소드를 실행시키면 name-value 형태의 맵(Map)을 리턴한다.

핸들러의 초기화 작업을 진행하려면 init() 메소드를 구현해야 한다.

destroy()Handler.destroy() 메소드는 핸들러 객체의 인스턴스를 제거하는 경우 불리며, 핸들러의 생성 주기 동안 획득된 자원을 다시 놓아 주게 하려면 이 함수를 구현해야 한다.
getHeaders()핸들러 인스턴스에 의해 처리되는 header 블록을 가져오는 경우 사용한다.
handleRequest()

Handler.handleRequest() 메소드는 Back-end에 SOAP 메시지를 전달하기 전에 가로챌 경우에 사용한다. MessageContext 객체는 SOAP 메시지 핸들러에 의해 처리되는 메시지의 내용을 가지고 있으며, MessageContext의 서브 인터페이스인 SOAPMessageContext를 사용하면 SOAP 요청 메시지의 내용을 얻거나 변경할 수 있다.

메시지를 처리하기 위해서는 앞에서 언급되었던 SAAJ를 사용한다. SOAPMessageContext.getMessage()를 수행하면, SAAJ API에 속하는 SOAPMessage를 얻을 수 있고, SOAPMessage는 SOAPPart 객체와 Attachment로 구성된다. SOAP 메시지를 다루는 작업은 SAAJ 프로그래밍 기법과 동일하다.

handleResponse()

Handler.handleResponse() 메소드는 SOAP 메시지가 Back-end에서 처리된 후 웹 서비스를 호출한 클라이언트에게 회신하기 전에 SOAP 메시지를 가로채려 할 때 사용한다. MessageContext 객체는 SOAP 메시지 핸들러에 의해 처리되는 메시지의 내용을 가지고 있으며, MessageContext의 서브 인터페이스인 SOAPMessageContext를 사용하면 SOAP 응답 메시지의 내용을 얻거나 변경할 수 있다.

메시지를 처리하기 위해서는 앞에서 언급되었던 SAAJ를 사용한다. SOAPMessageContext.getMessage()를 수행하면 SAAJ API에 속하는 SOAPMessage를 얻을 수 있고, SOAPMessage는 SOAPPart 객체와 Attachment로 구성된다.

SOAP 메시지를 다루는 작업은 SAAJ 프로그래밍 기법과 동일하다.

handleFault()

Handler.handleFault() 메소드는 SOAP 메시지 처리 모델을 기반으로 SOAP Fault를 처리하는 메소드이다. handleRequest() 메소드와 handleResponse() 메소드에 의해 발생된 SOAP Fault뿐만 아니라 Back-end에서 발생된 오류를 처리하기 위하여 이 메소드는 구현한다.

MessageContext 객체는 SOAP 메시지 핸들러에 의해 처리되는 메시지의 내용을 가지고 있으며, MessageContext의 서브 인터페이스인 SOAPMessageContext를 사용하면 SOAP 응답 메시지의 내용을 얻거나 변경할 수 있다.

메시지를 처리하기 위해서는 앞에서 언급되었던 SAAJ를 사용한다.

본 절에서는 클라이언트에서 File_Send.txt라는 파일을 서버로 전송하고 서버에서는 이 파일을 지정된 폴더에 저장하고 파일을 받았다는 응답을 전달하는 웹 서비스 파일 송수신 예제이다.

클라이언트는 다음의 과정으로 생성한다.

  1. 파일 수신하는 메시지 핸들러 구현

  2. 파일 수신 후 파일을 받았다는 메시지를 전달하는 서비스 Back-end 구현

  3. 웹 서비스 배치서술자의 작성 및 웹 서비스 생성과 배치

  4. 웹 서비스 클라이언트 핸들러 작성

  5. 웹 서비스 클라이언트 작성

  6. 실행

다음은 웹 서비스의 메시지 핸들러 구현으로서 handleRequest()가 Back-end로 SOAP 요청을 하기 전에 먼저 실행된다. handleRequest() 함수 안에서는 SOAP 메시지에 Attachment로 붙어있는 파일을 SAAJ API를 이용하여 다루게 된다.

[예 24.2] 파일 수신 메시지 핸들러 구현 : << ServerAttachmentHandler.java >>

package filetransfer;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.rpc.JAXRPCException;
import javax.xml.rpc.handler.HandlerInfo;
import javax.xml.rpc.handler.MessageContext;
import javax.xml.rpc.handler.soap.SOAPMessageContext;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;

import javax.xml.rpc.handler.GenericHandler;

public final class ServerAttachmentHandler 
    extends GenericHandler
{
    private File dir;

    private final String DIR_PROP="directory";

    public void init(HandlerInfo info) {
        super.init(info);    
        Map m = info.getHandlerConfig();    

        String dirName = (String) m.get(DIR_PROP);

        if (dirName == null) {
            throw new JAXRPCException("Property named: "
                + DIR_PROP + " was not found");
        }
        dir = new File(dirName);

        if (! dir.exists()) {
            if (! dir.mkdirs()) {
                throw new JAXRPCException("Unable to create directory: " + dirName);
            }
        }
        if (! dir.canWrite()) {
            throw new JAXRPCException(
                "Don't have write permission for " + dirName);
        }
    }

    private String getFileName(SOAPMessage request) 
        throws SOAPException
    {
        SOAPBody body =
            request.getSOAPPart().getEnvelope().getBody();
        Object obj = body.getChildElements().next();
        SOAPElement opElem = (SOAPElement) obj;
        SOAPElement paramElem = (SOAPElement)opElem.getChildElements().next();
        return paramElem.getValue();
    }

    private void copyFile(InputStream is, OutputStream os) 
        throws IOException
    {
        byte [] b = new byte[8192];
        int nr;
        while ((nr = is.read(b)) != -1) {
            os.write(b, 0, nr);
        }
    }
    public boolean handleRequest(MessageContext mc) {
        SOAPMessageContext ctx = (SOAPMessageContext) mc;
        SOAPMessage request = ctx.getMessage();
        if (request.countAttachments() == 0) {
            throw new JAXRPCException("** Expected attachments");
        }
        try {
            Iterator it = request.getAttachments();

            while(it.hasNext()) {
                AttachmentPart part = (AttachmentPart) it.next();
                String fileName = getFileName(request);
                System.out.println("Received file named: " + fileName);

                File outFile = new File(dir, fileName);
                OutputStream os = null;
                InputStream  is = null;

                try {
                    os = new FileOutputStream(outFile);
                    is = part.getDataHandler().getInputStream();

                    copyFile(is, os);

                } catch (IOException ioe) {
                   ioe.printStackTrace();
                   throw new JAXRPCException("Exception writing file " + fileName, 
                   ioe);
                } finally {
                    try { 
                        if (is != null) is.close(); 
                    } catch (IOException ignore) {}

                    try {
                        if (os != null) os.close();
                    } catch (IOException ignore) {}
                }
            }
        } catch (SOAPException e) {
            e.printStackTrace();
            throw new JAXRPCException(e);
        }
        return true;
    }
    public QName[] getHeaders() {
        // TODO Auto-generated method stub
       return null;
    }
}


다음은 웹 서비스 DD 파일(webservices.xml)이다.


위와 같은 작업이 완료되면 web.xml과 jeus-webservices-dd.xml을 다음과 같이 작성한다.



위와 같이 DD 파일의 작성이 완료되면 EAR로 패키징하여 JEUS에 배치 작업을 한다.

이 서비스에 접근할 수 있는 주소는 다음과 같다.

http://localhost:8088/FileAttachmentService/FileAttachmentService

클라이언트 핸들러에서는 handleRequest()에서 메시지 컨텍스트를 직접 다루며, SAAJ를 통하여 SOAP 메시지에 Attachment 형태로 파일을 첨부하게 된다.

[예 24.8] 웹 서비스 클라이언트 핸들러 : << ClientAttachmentHandler.java >>

package filetransfer;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.xml.namespace.QName;
import javax.xml.rpc.JAXRPCException;
import javax.xml.rpc.handler.MessageContext;
import javax.xml.rpc.handler.soap.SOAPMessageContext;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;

import javax.xml.rpc.handler.GenericHandler;

public final class ClientAttachmentHandler 
  extends GenericHandler
{
    private String getFileName(SOAPMessage request) 
        throws SOAPException
    {
        SOAPBody body = request.getSOAPPart().getEnvelope().getBody();

        SOAPElement opElem = (SOAPElement)body.getChildElements().next();

        SOAPElement paramElem = (SOAPElement)opElem.getChildElements().next();
        return paramElem.getValue();
    }

    public boolean handleRequest(MessageContext mc) {

        SOAPMessageContext ctx = (SOAPMessageContext) mc;
        SOAPMessage request = ctx.getMessage();

        try {
            String fileName = getFileName(request); 
            AttachmentPart part = request.createAttachmentPart();
            part.setContentType("application/x-zip-compressed");
            FileDataSource fds = new FileDataSource(fileName);
            part.setDataHandler(new DataHandler(fds));
            request.addAttachmentPart(part);
        } catch(SOAPException e) {
            e.printStackTrace();
            throw new JAXRPCException(e);
        }
        return true;
    }

    public QName[] getHeaders() {
        // TODO Auto-generated method stub
        return null;
    }    
}