IT_Programming/Java

XML 문서에 Binary Data를 Insert하는 방법

JJun ™ 2010. 2. 5. 20:13

 

출처: http://www.itdi.co.kr/onuri/bbs/board.php?bo_table=02_4&wr_id=33

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
[첨  언] : 편의상 경어는 생략합니다. 샘플소스가 약간 지저분하네요. ^^;
[작성자] : 최 호필(hops)
              E-mail :
hops@bcline.com
              Homepage : http://hops.x-y.net
[작성일] : 2003년 10월 16일
[참  고]  : 모든 예제는 J2SE v1.3.1, DOM4J-1.4에서 테스트되었습니다.
[License] : 이 문서는 GPL을 따릅니다.

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

XML 문서에 Binary Data를 Insert하는 방법


CONTEXT

XML문서 내에 Binary Data(이미지 파일 또는 실행파일 등)을 넣을 수 있다. 어떻게 하는 것인지 알아보기 전에 왜 이런 것이 필요한가에 대해서 먼저 살펴보자. 왜 필요할까? 음.. 필요하니깐 필요한게 당연하다(ㅡ.ㅡ;;)
현재 하고 있는 일 중의 하나를 예로 들어 보겠다. 클라이언트와 서버 간의 통신이 일어나는데 이 Protocol을 XML로 정의하였다. HTTP를 이용하여 그 위에서 XML 데이터를 주고 받는 형식이다. 이 문서는 XML을 이용한 Protocol 설계를 설명하는 문서가 아니기에 그 부분은 생략하기로 한다. 클라이언트와 서버 간에 주고 받는 데이터 중에서 어떤 것은 서버에서 Binary File을 클라이언트로 보내줘야 하는 부분이 발생하였고 이것을 어떻게 처리하는지를 파악하고 문제해결을 했기에 문서로 남긴다. 결론적으로 말하면 Binary Data를 Base64 Encoding해서 전달하면 된다라는 것이다.

 

ENVIRONMENT

먼저 테스트를 했던 환경을 잠깐 언급하겠다. 환경은 아래와 같다.

