[4th Seoul Workshop]Jeeve Management and Servlet Programming

장규오
서울시 서대문구 충정로 3가 32-11 충정 B/D 2층
엘림네트 인터넷사업본부 웹서비스팀
E-mail: kojang@elim.net
URL: http://www.elim.net/~kojang/
Abstract
월드와이드웹이 보편화 되면서 웹 서버가 급증하게 되었고, 서버의 종류도 다양해지고 있다. 그렇지만, 운영과 개발의 여러가지 문제점과 불편한 점이 노출되고 있다. 이러한 문제점들을 자바로 개발된 서버를 이용했을때의 어떠한 장점이 있는지 알아본다.
자바로 개발된 웹서버인 Jeeve의 설치 방법과 운영 방법을 알아본 후, 기존의 CGI와 비슷한 형태의 자바 CGI프로그램인 Servlet 프로그래밍 기법과 Servlet의 장/단점을 알아본다.

Keywords:
WWW, Java, Jeeve, Servlet, CGI, Applet



1. Intallation

Jeeve는 Internet Information Server로써 웹서버와같은 기능을 수행하는 서버이다. 다음의 사이트에서 Jeeve를 다운 로드 받을 수 있다. 사용환경은 Windows95/NT, Solaris이며, 두 OS에 구분없이 실행할 수 있도록 되어있다.

