Hotjava Protocol and Content Handler
현재 대부분의 자바 프로그램은 자바 애플릿과 어플리케이션이 주류를 이루고 있다. 그러나 핫자바 브라우저의 동적인 프로토콜, 데이터 타입의 확장을 위해 사용되는 프로토콜 핸들러, 컨텐트 핸들러 또한 차세대 자바를 이용한 어플리케이션 확장 기술로 차후로는 많은 예제들이 쏟아져 나올 것이다. 프로토콜 핸들러나 컨텐트 핸들러 구현을 위한 플랫폼으로는 핫자바 브라우저만을 사용할 수 있다는 점을 유의하자. 본 논문에서는 이를 가능하게 하는 핫자바의 컨텐트 핸들러와 프로토콜 핸들러의 개념, 프로그래밍 방법등을 예제를 통해 기술한다.
(그림 1) 핫자바의 프로토콜, 데이타 처리 확장 기술
일반적인 웹 프로토콜(http, ftp, telnet, news, mailto ...)과 컨텐트 (html, text, gif, jpg ...)의 처리는 웹 브라우저의 고정화된 협상 과정을 통해 이루어진다. 반면, 핫자바 브라우저는 (그림 2)와 같은 동적인 협상과정이 이루어지므로, 새로운 프로토콜, 데이타를 확장할 수 있는 가능성이 생기게 된다.
(그림 2) 핫자바의 데이타, 프로토콜 처리
<protocol>://<host>/<file>
프로토콜 처리 루틴을 찾게 되며, 새로운 프로토콜인 경우, 해당하는
프로토콜 핸들러 루틴을 찾아 처리를 하게된다.
이러한 방식은 기존 프로토콜의 웹 기능 확장면 뿐만 아니라
사용자가 새롭게 정의한 프로토콜에 이루기까지 폭넓게 적용가능하다.
예를 들면 다음과 같은 적용이 가능한다.
기존 웹 브라우저에서는 http, ftp, telent와 같은 프로토콜들을 지원하고 있다. 본 논문에서는 finger 프로토콜을 위한 코드를 프로토콜 핸들러로 작성하여 핫자바에서 확장 사용이 가능하도록 인스톨시켜보자. 인터넷 도메인 etri.re.kr을 갖는 환경에서 finger 서비스를 위한 프로토콜 핸들러를 인스톨하여 처리하고자 한다면 다음과 같은 인스톨 과정과 프로그래밍 과정이 요구된다. 인터넷 도메인을 따라 프로토콜 핸들러를 작성하는 이유는 네이밍 충돌을 방지하기 위해서이다.
classes - kr - re - etri- protocol - finger
대개 디렉토리의 시작은 classes로 하며, 나머지 4가지 디렉토리 이름은
kr.re.etri.protocol 패키지를 생성하기 위한 이름들이다. 마지막 finger 디렉토리는
프로토콜 핸들러의 이름과 같아야 한다.
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;
}
}
setenv CLASSPATH .:/home/developer/classes
도스 쉘(Windows 95/NT)인 경우,
set CLASSPATH=.;C:\developer\classes
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
$HOME/.hotjava/properties
도스 쉘(Windows 95/NT)인 경우,
\.hotjava\properties
핫자바 프라퍼티 파일에 새로운 패키지인 kr.re.etri.protocol.finger를 포함하도록 세팅한다.
java.protocol.handler.pkgs=kr.re.etri.protocol
finger://mkshin@pec.etri.re.kr
핫자바의 결과 화면은 (그림 3)과 같다.
(그림 3) finger 프로토콜 핸들러 결과 화면
classes - kr - re - etri - content - text
대개 디렉토리는 classes로 시작하며, 나머지 4가지 디렉토리 이름은
kr.re.etri.content 패키지를 생성하기 위한 이름들이다.
마지막 text 디렉토리는 데이터의 MIME 타입과 같은 이름을 가져야 한다.
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
// 코드 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();
}
}
}
setenv CLASSPATH .:/home/developer/classes
도스 쉘(Windows 95/NT)인 경우,
set CLASSPATH=.;C:\developer\classes
javac plain.java
$HOME/.hotjava/properties
도스 쉘(Windows 95/NT)인 경우,
\.hotjava\properties
핫자바 프라퍼티 파일에 새로운 컨텐트 패키지인 kr.re.etri.content.text를 포함하도록 세팅한다.
java.content.handler.pkgs=kr.re.etri.content
This document has two lines. This is the first line.
This is the second (and last) line.
핫자바 브라우저 내에는 단순히 위의 두줄만이 출력되는 것이 아니고
(코드 3)의 getContent() 메소드에서 정의된 출력폼대로
(그림 4)와 같은 결과가 출력된다.
(그림 4) text/plain 컨텐트 핸들러 결과 화면