[4th Seoul Workshop] Hotjava Protocol and Content Handler

신명기
305-350 대전광역시 유성구 가정동 161번지
한국전자통신연구소 멀티미디어표준연구실
E-mail : mkshin@pec.etri.re.kr
URL : http://pec.etri.re.kr/~mkshin/
Abstract:
선 마이크로시스템즈에서는 96년 5월 핫자바 프리베타 버젼을 발표했다. 대부분의 인터넷 프로그래머가 핫자바 베타 버젼의 개발을 학수고대한 이유는 핫자바가 브라우저 이상의 의미를 가지며, 인터넷 브라우저를 구성하는 새로운 개념을 제시하기 때문이다. 즉, 핫자바 브라우저는 인터넷-인식 어플리케이션(Internet-aware application)을 구성하는 클래스 라이브러리로서의 역할을 한다. 핫자바는 모든 플랫폼상에서 안전하게 동작할 수 있는 브라우저와 어플리케이션을 사용자 입장에서 자유롭게 구성(customization)할 수 있으며, 핫자바 클래스 라이브러리로 작성된 모든 확장된 프로그램은 모든 자바-호환 환경에서 동작이 가능하게 된다. 이 의미는 핫자바 브라우저에 인터넷을 배경으로 새로운 데이타 타입이나 프로토콜을 손쉽게 확장할 수 있음을 뜻한다. 본 논문에서는 이를 가능하게 하는 핫자바의 컨텐트 핸들러와 프로토콜 핸들러의 개념, 프로그래밍 방법등을 예제를 통해 기술한다.
Keywords:
자바, 핫자바, 프로토콜 핸들러, 컨텐트 핸들러

1. 서론

선 마이크로시스템즈에서는 JDK 1.0.2에 이어 96년 5월 핫자바 프리베타 버젼을 발표했다. 현재 핫자바 버젼은 프리베타 1이며, 핫자바 1.0의 기능들은 아직까지 완전하지 않다. 핫자바 브라우저가 넷스케이프나 인터넷 익스플로러와 같은 브라우저 기능이 우수한 다른 웹 브라우저와 차별성을 갖는 이유는 인터넷-인식 어플리케이션을 구성하는 클래스 라이브러리로서의 역할을 할 수 있기 때문이다. 핫자바는 모든 플랫폼상에서 안전하게 동작할 수 있는 브라우저와 어플리케이션 을 사용자 입장에서 자유롭게 구성할 수 있으며, 핫자바 클래스 라이브러리로 작성된 모든 확장된 프로그램은 모든 자바-호환 환 경에서 동작이 가능하게 된다. 이 의미는 핫자바 브라우저에 인터넷을 배경으로 새로운 데이타 타입이나 프로토콜을 손쉽게 확장할 수 있음 을 뜻한다.

현재 대부분의 자바 프로그램은 자바 애플릿과 어플리케이션이 주류를 이루고 있다. 그러나 핫자바 브라우저의 동적인 프로토콜, 데이터 타입의 확장을 위해 사용되는 프로토콜 핸들러, 컨텐트 핸들러 또한 차세대 자바를 이용한 어플리케이션 확장 기술로 차후로는 많은 예제들이 쏟아져 나올 것이다. 프로토콜 핸들러나 컨텐트 핸들러 구현을 위한 플랫폼으로는 핫자바 브라우저만을 사용할 수 있다는 점을 유의하자. 본 논문에서는 이를 가능하게 하는 핫자바의 컨텐트 핸들러와 프로토콜 핸들러의 개념, 프로그래밍 방법등을 예제를 통해 기술한다.

2. 핫자바 : 새로운 데이타 타입과 프로토콜의 확장