Download
  • http://java.sun.com/products/jeeves/CurrentRelease/
  • ftp://ftp.javasoft.com/pub/
  • 압축파일을 풀어주는 것만으로 설치가 끝난다.
    Jeeve를 설치하고 실행 하기 위해서는 JDK가 이미 설치되어 있어야 하고, 압축을 풀면 'JeevesA1.2'디렉토리와 하위 디렉토리 몇개가 생성이 되는데, 각 디렉토리의 내용은 다음과 같다.
  • native : Native 메소드를 이용한 동적 라이브러리 개발에 필요한 C언어 루틴.
  • lib : 동적 라이브러리.
  • servlets : Servlet 예제 프로그램.
  • admin : Jeeve 환경 설정 파일
  • public_html : HTML 문서 디렉토리
  • cgi-bin : Perl로 작성된 CGI프로그램
  • icons : HTML 문서에서 사용하는 아이콘
  • etc : Jeeve 실행에 필요한 기타 파일
  • bin : Jeeve HTTPD 실행 프로그램
  • doc : Jeeve설치 및 환경설정, Servlet 개발에 필요한 API Document.
  • 일반적인 웹서버와 마찬가지로 Jeeve를 실행하기 위해서는 'logs'디렉토리를 생성해주어야 실행이 된다.
    그 밖의 환경설정은 기본값을 가지고 있고, 필요에 따라서 값을 변경할 수 있다. 'bin' 디렉토리의 'httpd'를 실행 시킨후 다음처럼 포트번호 '8888'로 접속하면 된다.
    http://ServerHostName:8888/



    2. Configuration

    최근에 등장하는 상용 웹서버는 환경 설정에 필요한 CGI 프로그램과 문서를 제공한다. Jeeve도 마찬가지로 환경설정에 필요한 애플릿과 문서들을 제공하는데, 이런 애플릿이 포함된 문서는, 서버를 실행 했을때 실행되는 기본 홈페이지에 있다.[그림 1]

    [그림 1] Jeeve의 기본 홈페이지
    [그림 1] Jeeve의 기본 홈페이지

    'Server Administration'부분에 가면 서버 관리를 위한 애플릿이 실행되는데 제일먼저 다음과 같은 대화상자가 나타나서 ID와 Password를 물어본다. 그러면, ID = 'admin', Password = 'admin' 를 입력하면 된다.

    [그림 2] Login to Administration

    [그림 2] Login to Administration



    다음에 설명되는 모든 환경 설정 기능은 바로 이 애플릿에서 모두 가능하므로 세세한 설명은 생략하고, 실제로 'admin' 디렉토리에는 서버의 환경 설정에 따른 다음과 같은 파일들이 있다.
  • httpd.properties
  • servlets.properties
  • rules.properties
  • aliases.properties
  • mime.properties
  • acl.properties
  • 이 파일들이 실제로 어떻게 저장되고 서버에 어떠한 영향을 미치는지에 대한 것들을 알아보겠다.

    2.1. Gerneral Server Configuration


    기본적인 Jeeve 서버의 환경 설정에 대해서 알아본다. 이미 다른 웹서버를 설치하고 사용해본 경험이 있다면 큰 어려움은 없다. 가장 중요한 환경 설정 파일은 admin/httpd.properties이다. 몇가지 환경설정이 가능한데, 크게 고치지 않아도 문안하며, 우선 '8888'포트 번호를 '80'으로 바꾸고, 몇가지를 바꾸어본다.
    admin/httpd.properties
  • server.port=80
  • server.backlog=50
  • server.min.threads=10
  • server.max.threads=50
  • server.timeout=60000
  • 포트 번호를 '80'으로 하면 웹서버에 접속할 때 포트 번호를 적지 않아도 된다. 따라서, 다음과 같이 접속할 수 있다. 설정 파일을 저장하고 서버를 다시 띄워본다.
    http://ServerHostName/

    [그림 3] Gerneral Server Configuration
    [그림 3] Gerneral Server Configuration



    2.2. Server Logging


    서버에 접속하면 'log' 디렉토리에 서버의 접속정보에 관한 파일이 생성된다. 각각의 파일의 의미는 다음과 같다.
  • access_log : 서버에 접속한 클라이언트의 정보가 쌓인다. 따라서, 누가 어떤 정보를 얼마나 자주 접속하는지 통게를 낼 수 있다.
  • error_log : 서버의 오류 메시지를 저장한다. 서버가 내부적인 오류를 발생시키고 비정상적으로 종료했을 경우, 오류를 찾는데 참고할 수 있다.
  • event_log : 서버와 Servlet에 의한 이벤트 발생 상황을 볼 수 있다.

  • [그림 3] Server Logging
    [그림 3] Server Logging

    2.3. Jeeves File Aliasing/Servlet Aliasing, Servlet Loading

    Alias기능은 local 디스크에 있는 파일이나 서브릿을 외부에서 URL로 찾아갈 수 있도록한다. 다음은 File Aliasing을 등록하는 aliases.properties파일과

    aliases.properties
         ......
    /=public_html
    /cgi-bin=cgi-bin
    /admin=admin/html
    /doc=doc
    /applet=servlets
    /icons=icons
    /~=public_html/usr
         ......
    
    서브릿은 관리자에 의해서 Jeeve서버가 실행될때 자동으로 로드할 수 있도록 환경설정을 해주어야 한다. local에 있는 서블릿과 remote에 있는 서블릿을 로드할 수 있다. 로드 하고자 하는 서브릿의 이름과 아규먼트, 클래스 이름을 패키지 이름까지 정확하게 적고(확장자 .class를 뺀다) 등록한다. 그러면, 다음과 같이 servlets.properties이 갱신된다.
    servlets.properties
    # Servlets Properties
    #
    # .codebase=URL where the code is located
    # .code=class name (foo or foo.class)
    # .initArgs=list of {name, value} pairs which can be accessed
    #                         by the servlet using the servlet API calls
    #
    # For example:
    # rcounter.codebase=http://www.javasoft.com/~pavani/classes/
    # rcounter.code=rcounter.class
    #
         ......
    # admin servlet
    admin.code=sun.server.http.AdminServlet
    
    ####################### Demo Servlets ############################
    # local counter
    counter.code=Counter
    counter.initArgs=\
            initial=0
    
    # phone servlet
    phone.code=PhoneServlet
    phone.initArgs=\
            phonelist=servlets/phonelist
         ......
    
    등록된 서블릿을 호출하기 위해서는 다음과 같은 URL형태로 오픈한다.
    http://<server-host-name>/servlet/<servletName>
    또한, 등록되어 로드된 서브릿은 리스트에서 삭제할 수도 있다.
    그런데 재미있는 것은 서블릿 예제 프로그램을 보면 위의 'counter' 서블릿을 호출할 때 counter.html 로 호출하게 된다. 이것이 가능한 이유는 rules.properties 때문이다. 웹서버용 애플릿 즉 서블릿을 호출하기 위해서는 2단계의 이름 변형작업이 들어간 것이다. 'counter' 서블릿의 경우 다음과 같다.
    counter.html(virtual-path) -> counter(servelt-name) -> Counter(class-file)
    rules.properties
    # Servlet rules properties
    #
    # This file specifies the translation rules for invoking servlets.
    # The syntax is:
    #
    #       =
    #
    # where  is the virtual path used to invoke the servlet,
    # and  is the name of the servlet. Surrounding white space
    # is ignored. The ordering of the rules is not important, as the longest
    # match will always be used first.
    
    /=file
    /cgi-bin=cgi
    /imagemap=imagemap
    /servlet=invoker
    /counter.html=counter
    /phone.html=phone
    /snoop.html=snoop
    /finger.html=finger
    /fun.html=fun
         ......
    
    [그림 4] Jeeves File Aliasing
    [그림 5] Jeeves Servlet Aliasing
    [그림 6] Servlet Loading
    [그림 4] Jeeves File Aliasing [그림 5] Jeeves Servlet Aliasing [그림 6] Servlet Loading

    2.4. Jeeves Mime Type Administration


    인터넷에 있는 다양한 형태의 파일을 구분해서 화면으로 출력 또는 응용프로그램을 호출하기 위해서, mime.properties파일에서 표준 맵핑이 가능하도록 정의한다. 이곳에 정의되지 않은 알려지지 않은 파일은 test/plain형태로 매핑된다. 따라서, 만약 .arj파일을 웹서버에서 깨지지 않고 전송하기 위해서는 arj=application/arj를 추가를 하면 된다. 다음은 이 파일의 일부분이다.
    mime.properties
    # Default Mime Type
    *=text/plain
    
    # Mime Types
    java=text/plain
    html=text/html
    htm=text/html
    body=text/html
    txt=text/plain
    rtx=text/richtext
    ps=application/x-postscript
    class=application/java
         ......
    

    [그림 7] Jeeves Mime Type Administration
    [그림 7] Jeeves Mime Type Administration

    2.5. Security Configuration

    Jeeves 역시 ACL(Access Control Lists)에 의해서 웹페이지나 서블릿과 같은 리소스를 사용할 수 있는 사용자를 등록/삭제할 수 있다. 각 사용자는 ID와 Password를 가질 수 있고, Realm을 이용해서 같은 ID로 서로다른 접근 영역을 지정할 수 있다. (Jeeves A1.2에서는 새로운 realm을 지정할 수 있는 기능은 없고 adminRealm, defaultRealm만 지원한다.)
    특별히 사용자의 권한중에서도 앞부분의 'admin'과 같은 관리자 권한을 부여할 수 가 있는데, 만약 실수를 해서 암호를 잊어버리면 다시는 더이상 서버 관리를 할 수 없으므로 조심히 등록해야 하고 실수가 없도록 유의해야 한다.
    여기 관리자 화면에서 갱신된 내용은 'admin/realms'디렉토리에 계정이 자동으로 등록된다. 이곳에는 암호화 되어 있는 파일도 있으므로 가급적 수정하지 않는 것이 좋다.

    [그림 8] User Account Management
    [그림 9] Group Management
    [그림 10] ACL Editing
    [그림 11] Resource Protection
    [그림 8] User Account Management
    [그림 9] Group Management
    [그림 10] ACL Editing
    [그림 11] Resource Protection



    3. Java Server and Servlets Architecture

    여기에서는 jeeve의 http 서버기능이 어떻게 구현되고, servlet 의 실행 구조에 대해서 알아본다. 특히, 자바의 독특한 특징중에서 자바 프로그램은 곧 라이브러리로써 프로그래밍에 사용할 수 있다는 점이다. 즉 Jeeve의 http internet server와 유사한 프로그램과 Servlet과 같은 프로그래밍을 할 수 있으므로, Jeeve와 Servlet의 API에 대해서 간단히 설명한다.

    일반적인 Http Server(Daemon)은 실행시키면 여러개의 프로세스가 생성되어 Client가 접속하기를 기다린다. Jeeve도 마찬가지로 여러곳에서 같은 port로 접속을 처리할 수 있도록 지원하는데 다른점은 Thread로 처리된다는 것이다. 자세히 말하면, 서버는 지속적으로 Client의 접속을 기다리고 있고 접속이 시도되면 스레드를 생성해서 서비스를 하고, 다시 접속을 기다리는 형태이다.

    Jeeve의 기본 기능 구현에 사용된 패키지는 다음과 같다.

    sun.server Package
  • Server : Multiple connection handler threads를 관리할 수 있는 network server기능.
  • ServerHandler : 위의 Server클래스로 부터 정보를 받아 새로운 핸들을 생성한다.
  • ServerParameters : 서버의 속성을 바꿀 수 있는 파라미터를 위한 클래스.
  • 위에서 설명한 바와같이 관리자에의해 등록된 서블릿은 서버가 실행 될때 자동으로 로드된다. 그밖에 서블릿은 필요시에 로드 된다. 따라서, 서블릿은 인터넷에서 자동으로 로드되어 실행 되는 애플릿과도 같은 바이트코드 형태의 객체이다.

    [그림 12] Architecture 1
    [그림 12] Architecture 1

    위의 그림처럼 Client로부터 서블릿을 사용하고자 하는 요청이 들어오면 Handler thread pool은 위에서 설명한 방식대로 URL형태의 서블릿을 rules.properties에 정의된 형태로 서블릿을 찾아 서비스를 해준다.

    [그림 13] Architecture 2
    [그림 13] Architecture 2

    그런데 Local과 Remote에 있는 서블릿은 실행 방식이 약간다른데, 위와 같이 Remote에 있는 서블릿은 보안에 문제가 있을수가 있으므로, Stub Servelt이 중간에 하나더 존재하게 된다.
    이러한 것들을 구현하기 위해서 sun.server.http 패키지를 사용한다.
    sun.server.http Package
  • AdminServlet
  • CgiServlet
  • CommonLog
  • Cookie
  • Dispatcher
  • FileServlet
  • FormServlet
  • HttpInputStream
  • HttpOutputStream
  • HttpRequest
  • HttpResponse
  • HttpSecurityAclInfo
  • HttpServer
  • HttpServerAdmin
  • HttpServerHandler
  • HttpServerParameters
  • HttpServletContext
  • HttpServletStub
  • ImagemapServlet
  • License
  • SSIncludeServlet
  • ServerSecurity
  • ServletSecurity
  • 일반적인 웹서버를 설치하면 imagemap과 같은 기본적인 CGI루틴이 존재하듯이, 서블릿 중에는 기본적인 Core Servelt이 있고, 다음과 같은 패키지 들이 있다. 패키지 중에는 위에서 간간히 설명한 패키지들도 포함되어 있다.

    Core Servlet
  • File Servlet
  • Invoker Servlet
  • SSInclude Servlet
  • Admin Servlet
  • CgiServlet
  • ImagemapServlet
  • Servlet packages
  • package java.security.acl
  • package java.servlet
  • package sun.server
  • package sun.server.html
  • package sun.server.http
  • package sun.server.http.security
  • package sun.server.misc
  • package sun.server.util



  • 4. Servlet Programming

    위에서 소개한 수많은 패키지속의 클래스들을 한꺼번에 익히는 것은 시간적으로 어쩌면 불가능 하다고도 볼 수 있다. 그렇다면, 가장 좋은 방법은 기본적인 프로그래밍 기법만 익히고 API Document를 필요시에 찾아보면서 필요한 서블릿을 개발하는 것이다. 그러면, 이제부터는 Sun에서 예제로 내놓고 있는 서블릿 프로그램중 몇개를 살펴 보겠다.

    4.1. Simple Servlet

    source code
    import java.io.*;
    import java.servlet.*;
    import sun.server.html.*;
    
    public class SimpleServlet extends Servlet { 
    
        public void service(ServletRequest req, ServletResponse res)
            throws Exception 
        {
            res.setContentType("text/html");
            res.writeHeaders();
            OutputStream out = res.getOutputStream();
            String title = "SimpleServlet Output";
    
            HtmlPage page = new HtmlPage(title);
            page.addTagPair("H1", title);
            page.addText("<P>This is output from SimpleServlet.");
            
            page.write(out);
            out.flush();
        }
    
        public String getServletInfo() {
            return "A simple servlet";
        }
    
    }
    
    HTML tag
    <a href="/servlet/simpleservlet">Try it</a>
    
    [그림 14] Simple Servlet의 실행화면
    [그림 14] Simple Servlet의 실행화면

    4.2. Snoop Servlet

    source code
    import java.io.*;
    import java.util.*;
    import java.servlet.*;
    
    public class SnoopServlet extends Servlet {
        public void service(ServletRequest req, ServletResponse res)
            throws IOException
        {
            res.setContentType("text/html");
            res.setHeader("Pragma", "no-cache");
            res.writeHeaders();
            PrintStream out = new PrintStream(res.getOutputStream());
            out.println("<html>");
            out.println("<head><title>Snoop Servlet</title></head>");
            out.println("<body>");
            out.println("<h1>Request information:</h1>");
            out.println("<pre>");
            print(out, "Request method", req.getMethod());
            print(out, "Request URI", req.getRequestURI());
            print(out, "Request protocol", req.getProtocol());
            print(out, "Servlet path", req.getServletPath());
            print(out, "Path info", req.getPathInfo());
            print(out, "Path translated", req.getPathTranslated());
            print(out, "Query string", req.getQueryString());
            print(out, "Content length", req.getContentLength());
            print(out, "Content type", req.getContentType());
            print(out, "Server name", req.getServerName());
            print(out, "Server port", req.getServerPort());
            print(out, "Remote user", req.getRemoteUser());
            print(out, "Remote address", req.getRemoteAddr());
            print(out, "Remote host", req.getRemoteHost());
            print(out, "Authorization scheme", req.getAuthType());
            out.println("</pre>");
            /*
            Hashtable params = req.getQueryParameters();
            if (params.size() > 0) {
                out.println("<h1>Servlet parameters:</h1>");
                out.println("<pre>");
                Enumeration e = params.keys();
                while (e.hasMoreElements()) {
                    String name = (String)e.nextElement();
                    out.println(" " + name + " = " + (String)params.get(name));
                }
                out.println("</pre>");
            }
            */
            if (!req.getProtocol().equals("HTTP/0.9")) {
                out.println("<h1>Request headers:</h1>");
                out.println("<pre>");
                String name;
                for (int i = 0; (name = req.getHeaderName(i)) != null; i++) {
                    out.println(" " + name + ": " + req.getHeader(name));
                }
                out.println("</pre>");
            }
            out.println("</body></html>");
        }
    
        private void print(PrintStream out, String name, String value) {
            out.print(" " + name + ": ");
            out.println(value == null ? "<none>" : value);
        }
    
        private void print(PrintStream out, String name, int value) {
            out.print(" " + name + ": ");
            if (value == -1) {
                out.println("<none>");
            } else {
                out.println(value);
            }
        }
    
        private static final String UNKNOWN = "<unknown>";
    
        public String getServletInfo() {
            return "A servlet that shows the request headers sent by the client";
        }
    }
    
    HTML tag
    <a href="/snoop.html">Try it</a>
    
    [그림 15] Snoop Servlet의 실행화면
    [그림 15] Snoop Servlet의 실행화면




    4.3. Fun Servlet

    source code
    import java.io.*;
    import java.servlet.*;
    import sun.server.html.*;
    
    public class FunServlet extends Servlet { 
    
       public void service(ServletRequest req, ServletResponse res)
         throws Exception
       {
         String line;
         res.setContentType("text/html");
         res.writeHeaders();
         OutputStream out = res.getOutputStream();
         DataInputStream in = new DataInputStream(req.getInputStream());
    
         String title = "Servlet Demo";
         HtmlPage page = new HtmlPage(title);
         
         //
         // Spew out html code that will make the browser load an applet.
         // Setup the codebase right. Will work only in Java enabled browsers
         //
         page.addText("<applet code=NervousApplet.class codebase=\"../applet/\" height=200 width=1800>");
         page.addText("<param name=text value=\"Jeeves can load Servlets!\">");
         page.addText("</applet>");
         page.write(out); 
       }
    }
    
    HTML tag
    <a href="/fun.html">Try it</a>
    
    [그림 16] Fun Servlet의 실행화면
    [그림 16] Fun Servlet의 실행화면

    4.4. Finger Servlet

    source code
    import java.io.*;
    import java.servlet.*;
    import sun.server.html.*;
    
    public class FunServlet extends Servlet { 
    
       public void service(ServletRequest req, ServletResponse res)
         throws Exception
       {
         String line;
         res.setContentType("text/html");
         res.writeHeaders();
         OutputStream out = res.getOutputStream();
         DataInputStream in = new DataInputStream(req.getInputStream());
    
         String title = "Servlet Demo";
         HtmlPage page = new HtmlPage(title);
         
         //
         // Spew out html code that will make the browser load an applet.
         // Setup the codebase right. Will work only in Java enabled browsers
         //
         page.addText("<applet code=NervousApplet.class codebase=\"../applet/\" height=200 width=1800>");
         page.addText("<param name=text value=\"Jeeves can load Servlets!\">");
         page.addText("</applet>");
         page.write(out); 
       }
    }
    
    HTML tag
    <form action="/finger.html" method="GET">
    <table>
    <tr><td>Username:</td><td><input type="TEXT" name="user" size=10></td></tr>
    <tr><td>Hostname:</td><td><input type="TEXT" name="hosts" size=10></td>
    <td>leave blank for local host<br>
    multiple hostnames may be separted by commas</td></tr>
    <tr><td>Verbose:</td><td><input type="checkbox" name="verbose" value="yes" CHECKED></td></tr>
    </table>
    <input type="SUBMIT" value="Try it">,
    </form>
    
    [그림 17] Finger Servlet의 HTML Form
    [그림 18] Finger Servlet의 실행화면
    [그림 17] Finger Servlet의 HTML Form
    [그림 18] Finger Servlet의 실행화면



    5. Summary

    지금까지 웹 서버로써 Jeeve와 기존의 CGI와 비슷한 형태의 Java 응용 프로그램인 Servlet에 대해서 알아보았다. CGI 프로그램을 실행하면 새롭게 프로그램이 Load되어 실행되고 여러명의 사용자가 같은 CGI를 실행하더라도 각각의 CGI프로그램이 새롭게 로드되어 실행되는 반면 Servlet은 여러가지 장점을 가지고 있다.

    Servlet은 처음 실행되면 Server가 down될때 까지 계속 실행되어, 여러명의 다른 사용자가 Servlet을 접속하면 Thread를 생성하여 서비스한다. 마치 Daemon과 같은 기능을 유지한다. 또한, Servlet은 지속적으로 실행되고 있으므로 HTTPD의 근본적인 문제인 Connection을 유지할 수 있다는 장점을 가지고 있다. 한편, Jeeve와 Servlet은 모두 자바로 개발되었기 때문의 기타 자바의 장점인 플랫폼 독립적이고(새롭게 컴파일할 필요가 없다.), 동적이며 확장성/이식성/유지보수 기능이 뛰어나며, 프로그램 사이즈가 아주 작기 때문에 시스템 부하가 작다. 아직까지 정식 버전의 Jeeve가 나오지 않았지만, 자바로 개발된 HotJava 웹 브라우저와 함께 앞으로 크게 기대된다.

    그밖의 새로운 소식이나 궁금한 사항은 메일링 리스트를 통해서 얻을 수 있다.

    JavaSoft jeeve mailing :
  • listserv@java.sun.com
  • jeeves@java.sun.com
    or
    jeeves-interest@java.sun.com
  • subscribe jeeves-announce your real name
    or
    subscribe jeeves-interest your real name
    JavaKorea mailing :
  • majordomo@elim.net
  • java@elim.net
  • subscribe java




  • 참고문헌(References)

    [JavaSoft Home Page]
    URL: http://www.javasoft.com/
    JDK와 Jeeve 및 기타 JavaSoft의 제품과 소프트웨어의 정보를 얻을 수 있다.
    [Jeeves Home Page]
    URL: http://www.javasoft.com/products/jeeves/index.html
    Jeeve에 대한 문서와 관련 모임들에 대한 모든 자료를 얻을 수 있다.
    [Gamelan : Programming in Java]
    URL: http://www-b.gamelan.com/pages/Gamelan.programming.libraries.html
    Java Programming에 도움이 될만한 자바 프로그램과 문서들을 모아놓았다.

    Last-Modified : Saturday, 22-October-96 04:00:00