OS: Windows 2000 Professional
J2SDK version: 1.3.1_07-b02
Servlet Container: BEA Weblogic Server 6.1 Service Pack4
XML Parser: DOM4J 1.4
BASE64 Encoding Utility: com.Ostermiller.util.Base64 (http://ostermiller.org/utils/Base64.html)

위의 환경에서도 볼 수 있듯이 Server쪽은 Weblogic Server의 Servlet Container를 이용하여 Servlet이 클라이언트의 요청을 처리하도록 하였고, 클라이언트는 StandAlone Application을 작성하여 HttpURLConnection을 사용하여 Servlet과 연결해서 데이터를 전송하였다. 또한 XML의 Parser로는 DOM4J를 사용하여 SAXReader를 통해 처리하였고, 마지막으로 Base64 Encoding하는 Utility는 위에 언급한 클래스를

사용하였다.

다른 부분의 설정부분을 설명하는 것은 이 문서의 범위를 넘어가기에 생략하고 XML Parser와 Ostermiller Util 설치하는 법만 잠시 언급하면, 각각의 Site에서 다운을 받아서 JAR파일을 클래스패스에 추가하면 된다. Servlet Container의 클래스패스에 설정하는 법과 Application의 클래스패스를 설정하는 법 역시 간단한 것이기에 언급하지 않는다. 단, Ostermiller Util을 다운을 받게 되면 JAR파일로 되어 있는데 이것을 압축유틸리티로 풀어보면 안에 소스와 직접 실행시키는데 필요한 Properties파일이 존재한다. 자세한 Installation은 해당 사이트의 설명을 참조한다. 이 문서에서 설명하는 것을 테스트 하는데는 클래스패스만 잡으면 모든 작업이

끝나고 별도의 Properties파일을 수정하는 것은 없다.

 

XML Format

주고 받는 XML의 형식을 프로그램 설명을 위해 잠시 언급하기로 한다. 별도의 DTD나 Schema는 사용하지 않고 실제 샘플 프로그램에서 사용하는 그대로의 XML을 보여준다. 클라이언트에서 서버로 보내는 Request와 Response는 아래와 같다. 먼저 클라이언트에서 발송하는 Request용 XML문서이다.      

<?xml version="1.0" encoding="euc-kr"?>
      <ctp>
         <name>한글</name>
         <filename>test.txt<filename>
      </ctp>


다음은 서버에서 클라이언트로 보내는 Response용 XML문서이다.

file Element 안에 Binary Data가 들어간다.      

<?xml version="1.0" encoding="euc-kr"?>
      <ctp>
         <name>한글</name>
         <filename>$FILE_NAME</filename>
         <file>$BINARY_DATA</file>
      </ctp>


filename Element에는 클라이언트로 전송하려는 바이너리 파일의 파일명이 들어간다. name Element는

한글을 테스트 해 보기 위해 그냥 한글이라는 데이터를 넣은 것 뿐이고 아무런 의미는 없다.

서버쪽에서 위의 XML문서를 클라이언트로 보내면 클라이언트는 XML문서를 받아서 name과 filename을 Console에 뿌리고 클라이언트 프로그램이 위치한 디렉토리에 filename을 가진 파일을 생성한다.

 

SERVER PROGRAM SAMPLE

먼저 서버측 프로그램을 살펴보자. 테스트용이기에 아주 간단하게만 구현하였다.

먼저 클라이언트의 Request를 받고, Response를 내보내는 Servlet이다.   

/*
    * System            :
    * Program Id        :
    * Program Name      :
    * Source File Name  : Gateway.java
    * Description       :
    * Programmer        : 최호필
    * Creation Date     : 2003. 10. 16.
    * Update Date       :
    * Update History    :
    */

   import java.io.*;

   import javax.servlet.http.HttpServlet;
   import javax.servlet.http.HttpServletRequest;
   import javax.servlet.http.HttpServletResponse;
   import javax.servlet.ServletOutputStream;

   import hops.xml.util.Util;
   import hops.xml.util.Dom4JAdapter;

   /**
    *
    *
    * @author Copyright (c) Ho-Pil, Choi. All Rights Reserved
    * @version 1.0.0
    */
   public class Gateway extends HttpServlet {

      public void service(HttpServletRequest req, HttpServletResponse res)
         throws IOException {
         System.out.println("service called....");
         Dom4JAdapter adapter = new Dom4JAdapter(req.getInputStream());
         adapter.parsing();
         String output = adapter.reverseParsing();
         res.setContentType("text/xml; charset=euc-kr");
         ServletOutputStream out = res.getOutputStream();
         out.print(output);
      }

   }


Gate에 대한 특별한 설명은 하지 않는다. 단지 GET방식이든 POST방식이든 클라이언트의 요청인 req의 InputStream을 Dom4JAdapter의 생성자로 넘기고 parsing()메소드를 호출하는 것으로 되어 있고 reverseParsing()메소드를 호출하여 클라이언트로 보낼 XML문서를 생성하여 클라이언트로 전송하는 역할만을 수행한다. 그럼 다음으로 Dom4JAdapter 클래스를 보도록 하자.   

/*
    * System            :
    * Program Id        :
    * Program Name      :
    * Source File Name  : Dom4JAdapter.java
    * Description       :
    * Programmer        : 최호필
    * Creation Date     : 2003. 10. 16.
    * Update Date       :
    * Update History    :
    */

   package hops.xml.util;

   import java.io.*;

   import org.dom4j.Element;
   import org.dom4j.Document;
   import org.dom4j.DocumentException;
   import org.dom4j.io.SAXReader;
  
   import com.Ostermiller.util.Base64;
  
   /**
    *
    *
    * @author Copyright (c) Ho-Pil, Choi. All Rights Reserved
    * @version 1.0.0
    */
   public class Dom4JAdapter {
      private Element rootElement;
      private Document document;
     
      private void log(String s) {
         System.out.println(s);
      }
     
      public Dom4JAdapter(InputStream is) {
         try {
            SAXReader reader = new SAXReader();
            log("hear is in the parser...");
            document = reader.read(is);
            rootElement = document.getRootElement();
         } catch(DocumentException de) {
            log("1: " + de.toString());
         }
      }
     
      public void parsing() {
         // name Element의 내용을 뽑아서 String으로 리턴한다.
         String name = Util.elementAsString(rootElement,"name");
         log("name: " + name);
         String fileName = Util.elementAsString(rootElement,"filename");
         log("fileName: " + fileName);
         String file = Util.elementAsString(rootElement,"file");
      }
     
      public String reverseParsing() {
         try {
            String bFile = "C:\";
            String fileName = "poster.jpg";
            StringBuffer buffer = new StringBuffer();
            buffer.append("<?xml version="1.0" encoding="euc-kr"?>");
            buffer.append("<ctp>");
            buffer.append("<name>한글</name>");
            buffer.append("<filename>" + fileName + "</filename>");
            buffer.append("<file> ");
           
            BufferedInputStream is = new BufferedInputStream(new FileInputStream(bFile+fileName));
            ByteArrayOutputStream out = new ByteArrayOutputStream();
           
            // 맨 마지막 Parameter인 boolean은 BASE64의 76자마다 Line Break를 넣을 것인가를 지정
            Base64.encode(is,out,true);
            String base64 = out.toString();
            System.out.println("BBBB: " + Base64.isBase64(base64));
            buffer.append(base64);
            buffer.append(" </file>");
            buffer.append("</ctp>");
            return buffer.toString();
         } catch(Exception e) {
            log(e.toString());
         }
         return null;
      }
   }


위 클래스 중에서 실제로 봐야 할 부분이 reverseParsing() 메소드이다. 그 중에서도 Base64.encode(is,out,true);이다. 이 메소드는 InputStream인 is를 받아서 OutputStream인 out에 넣어주는데 넣기전에 BASE 64 Encoding을 시킨다라는 것이다. 또한 맨 마지막의 Parameter는 76자마다 Line Break를 넣을 것이냐를 지정하게 된다. 지정하지 않으면 Decoding시에 BASE 64 Encoding이 아닌 것으로 인식을 하는 문제가 있다. 따라서 반드시 Line Break를 넣을 수 있도록 한다.

 

CLIENT PROGRAM SAMPLE

마지막으로 클라이언트측의 코드를 살펴보자. 클라이언트에서 눈여겨 살펴 봐야 하는 부분은 BASE 64로 Encoding된 부분을 읽어서 다시 Decoding해서 파일로 만들어 내는 부분이 된다.   

/*
    * System            :
    * Program Id        :
    * Program Name      :
    * Source File Name  : SendXMLTest.java
    * Description       :
    * Programmer        : 최호필
    * Creation Date     : 2003. 10. 16.
    * Update Date       :
    * Update History    :
    */
   
   import java.net.URL;
   import java.net.HttpURLConnection;
   import java.io.BufferedOutputStream;
   import java.io.ByteArrayInputStream;
   import java.io.FileOutputStream;
   import java.io.InputStream;
  
   import org.dom4j.*;
   import org.dom4j.io.SAXReader;
   import com.Ostermiller.util.Base64;
  
   public class SendXMLTest {
      private URL serverURL;
     
      public SendXMLTest(String url) throws Exception {
         serverURL = new URL(url);
      }
     
      public void send() throws Exception {
         HttpURLConnection httpConn = (HttpURLConnection)serverURL.openConnection();
         log("Open Connection...");
        
         String contents = xmlForSending();
         byte[] byteBuf = contents.getBytes();
         httpConn.setRequestProperty("Content-Length",String.valueOf(byteBuf.length));
         httpConn.setRequestProperty("Content-Type","text/xml; charset=euc-kr");
         httpConn.setRequestMethod("POST");
         httpConn.setDoOutput(true);
         httpConn.setDoInput(true);
        
         BufferedOutputStream bos = new BufferedOutputStream(httpConn.getOutputStream());
         bos.write(byteBuf,0,byteBuf.length);
         bos.flush();
         bos.close();
        
         InputStream is = httpConn.getInputStream();
        
         // Validation을 false로 만든다.
         SAXReader reader = new SAXReader(false);
        
         // ElementHandler를 추가한다. 여기서는 사용하지 않는다.
         // reader.setDefaultHandler(new FileElementHandler());
        
         Document document = reader.read(is);
         Element rootElement = document.getRootElement();
         String name = rootElement.elementTextTrim(new QName("name",rootElement.getNamespace()));
         String fileName = rootElement.elementTextTrim(new QName("filename",rootElement.getNamespace()));
         log("Name: " + name);
         log("FileName: " + fileName);
         String files = rootElement.elementTextTrim(new QName("file",rootElement.getNamespace()));
        
         BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(fileName));
         byte[] buf = Base64.decodeToBytes(files);
        
         os.write(buf,0,buf.length);
         os.flush();
        
         os.close();
         is.close();
        
         httpConn.disconnect();
         log("Close Connection...");
      }
     
      private void log(String s) {
         System.out.println(s);
      }
     
      private String xmlForSending() {
         StringBuffer buffer = new StringBuffer();
         buffer.append("<?xml version="1.0" encoding="euc-kr"?>");
         buffer.append("<ctp>");
         buffer.append("<name>한글</name>");
         buffer.append("<filename>test.txt</filename>");
         buffer.append("</ctp>");
         return buffer.toString();
      }
     
      public static void main(String[] args) throws Exception {
         if (args.length > 1) {
            System.out.println("<Usages>#java SendXMLTest [URL]");
            System.exit(1);
         }
        
         String url = null;
         if (args.length == 0) {
            url = "http://IP:PORT/servlets/Gateway";
         } else {
            url = args[0];
         }
         SendXMLTest test = new SendXMLTest(url);
         test.send();
      }
     
      /*
      public final class FileElementHandler implements ElementHandler {
         public void onStart(ElementPath elementPath) {
            System.out.println(onStart: " + elementPath.getPath());
         }
        
         public void onEnd(ElementPath elementPath) {
            System.out.println(onEnd: " + elementPath.getPath());
         }
      }
      */
   }