현재 핫자바 베타 버젼에서 지원되는 기능은 다음과 같다. 핫자바는 JDK 1.0으로 작성된 애플릿을 브라우징 할 수 있으며, 애플릿 보안 기능 강화, 멀티쓰레드 어플리케이션 지원, 여러개의 URL 목록 유지 등 알파 버젼과는 다른 향상된 기능들을 많이 지원하고 있다. 그 중에서도 넷스케이프나 인터넷 익스플로러와 차별화되는 가장 중요한 기능은 컨텐트 핸들러와 프로토콜 핸들러를 사용하여 새롭게 필요로 하는 데이타와 프로토콜의 처리 루틴을 브라우저에 쉽게 확장할 수 있는 점이다. 예를들어 사용자 입장에서 (그림 1)과 같이 VRML과 같은 새로운 데이타 타입의 브라우징이나 S(Secure)-HTTP, Multicast와 같은 프로토콜의 처리 루틴을 기존 브라우저의 코드 수정 없이 쉽게 확장 할 수 있다. 참고로 넷스케이프의 플러그-인 기술은 데이타 만을 플러그-인 할 수 있다.


(그림 1) 핫자바의 프로토콜, 데이타 처리 확장 기술

현재 제공되는 프리베타 버젼은 이러한 프로토콜 핸들러, 컨텐트 핸들러를 브라우저내의 로컬 화일 시스템에 인스톨 하는 방식으로 구현해야 하지만, 1.0 정식 버젼에서는 이러한 브라우저 확장 프로그램을 다운로드 받아 사용할 수 있게끔 기능이 향상될 예정이어서 진정한 네트웍 소프트웨어 확장 기술로서 선보이게 될 것이다.

일반적인 웹 프로토콜(http, ftp, telnet, news, mailto ...)과 컨텐트 (html, text, gif, jpg ...)의 처리는 웹 브라우저의 고정화된 협상 과정을 통해 이루어진다. 반면, 핫자바 브라우저는 (그림 2)와 같은 동적인 협상과정이 이루어지므로, 새로운 프로토콜, 데이타를 확장할 수 있는 가능성이 생기게 된다.



(그림 2) 핫자바의 데이타, 프로토콜 처리

프로토콜 핸들러의 경우는 디폴트로 다음과 같은 URL 입력 스트링 파싱을 통해
    <protocol>://<host>/<file>
                
프로토콜 처리 루틴을 찾게 되며, 새로운 프로토콜인 경우, 해당하는 프로토콜 핸들러 루틴을 찾아 처리를 하게된다. 이러한 방식은 기존 프로토콜의 웹 기능 확장면 뿐만 아니라 사용자가 새롭게 정의한 프로토콜에 이루기까지 폭넓게 적용가능하다. 예를 들면 다음과 같은 적용이 가능한다. 이러한 프로토콜의 결과로 반환되는 데이타 값은 MIME 타입(text/html, text/plain, image/gif, image/jpg ...)에 따라 처리되며, 새로운 타입을 갖는 컨텐트가 요구되면(예를들어 video/mbone, appication/xmcm ...) 그 처리를 위한 컨텐트 핸들러 루틴을 찾아 동작하게 된다.

3. 프로토콜 핸들러 프로그래밍

프로토콜 핸들러란 특정 프로토콜의 처리가 요구될 때 핫자바가 로드하는 자바 프로그램으로, 새로운 프로토콜을 핫자바 브라우저에 쉽게 확장하는 하는 기술이다. 참고로 넷스케이프에서는 지원되지 않으며, 핫자바에서만 가능하다.

기존 웹 브라우저에서는 http, ftp, telent와 같은 프로토콜들을 지원하고 있다. 본 논문에서는 finger 프로토콜을 위한 코드를 프로토콜 핸들러로 작성하여 핫자바에서 확장 사용이 가능하도록 인스톨시켜보자. 인터넷 도메인 etri.re.kr을 갖는 환경에서 finger 서비스를 위한 프로토콜 핸들러를 인스톨하여 처리하고자 한다면 다음과 같은 인스톨 과정과 프로그래밍 과정이 요구된다. 인터넷 도메인을 따라 프로토콜 핸들러를 작성하는 이유는 네이밍 충돌을 방지하기 위해서이다.

3.1 디렉토리 생성

프로토콜 핸들러가 동작할 디렉토리를 단계별로 생성한다.
        classes - kr - re - etri- protocol - finger
대개 디렉토리의 시작은 classes로 하며, 나머지 4가지 디렉토리 이름은 kr.re.etri.protocol 패키지를 생성하기 위한 이름들이다. 마지막 finger 디렉토리는 프로토콜 핸들러의 이름과 같아야 한다.

3.2 프로토콜 핸들러를 위한 자바 소스 파일 작성

        classes - kr - re - etri- protocol - finger - Handler.java
                                                   |- FingerURLConnection.java
finger 프로토콜 핸들러를 위한 자바 소스 파일인 Handler.java와 FingerURLConnection.java를 finger 디렉토리 아래에 작성한다. 패키지로 package kr.re.etri.protocol.finger가 선언되어야 하며, Handler.java 프로그램은 java.net 패키지의 URLStreamHandler 클래스의 하위클래스로 정의해야 하며, FingerURLConnection.java 프로그램은 URLConnection의 하위클래스로 정의해야 함을 유의한다.

URLStreamHandler 클래스는 JDK 1.0.2 java.net 패키지에 다음과 같은 계층 구조로 포함되어 있으며 정의된 메소드는 다음과 같다.

java.lang.Object
   |
   +----java.net.URLStreamHandler
핫자바 브라우저의 특징은 데이타와 프로토콜 처리를 위한 컨텐트 협상 과정이 동적으로 이루어 진다는데 있다. 즉, 사용자가 특별히 지정해 놓지 않으면 기존 핫자바에서 구현된 디폴트 코드로 협상 과정이 일어나며, 사용자가 정의해 놓은 코드(즉, 해당 메소드를 오버라이드 해 놓으면) 그 코드를 수행하게 된다.

Handler.java 코드는 (코드 1)과 같다. openConnection() 메소드는 오버라이드되어 주어진 url을 가지고 FingerURLConnection(url) 클래스를 호출하게끔 되어있다. finger의 URL 스킴 정의를 위해 parseURL()과 toExternalForm()을 오버라이드 하였다.

//코드 1 : Handler.java 
package kr.re.etri.protocol.finger;

import java.net.*;
import java.io.*;
import java.util.StringTokenizer;

public class Handler extends java.net.URLStreamHandler {
    
    static void debug(String s) {
        System.err.println(s);
    }

    public Handler () {
        System.out.println("Made a FingerURLStreamHandler");
    }

    protected URLConnection openConnection(URL url) throws IOException {
        return new FingerURLConnection(url);
    }

    protected void parseURL(URL u, String spec, int start, int limit) {
        String protocol = u.getProtocol(); 
        String host = new String();
        int port = u.getPort();
        if(port <=0)
            port = 79; // default port for finger
        String file = new String();
        String ref = null;
        StringTokenizer st = null;

        if ((start <= limit )) {
            while(spec.charAt(start) == '/' || spec.charAt(start) == ':')
                start++;
        }
           
        st = new StringTokenizer(spec.substring(start), ",", false);
        
        while(st.hasMoreTokens()) {
            String elem = st.nextToken();
            String newHost, newUser;
            int i;
            if(elem == null)
                continue;
            i = elem.indexOf('@');
            if(i < 0) {
                try {
                    newHost = InetAddress.getLocalHost().getHostName();
                } catch (UnknownHostException e) {
                    newHost = "localhost";
                }
            }
            else
                newHost = new String(elem.substring(i + 1));
            newUser = new String(elem.substring(0, i));
            if(host.length() != 0) {
                file += ",";
                host += ",";
            }
            file += newUser;
            host += newHost;
        }
        setURL(u, protocol, host, port, file, ref);
    }   