주의할 점은 POST로 보낼때 CONTENT TYPE과 CONTENT LENGTH가 있어야 한다라는 점이다. Decode를 하는 부분을 보면 file Element에 있는 BASE 64로 Encoding된 내용을 Parser로 뽑아 내고, Base64 클래스의 decodeToBytes()메소드를 사용하여 byte array로 변환시키고 파일에 기록하면 된다.

위의 프로그램이 정상적으로 동작했다면 Binary File이 생성되었을 것이다. 이미지라면 직접 보면 올바르게 생성되었을 것이다. 일반적으로 그냥 Binary File을 XML 문서에 넣을 수는 없다. XML 문서는 PURE TEXT만을 사용하도록 되어 있기에 Unicode 문자가 아닌 것이 오면 Well-Formed 문서가 아니라는 에러를 내기

때문이다. 마지막으로 여기에서 설명이 생략된 것들은 여러 참고 문헌이나 Site에 잘 나와 있으니 참고하기

바란다.

'IT_Programming > Java' 카테고리의 다른 글

Finalize Guardian Idiom  (0) 2010.02.25
[펌] Java에서 JavaScript호출하기  (0) 2010.02.23
JDOM (The Java DOM)  (0) 2010.02.02
Painting in AWT and Swing - 반복적 repaint() 호출 이슈!  (0) 2010.01.19
Java(JDK) 7 특징  (0) 2009.11.26