    /**
     * reverse parse the URL - put back to "original" form
     */
    protected String toExternalForm(URL u) {
        StringTokenizer st1 = new StringTokenizer(u.getFile(), ",", false);
        StringTokenizer st2 = new StringTokenizer(u.getHost(), ",", false);

        String body = new String();

        while(st1.hasMoreTokens() && st2.hasMoreTokens()) 
            body += st1.nextToken() + "@" + st2.nextToken() + ",";
        
        return new String(u.getProtocol() + "://" + body);
    }
}
또한 같은 디렉토리에 FingerURLConnection.java 소스 파일도 (코드 2)와 같이 작성한다.
//코드 2 : FingerURLConnection.java
package kr.re.etri.protocol.finger;

import java.net.*;
import java.io.*;
import java.util.StringTokenizer;
import java.util.Date;

public class FingerURLConnection extends java.net.URLConnection {

private void debug(String s) {
    System.err.println(s);
}
    private Socket server = null;

    public FingerURLConnection (URL url) {
        super(url);
        System.out.println("Made a FingerURLConnection");
    }


public Object getContent() throws IOException {
    debug("In FingerURLConnection.getContent()");
    StringBuffer sb = new StringBuffer();
    sb.append("<TITLE>Finger output</TITLE>\n<HTML>");
    StringTokenizer st1 = new StringTokenizer(url.getFile(), ",", false);
    StringTokenizer st2 = new StringTokenizer(url.getHost(), ",", false);

        while (st1.hasMoreTokens() && st2.hasMoreTokens()) {

           sb.append("<P><HR>" + getSingleData(st1.nextToken(), st2.nextToken()));
        }

        sb.append("<P><P><HR>" + ((new Date()).toString()) + "\n</HTML>");
        return sb.toString();
}

public void connect() throws IOException {
    debug("fingerURLconn::connect");
}


private String getSingleData(String user, String host) {
        try {
            server = new Socket(host, 79);
            System.out.println("Made socket to " + host + " on port " + 79);
        } catch (UnknownHostException uk) {
            debug("Unknown host in getSingleData: " + host);
            return "<PRE>Unknown host " + host + "</PRE>";
        } catch (SocketException ee) {
            debug("SocketException in getSingleData: " + host);
            return "<PRE>No finger server running on " + host + "</PRE>";
        } catch (IOException eee) {
            debug("IOException in getSingleData, making socket: " 
                   + eee.getMessage());
            eee.printStackTrace();
            return "<PRE>IOError connecting to " + host + "</PRE>";
        }

        
    debug("write to socket");

    PrintStream ps = null;
    DataInputStream dis = null;
    try {
        ps = new PrintStream(server.getOutputStream());
        dis = new DataInputStream(server.getInputStream());
    } catch (IOException e) {
        debug("input/output error making streams: " + e.getMessage());
        e.printStackTrace();
        return "<PRE>ERROR OCCURRED</PRE>";
    }

    ps.println(user);

    String li = null;
    StringBuffer sb = new StringBuffer();
    sb.append("<PRE>Finger data for " + user + "@" + host + ":</PRE><BR>\n");
    try {
        while((li = dis.readLine()) != null) {
            debug("Server output:>> " + li);
            sb.append(li + "\n<P>");
        }
    } catch (IOException e) {
        debug("input/output error in readLine: " + e.getMessage());
        e.printStackTrace();
        return "<PRE>ERROR OCCURRED</PRE>";
    }   
    return sb.toString();
}

public InputStream getInputStream() throws IOException {

    debug("In FingerURLConn::getInputStream");
    if(!connected)
        connect();
    
    return new StringBufferInputStream((String)getContent());
}

public String getContentType() {
    debug("getting FingerURLConn::ContentType()");
    return "text/html";
}

public OutputStream getOutputStream() throws IOException {
    if(!connected)
        connect();
    return null;
}
}

3.3 클래스패스를 세팅

CLASSPATH 변수를 세팅한다. 유닉스인 경우,
        setenv CLASSPATH .:/home/developer/classes
도스 쉘(Windows 95/NT)인 경우,
        set CLASSPATH=.;C:\developer\classes

3.4 소스를 컴파일

자바 컴파일러를 이용해 프로토콜 핸들러를 컴파일한다. 유닉스인 경우,
        cd kr/re/etri/protocol/finger
        javac Handler.java
        javac RunURLConnection.java
        
도스 쉘(Windows 95/NT)인 경우
        cd kr\re\etri\protocol\finger
        javac Handler.java
        javac RunURLConnection.java
        

3.5 java.protocol.handler.pkgs 프라퍼티 세팅

핫자바 프라퍼티 파일은 유닉스냐 도스 쉘이냐에 따라 다음과 같이 위치하고 있다. 유닉스인 경우,
        $HOME/.hotjava/properties
        
도스 쉘(Windows 95/NT)인 경우,
        \.hotjava\properties
        
핫자바 프라퍼티 파일에 새로운 패키지인 kr.re.etri.protocol.finger를 포함하도록 세팅한다.
        java.protocol.handler.pkgs=kr.re.etri.protocol
        

3.6 프로토콜 핸들러 구동

핫자바를 새롭게 구동시킨 후 다음과 같은 URL을 사용해 finger 프로토콜 핸들러를 통해 finger 서비스를 받아보자.
        finger://mkshin@pec.etri.re.kr
        
핫자바의 결과 화면은 (그림 3)과 같다.


(그림 3) finger 프로토콜 핸들러 결과 화면

4. 컨텐트 핸들러 프로그래밍

컨텐트 핸들러란 특정 MIME 타입(text/plain, text/richplain, image/gif 등) 테이터의 브라우징이 필요로 할 때 핫자바가 로드하는 자바 프로그램으로, 새로운 타입의 데이터의 처리 코드를 확장하는 기술이다. 넷스케이프에서는 지원되지 않고 핫자바에서만 가능하다. 예를 들어 인터넷 도메인 etri.re.kr을 갖는 환경에서 MIME 타입 text/plain을 갖는 모든 메시지를 프로토콜 핸들러를 인스톨하여 처리하고자 한다면, 다음과 같은 과정으로 컨텐트 핸들러를 구축 하면 된다.

4.1 디렉토리 생성

컨텐트 핸들러가 동작할 디렉토리를 단계별로 생성한다.
        classes - kr - re - etri - content - text
대개 디렉토리는 classes로 시작하며, 나머지 4가지 디렉토리 이름은 kr.re.etri.content 패키지를 생성하기 위한 이름들이다. 마지막 text 디렉토리는 데이터의 MIME 타입과 같은 이름을 가져야 한다.

4.2 켄텐트 핸들러를 위한 자바 소스 파일 작성

        classes - kr - re - etri - content - text - plain.java
text 디렉토리 아래에 text/plain 타입의 문서를 처리하기 위한 자바 소스 파일인 plain.java를 작성한다. 패키지로 package kr.re.etri.content.text가 선언되어야 하며, java.net 패키지의 ContentHandler 클래스의 하위클래스 로 정의해야 함을 유의한다.

ContentHandler 클래스는 JDK1.0.2 java.net 패키지에 다음과 같은 계층 구조로 포함되어 있으며 정의된 메소드는 다음과 같다.

java.lang.Object
   |
   +----java.net.ContentHandler
plain.java 소스 코드는 다음과 같다.
// 코드 3 plain.java

package kr.re.etri.content.text;

import java.net.ContentHandler;
import java.io.InputStream;
import java.net.URLConnection;
import java.io.IOException;

public class plain extends ContentHandler {

  public Object getContent(URLConnection uc) {
      try {
           InputStream is = uc.getInputStream();
           StringBuffer sb = new StringBuffer();
           int c;

           sb.append("[Content of " + uc.getURL() + "]\n\n");
           sb.append("[This opening message brought to you by your   
                    plain/text\n");
           sb.append("content handler. To remove this content handler,
                    delete the\n");
           sb.append("kr.re.etri.content.text directory from your class path
                    and\n");
           sb.append("the java.content.handler.pkgs property from your
                    HotJava\n");
           sb.append("properties file.]\n");
                   
           sb.append("-------------------------------------\n\n");

           while ((c = is.read()) >= 0) {
                sb.append((char)c);
           }

           is.close();

           return sb.toString();
        } catch (IOException e) {
            
           return "Problem reading document: " + uc.getURL();
        }
    }
}

4.3 클래스패스를 세팅

CLASSPATH 변수를 세팅한다. 유닉스인 경우,
        setenv CLASSPATH .:/home/developer/classes
도스 쉘(Windows 95/NT)인 경우,
        set CLASSPATH=.;C:\developer\classes

4.4 소스를 컴파일

자바 컴파일러를 이용해 plain.java 소스를 컴파일한다.
        javac plain.java

4.5 java.content.handler.pkgs 프라퍼티 세팅

핫자바 프라퍼티 파일은 유닉스냐 도스 쉘이냐에 따라 다음과 같이 위치하고 있다. 유닉스인 경우,
        $HOME/.hotjava/properties
도스 쉘(Windows 95/NT)인 경우,
        \.hotjava\properties
핫자바 프라퍼티 파일에 새로운 컨텐트 패키지인 kr.re.etri.content.text를 포함하도록 세팅한다.
        java.content.handler.pkgs=kr.re.etri.content

4.6 text/plain을 처리하는 특별한 mailcap 정보를 삭제

4.6 새롭게 핫자바를 구동

이제 컨텐트 핸들러는 핫자바가 plain/text 문서를 로드할 때마다 자동으로 불려진다. 예를 들어 다음과 같은 문서의 텍스트인 content.txt를 로드했다면,
        This document has two lines.  This is the first line.
        This is the second (and last) line.
핫자바 브라우저 내에는 단순히 위의 두줄만이 출력되는 것이 아니고 (코드 3)의 getContent() 메소드에서 정의된 출력폼대로 (그림 4)와 같은 결과가 출력된다.


(그림 4) text/plain 컨텐트 핸들러 결과 화면

5. 결론

본 논문에서는 핫자바의 프로토콜과 데이타 처리 확장을 위한 프로토콜 핸들러, 컨텐트 핸들러의 개념과 간단한 프로그래밍 방법론을 기술했다. 이 두가지 개념을 이용하면, 인터넷 환경에서 웹 브라우저를 보다 경력한 사용자 인터페이스 환경으로 확장할 수 있다. 현재까지 자바를 이용한 프로그램은 주로 애플릿으로만 국한되어 있으나, 조만간 핫자바 1.0 정식 버젼이 제공되고, 네트웍 컴퓨터의 상용화가 이루어지면, 이러한 프로토콜/컨텐트 핸들러의 프로그램은 더욱더 많이 개발되어 질 것이다.

참고문헌(References)

[SUN 96a]
SunSoft, Installing a Local Content Handler, 1996
URL:http://java.s un.com:80/HotJava/1.0-prebeta1/UsersGuide/handlers/content.html
[SUN 96b]
SunSoft, Installing a Local Protocol Handler, 1996
URL:http://java.s un.com:80/HotJava/1.0-prebeta1/UsersGuide/handlers/protocol.html
[SUN 96c]
SunSoft, HotJava(tm) User's Guide, 1996
URL:http://java.sun.com:80/ HotJava/1.0-prebeta1/UsersGuide/users.html
[JavaOne 96]
Pavani Diwanji, Dave Brown, Advanced Tutorial: Network-Based Applications, JavaOne Conference, 1996
URL:http://java.sun.com:80/javaone/pres/NetAp ps.pdf
[신명기 96]
신명기, 최희창, 자바사냥, 정보시대, 1996
URL:http://pec.etri.re.kr/~mkshin/publication/h unt.html