13장. 간단한 웹 애플리케이션

자이썬을 사용하는 주요한 이점은 자바 플랫폼의 능력을 자바가 아닌 파이썬 프로그래밍 언어로 프로그래밍할 수 있다는 점이다. 오늘날 자바 세계에서, 가장 널리 사용되는 웹 개발 기법은 자바 서블릿이다. JavaEE에서는, HTML이나 기타 마크업 언어를 작성하기 위하여 순수 자바 서블릿을 작성하기보다는 여러 기법과 프레임워크를 사용한다. 그러나, 때로는 순수 자바 서블릿을 작성하는 것이 여전히 강점을 갖기도 한다. 서블릿을 작성하기 위하여 자이썬을 사용할 수 있으며, 이는 파이썬 언어의 기능을 사용할 수 있기 때문에, 자바가 줄 수 있는 것에 비하여 더욱 많은 이점을 가져다준다. 마찬가지로, 순수 자바 대신에 웹 시작 애플리케이션을 자이썬을 사용하여 작성함으로써 삶을 보다 편안하게 할 수 있다. 이러한 애플리케이션을 순수 자바로 작성하는 것은 어렵고 때로는 험난하기도 한 작업이다. 자이썬에서 사용가능한 몇 가지 기법을 활용하여 삶을 보다 편안하게 할 수 있다. 심지어는 자이썬 프로젝트에서 modjy 통합을 사용함으로써 WSGI 애플리케이션을 자이썬으로 작성할 수도 있다.

이 장에서는 자이썬을 사용하여 간단한 웹 애플리케이션을 작성하는 세 가지 기법, 즉 서블릿, 웹 스타트, WSGI를 살펴볼 것이다. 이러한 기법들을 사용하는 방법을 깊이 살펴볼 것이지만, 각 솔루션의 배포에 대해서는 17장에서 다룰 것이다.

서블릿

서블릿은 자바 환경에서 웹 기반 애플리케이션을 구축하는 데에 쓰이는 기술이다. 컨텐츠를 웹에 올리기 위한 기술로서 플랫폼과 서버에 대하여 독립적인 것이 있다. 자바 서블릿에 대하여 잘 알지 못하고 있다면, 그것을 배우는데에 시간을 들일 가치가 있을 것이다. 위키피디아(http://en.wikipedia.org/wiki/Java_Servlet)를 포함하여 자바 서블릿에 대한 훌륭한 자료들이 있다. 자이썬으로 서블릿을 작성하는 것은 웹 애플리케이션 내에서 자이썬을 사용하는 생산적이고도 쉬운 방법이다. 자바 서블릿은 더 이상 자바로 직접 작성되지는 않는 추세이다. 많은 자바 개발자들이 자바 코드만 사용하기 보다는, 자바 서버 페이지(JSP), 자바 서버 Faces(JSF), 또는 다른 프레임워크를 통하여 마크업 언어를 사용하여 웹 컨텐츠를 다루고 있다. 그렇지만, 순수 자바 서블릿을 사용하는 것이 꽤 유용한 경우도 있다. 그러한 경우에 자이썬을 사용한다면 우리의 삶이 보다 편해질 것이다. JSP에 대한 훌륭한 용례들이 있으며, 그와 비슷하게, JSP 코드의 로직을 구현하는 데에 자이썬을 사용할 수 있다. 후자의 기술은, 앞단의 마크업을 구현 로직으로부터 분리하는 모델-뷰-컨트롤러(MVC) 패러다임을 우리의 프로그래밍 모델에 적용할 수 있도록 해준다. 각각의 기법은 구현이 쉬우며, 이러한 기능을 기존의 자바 웹 애플리케이션에 문제 없이 적용할 수도 있다.

자이썬 서블릿을 사용함으로써 얻을 수 있는 다른 기능은 동적 테스팅이다. 자이썬은 실행시간에 컴파일되므로, 우리는 코드를 새로 컴파일하여 웹 애플리케이션에 다시 배포할 필요 없이 바로 변경할 수 있다. 이는 웹 애플리케이션의 테스트를 매우 편리하게 만들어주는데, 그 이유는 서블릿 컨테이너에 배포하고 테스트하기까지 기다려야하는 시간이 웹 애플리케이션 개발에 있어서 가장 고통스러운 부분이기 때문이다.

웹 애플리케이션에서 자이썬 서블릿 구성

웹 애플리케이션을 자이썬 서블릿과 호환되도록 하는 데에는 아주 약간의 수고가 필요하다. 자이썬은 PyServlet 내장 클래스를 갖고 있는데, 이것은 자이썬 소스 파일로부터 자바 서블릿을 생성해준다. PyServlet을 사용하기 위해서는 애플리케이션의 web.xml 파일에 PyServlet 클래스가 실행시간에 .py 확장자가 붙은 파일을 적재하도록 설정하는 XML을 간단히 추가하면 된다. 이러한 구성을 애플리케이션에 일단 추가하고, jython.jar를 CLASSPATH에 추가하면 자이썬 서블릿을 사용할 준비가 된 것이다. 예제 13-1을 보자.

예제 13-1. 웹 애플리케이션에서 자이썬을 사용할 수 있도록 구성

<servlet>
    <servlet-name>PyServlet</servlet-name>
    <servlet-class>org.python.util.PyServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>PyServlet</servlet-name>
    <url-pattern>*.py</url-pattern>
</servlet-mapping>

이와 같이 자이썬 서블릿 컨테이너에 의하여 사용될 어떠한 서블릿이든지 web.xml 파일에 추가하여, URL을 통하여 서블릿을 대응시킨다. 이 책의 목적을 위하여, 우리는 다음 섹션에서 NewJythonServlet라는 이름의 서블릿을 작성할 것이며, 따라서 다음과 같은 XML 구성을 web.xml 파일에 추가해야 한다. 예제 13-2를 보라.

예제 13-2. 자이썬 서블릿 코딩

<servlet>
    <servlet-name>NewJythonServlet</servlet-name>
    <servlet-class>NewJythonServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>NewJythonServlet</servlet-name>
    <url-pattern>/NewJythonServlet</url-pattern>
</servlet-mapping>

간단한 서블릿 작성

서블릿을 작성하려면, javax.servlet.http.HttpServlet 추상 자바 클래스를 CLASSPATH 내에 둠으로써 자이썬 서블릿이 그것을 확장하여 코드의 환경을 준비할 수 있도록 할 필요가 있다. 이 추상 클래스는, 다른 서블릿 구현 클래스와 함께 servlet-api.jar 파일에 속해 있다. 이 추상 클래스를 상속하는 어떠한 자바 서블릿이든지 반드시 구현해야하는 두 개의 메소드가 있는데, 그것은 바로 doGetdoPost이다. 전자는 서블릿을 위해 HTTP GET 동작을, 후자는 HTTP POST 동작을 수행한다. 그외에 doPut, doDeletegetServletInfo 메소드도 구현하는 것이 일반적이다. doPut 메소드는 HTTP PUT 동작을 수행하며, doDelete는 HTTP DELETE를, getServletInfo는 서블릿에 대한 설명을 제공한다. 다음의 예제와 같이 doGetdoPost만 사용하는 경우도 많다.

자, 매우 간단한 자바 서블릿 코드를 살펴보도록 하자. 이 서블릿은 웹 애플리케이션에서의 자신의 위치와 이름을 화면에 출력하는 기능만을 가지고 있다. 그 코드를 뒤따르는 것은 비교를 위하여 동일한 서블릿을 자이썬으로 작성한 것이다(예제 13-3).

예제 13-3. NewJavaServlet.java

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class NewJavaServlet extends HttpServlet {
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        try {
            out.println("<html>");
            out.println("<head>");
            out.println("<title>Servlet NewJavaServlet Test</title>");
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>Servlet NewJavaServlet at " +
            request.getContextPath () + "</h1>");
            out.println("</body>");
            out.println("</html>");
        } finally {
            out.close();
        }
    }

    @Override
    protected void doGet(HttpServletRequest request,
    HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request,
    HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    public String getServletInfo() {
        return "Short description";
    }
}

코드를 짧게 만들기 위하여 모든 주석은 제거하였다. 그러면, 예제 13-4에서 자이썬으로 작성한 동등한 서블릿을 살펴보도록 하자.

예제 13-4.

from javax.servlet.http import HttpServlet

class NewJythonServlet (HttpServlet):
    def doGet(self,request,response):
        self.doPost (request,response)

    def doPost(self,request,response):
        toClient = response.getWriter()
        response.setContentType ("text/html")
        toClient.println ("<html><head><title>Jython Servlet Test</title>"
        +
        "<body><h1>Servlet Jython Servlet at" +
        request.getContextPath() + "</h1></body></html>")

    def getServletInfo(self):
        return "Short Description"

간결한 코드도 그렇거니와, 동적인 서블릿을 가지고 작업하는 개발주기 또한 매력적이다. 앞서 기술한 바와 같이, 자이썬은 실행시간에 컴파일되므로 변경이 일어날 때마다 재배포할 필요가 없다. 자이썬 서블릿을 수정하고, 저장하고, 웹페이지를 새로고침하기만 하면 변경사항의 확인이 가능하다. 가능성에 대하여 생각하기 시작했다면, 위에서 예로 든 것은 간단한 예제일 뿐이며, 자바로 할 수 있었던 것은 무엇이든 파이썬 언어를 사용하여 자이썬 서블릿으로 해낼 수 있다는 것을 깨닫게 될 것이다.

자이썬 서블릿에 대하여 요약하자면, 우선 jython.jarservlet-api.jar를 CLASSPATH에 둔다. 그리고 필요한 XML을 web.xml에 두고, 끝으로 javax.servlet.http.HttpServlet 추상 클래스를 확장하여 서블릿을 작성하면 된다.

자이썬으로 JSP 사용하기

자이썬 서블릿은 보다 생산적인 개발 라이프사이클을 가져다주지만, 어떤 경우에는 앞단의 웹 코드를 다루는 데에 자이썬 코드가 가장 편리한 방법이 아닐 수도 있다. 때때로 HTML과 같은 마크업 언어가 복잡한 앞단의 개발에 나을 때도 있다. 예를 들어, 자이썬 서블릿 내에 자바스크립트를 포함하는 것은 쉽다. 그렇지만, 모든 자바스크립트 코드를 String으로 작성하여야 한다. IDE가 제공하는 구문 강조와 자동 채움 기능도 활용하지 못할 뿐만 아니라, 코드를 읽고 이해하는 데에도 어려움이 있다. 이러한 코드를 자이썬 또는 자바와 분리하는 것이 보다 읽기 쉽고, 장기간 유지보수하기 쉽다. 한 가지 가능한 해결책은 장고와 같은 파이썬 템플릿 언어를 택하는 것이 될 수 있지만, 자바 서버 페이지(JSP) 기술 또한 훌륭한 해결책이 될 수 있다.

JSP를 사용함으로써 자바 코드를 HTML 마크업에 통합하여 동적인 페이지 내용을 생성해낼 수 있다. 그렇다고 JSP를 맹목적으로 추종하는 것은 아니다. JSP는, 올바르게 사용하지 않을 경우에는 코드를 실재하는 악몽으로 만들어버릴 수 있다. JSP가 자바스크립트, HTML 및 자바를 한 개의 파일에 섞기에 편리하지만, 이는 유지보수를 매우 어렵게 만든다. 자바 코드를 HTML이나 자바스크립트와 뒤섞는 것은 좋지 못하다. 마찬가지로 자이썬을 HTML이나 자바스크립트와 뒤섞는 것도 좋지 않다.

모델-뷰-컨트롤러 (MVC) 패러다임을 통해, 자바나 자이썬으로 구현한 로직과 HTML 등의 마크업을 깔끔하게 분리할 수 있다. 자바스크립트는 항상 HTML과 따라가는데 이는 클라이언트측 스크립트 언어이기 때문이다. 달리 말해서, 자바스크립트 코드는 로직 코드와 분리된다고 할 수 있다. MVC의 관점에서, 컨트롤러 코드는 마크업 및 자바스크립트 코드로서, 최종사용자로부터 데이터를 획득하는 데에 사용된다. 모델 코드는 데이터를 조작하는 업무 로직이다. 모델 코드는 자이썬 또는 자바 내에 들어간다. 뷰는 결과를 표출하는 마크업 및 자바스크립트이다.

MVC를 사용한 깔끔한 분리를 통하여 JSP와 자이썬 서블릿을 성공적으로 함께 사용할 수 있다. 이 섹션에서는 그에 대한 간단한 예제를 살펴보도록 하겠다. 다른 많은 예제와 마찬가지로, 훌륭한 기능을 겉핡기로만 다룬다. JSP와 자이썬 서블릿을 함께 사용하는 방법을 배운 후에는 더 깊이 탐험해볼 수 있을 것이다.

JSP를 위한 설정

자이썬 서블릿을 사용하기 위해 웹 애플리케이션에서 특별히 설정해야할 것은 없다. 필요한 XML을 web.xml 배포 기술자에 추가하고, 올바른 JAR를 애플리케이션에 포함시키고, 코딩을 시작하면 된다. 주의하여야 할 점은 자이썬 서블릿에서 사용할 .py 파일이 반드시 CLASSPATH 내에 있어야한다는 것이다. 자이썬 서블릿을 JSP 웹 페이지와 같은 디렉토리에 두는 것이 일반적이다. 이는 일들을 편하게 만들 수 있지만, 코드를 관리하는 데에 있어 패키지를 사용하지 않았기 때문에 마음에 들지 않을 수도 있다. 여기서는 단순성을 위해서 서블릿 코드를 JSP와 동일한 디렉토리에 두지만, 여러분은 다른 방법을 취할 수도 있을 것이다.

컨트롤러/뷰 작성

애플리케이션의 뷰 부분은 마크업과 자바스크립트 코드를 사용하여 작성할 수 있다. 이러한 기법은 JSP를 활용하여 마크업을 수용하며, 자바스크립트는 JSP 내에 직접 포함하거나 별도의 .js 파일에 둘 수 있다. 깔끔한 후자의 기법을 선호하지만, 많은 웹 애플리케이션에서 페이지 내에 약간의 자바스크립트를 포함한다.

이 예제의 JSP는 보다 단순하여, 예제 내에는 자바스크립트를 사용하지 않으며 두 군데의 입력 텍스트 영역만을 갖는다. 이 JSP는 두 개의 양식을 갖는데, 이는 페이지에 두 개의 분리된 제출 단추를 갖기 때문이다. 각 양식은 서로 다른 자이썬 서블릿으로 리다이렉트되며, 입력 텍스트에서 주어진 자료를 가지고 어떤 일을 수행한다. 예제의 첫 번째 양식에는 사용자가 어떠한 텍스트든지 입력할 수 있는 작은 텍스트상자가 있으며 제출 버튼을 누르면 페이지에 다시 출력된다. 아주 쿨하지 않은가? 썩 훌륭하지는 않아도, JSP와 서블릿 구현 간의 연동을 배우기에는 좋은 예이다. 두 번째 양식에는 사용자가 숫자를 넣을 수 있는 두 개의 텍스트 상자가 있고, 제출 단추를 누르면 값들을 다른 서블릿으로 전달하여 두 수의 합을 반환한다. 예제 13-5는 이러한 단순한 JSP의 코드이다.

예제 13-5. 단순한 컨트롤러/뷰어 애플리케이션의 JSP 코드

testJSP.jsp

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Jython JSP Test</title>
    </head>
    <body>
        <form method="GET" action="add_to_page.py">
            <input type="text" name="p">
            <input type="submit">
        </form>
        <br/>
        <p>${page_text}</p>
        <br/>
        <form method="GET" action="add_numbers.py">
            <input type="text" name="x">
            +
            <input type="text" name="y">
            =
            ${sum}
            <br/>
            <input type="submit" title="Add Numbers">
        </form>
    </body>
</html>

이 JSP 예제에서, 첫 번째 양식이 컨트롤러 역할을 하는 add_to_page.py라는 이름의 자이썬 서블릿으로 리다이렉트함을 볼 수 있을 것이다. 이 경우에, p라는 이름의 입력 텍스트 상자 내에 포함된 텍스트는 서블릿을 전달되어, 페이지에 다시 표출된다. 다시 표출될 텍스트는 page_text라는 이름의 어트리뷰트에 저장되며, JSP 페이지 내에서 ${} 표기에 의해 참조됨을 볼 수 있을 것이다. 예제 13-6은 add_to_page.py의 코드이다.

예제 13-6. 단순한 자이썬 컨트롤러 서블릿

#######################################################################
# add_to_page.py
#
# Simple servlet that takes some text from a web page and redisplays
# it.
#######################################################################
import java, javax, sys

class add_to_page(javax.servlet.http.HttpServlet):
    def doGet(self, request, response):
        self.doPost(request, response)

    def doPost(self, request, response):
        addtext = request.getParameter("p")
        if not addtext:
            addtext = ""
        request.setAttribute("page_text", addtext)
        dispatcher = request.getRequestDispatcher("testJython.jsp")
        dispatcher.forward(request, response)

빠르고 단순하게, 서블릿은 요청을 받아서 매개변수 p 내에 포함된 값을 얻는다. 그런 다음 그 값을 addtext라는 이름의 변수에 할당한다. 그러면 이 변수는 page_text라는 이름의 어트리뷰트에 할당하고 testJython.jsp 페이지로 역으로 전달한다. 이 코드는 깊이 있는 애플리케이션을 만들고자하는 경우에는 다른 JSP로 쉽게 전달할 수 있다.

JSP의 두 번째 양식은 두 값을 취하여 합을 페이지로 반환한다. 누군가가 텍스트 상자에 숫자 값이 아닌 텍스트를 입력하였다면 합 대신에 오류 메시지를 표시한다. 매우 단순하기는 하지만, 이 서블릿은 데이터베이스 호출 등을 포함하는 어떠한 업무로직이라도 코딩할 수 있음을 보여준다. 예제 13-7을 보라.

예제 13-7. 자이썬 서블릿 업무 로직

#######################################################################
# add_numbers.py
#
# Calculates the sum for two numbers and returns it.
#######################################################################
import javax

class add_numbers(javax.servlet.http.HttpServlet):
    def doGet(self, request, response):
        self.doPost(request, response)

    def doPost(self, request, response):
        x = request.getParameter("x")
        y = request.getParameter("y")
        if not x or not y:
            sum = "<font color='red'>You must place numbers in each value box</font>"
        else:
            try:
                sum = int(x) + int(y)
            except ValueError, e:
                sum = "<font color='red'>You must place numbers only in each value box</font>"
        request.setAttribute("sum", sum)

        dispatcher = request.getRequestDispatcher("testJython.jsp")
        dispatcher.forward(request, response)

JSP와 서블릿을 앞의 자이썬 서블릿 섹션에서 생성한 웹 애플리케이션에 추가하면, 이 예제는 바로 동작할 것이다.

코드를 내장시켜주는 스크립틀릿이라고 하는 다양한 템플릿 태그를 사용하여 자바 서버 페이지에 코드를 포함시키는 것도 가능하다. 그러한 경우에, 빈 스크립팅 프레임워크(http://jakarta.apache.org/bsf/)와 같은 특수한 프레임워크를 사용하지 않는 한 JSP에는 반드시 자바 코드를 포함하여야 한다. 자바 서블릿 페이지에 대한 더 자세한 내용은 선 마이크로 시스템즈의 JSP 문서(http://java.sun.com/products/jsp/docs.html)나 Apress의 Beginning JSP, JSF and Tomcat Web Development: From Novice to Professional와 같은 책을 참고하기 바란다.

애플릿과 자바 웹 스타트

글을 쓰고 있는 현재, 자이썬 2.5.0에서는 애플릿을 사용할 수 없다. 그 이유는 애플릿은 정적으로 컴파일되어야하며 웹 페이지에 포함하려면 <applet> 또는 <object> 태그를 사용하여야 하기 때문이다. jythonc라는 정적 컴파일러는 더 나은 기술의 구현을 위해 자이썬 2.5.0에서 제거되었다. Jythonc는 자이썬 애플릿을 정적으로 컴파일하는 것과 같은 특정 작업을 수행하기에는 좋았지만, 자이썬과 자바의 통합과 같은 단순한 작업을 수행하기에는 불필요한 개별적인 컴파일 단계였기 때문에 개발 주기의 단절을 가져왔다. 머지 않은 미래의 자이썬 릴리스, 즉 2.5.1 또는 그 이상에는 애플릿을 위한 정적 컴파일을 수행하는 더 나은 방법이 포함될 것이다.

현재로서는, 자이썬 애플릿을 개발하기 위해서는 jythonc를 포함하는 이전의 배포본을 사용하여 웹페이지에 <applet> 또는 <object> 태그를 사용하여 연관시켜야 한다. 자이썬에서는, 표준 자바 애플릿과 상당히 비슷한 방식으로 애플릿을 작성한다. 하지만, 결과적인 행 수는 정교한 구문을 가진 자이썬 쪽이 훨씬 적다. 그와 같은 이유로, 일반적으로 자이썬으로 GUI를 개발하는 것이 자바 스윙 애플리케이션을 개발하는 것에 비하여 생산성이 크게 높다. 이것이 바로 애플릿을 자이썬으로 코딩하는 것이 좋은 해결책이며 그냥 지나쳐서는 안되는 이유이다.

GUI 기반의 에플리케이션을 웹에 분배하는 또 다른 방법으로는 자바 웹 스타트 기술의 사용을 들 수 있다. 웹 스타트 애플리케이션의 유일한 단점은 웹 페이지에 직접적으로 포함시킬 수 없다는 점이다. 웹 스타트 애플리케이션은 컨텐트를 클라이언트의 데스크톱에 다운로드한 다음 클라이언트의 지역 JVM에서 수행한다. 자바 웹 스타트 애플리케이션의 개발은 독립실행 데스크톱 애플리케이션의 개발과 다르지 않다. 사용자 인터페이스는 애플릿 사용자 인터페이스를 코딩하는 것과 마찬가지로 자이썬과 자바 스윙으로 코딩할 수 있다. 웹 스타트 애플리케이션을 배포할 준비가 되면, 배포에 사용되는 자바 네트워크 론칭 프로토콜 (JNLP) 파일을 생성하여 애플리케이션과 함께 제공할 필요가 있다. 그러한 일을 마친 후에, 웹 서버의 묶음을 복사하고 애플리케이션을 띄우는 데 사용할 웹 페이지를 생성한다.

이 섹션에서는 작은 웹 스타트 애플리케이션의 개발을 통하여, 개체 팩토리 디자인 패턴 및 독립실행 자이썬 JAR 파일과 함께 순수 자이썬을 사용하여 배포하는 예를 살펴볼 것이다. 그러한 목적을 달성하는 데에는 다른 방법도 있으며, 여기서는 다만 두 가지의 가능한 구현을 예시하였음을 주지하기 바란다.

단순한 GUI 기반 웹 애플리케이션의 작성

이 예제에서 개발하는 웹 스타트 애플리케이션은 매우 단순하지만, 여러분이 원한다면 더욱 발전시킬 수 있다. 이 섹션의 목적은 웹 기반의 GUI 애플리케이션을 어떻게 개발하는지를 보이는 것보다는, 그러한 애플리케이션을 개발하는 과정을 보이는 데에 있다. 실제로, GUI 장에서 논의하였던 어떠한 스윙 기반 애플리케이션이든 웹 스타트 기법을 사용하여 아주 쉽게 배포할 수 있다. 이전 섹션에서 기술한 바와 같이, 자이썬 웹 스타트 애플리케이션을 배포하는 데에는 여러 다른 방법이 있다. 우리는 간단한 자이썬 스윙 애플리케이션을 생성하기 위하여 개체 팩토리 디자인 패턴을 사용하는 쪽을 선호한다. 하지만, 모든 .py 파일을 사용하고 자이썬 독립실행 JAR 파일을 사용하여 배포하는 것 또한 가능하다. 이 섹션에서 그러한 기법들 각각에 대하여 논의할 것이다. 자바와 자이썬 코드를 혼합할 것이라면 개체 팩토리 패턴이 최선이라는 점을 알아냈다. JAR 방법은 엄격한 자이썬 애플리케이션을 개발할 때에 가장 잘 작동할 것이다.

개체 팩토리 애플리케이션 디자인

이 섹션에서 개발할 애플리케이션은 간단한 GUI로서 한 줄의 텍스트를 받아서 그것을 다시 JTextArea에 출력한다. 애플리케이션의 개발에는 넷빈스 6.7을 사용하며, 이 섹션의 일부는 그 IDE에서 제공하는 특정 기능을 참조할 수 있다. 개체 팩토리 웹 스타트 애플리케이션을 생성하기 시작하려면, 우선 프로젝트를 생성할 필요가 있다. 넷빈스에서 새로운 JythonSwingApp이라는 이름의 자바 애플리케이션을 생성하고 jython.jarplyjy.jar를 classpath에 추가한다.

먼저, 애플리케이션의 진정한 드라이버가 될 Main.java 클래스를 생성한다. Main.java의 목적은 자이썬 개체 팩토리 패턴을 사용하여 자이썬 기반 스윙 애플리케이션을 자바로 강제변형하는 것이다. 이 클래스는 애플리케이션의 시작점이며 자이썬 코드는 그 아래에서 모든 작업을 수행할 것이다. 이 패턴을 통하여, 자이썬 코드를 통하여 구현할 수 있는 자바 인터페이스가 필요하므로, 이 예제는 또한 GUI를 볼 수 있도록 하는 데에 쓰이는 start() 메소드를 구현하는 매우 간단한 인터페이스를 사용한다. 끝으로, 아래와 같은 이름의 자이썬 클래스는 Main.java 드라이버와 자바 인터페이스를 위한 코드이다. 이 애플리케이션의 디렉토리 구조는 예제 13-8에서와 같다.

예제 13-8. 개체 팩토리 애플리케이션 코드

JythonSwingApp

JythonSimpleSwing.py

jythonswingapp

Main.java

jythonswingapp.interfaces

JySwingType.java

Main.java

package jythonswingapp;

import jythonswingapp.interfaces.JySwingType;
import org.plyjy.factory.JythonObjectFactory;

public class Main {
    JythonObjectFactory factory;

    public static void invokeJython(){
        JySwingType jySwing = (JySwingType) JythonObjectFactory
        .createObject(JySwingType.class, "JythonSimpleSwing");
        jySwing.start();
    }

    public static void main(String[] args) {
        invokeJython();
    }
}

보는 바와 같이, Main.java는 자이썬 모듈을 강제변형하고 start() 메소드를 호출하는 것 외에 별다른 일을 하지 않는다. 예제 13-9에서, JySwingType.java 인터페이스와 그것을 자이썬으로 구현한 클래스를 볼 수 있을 것이다.

예제 13-9. JySwingType.java 인터페이스 및 구현

JySwingType.java

package jythonswingapp.interfaces;

public interface JySwingType {
    public void start();
}

JythonSimpleSwing.py

import javax.swing as swing
import java.awt as awt
from jythonswingapp.interfaces import JySwingType
import add_player as add_player
import Player as Player

class JythonSimpleSwing(JySwingType, object):
    def __init__(self):
        self.frame=swing.JFrame(title="My Frame", size=(300,300))
        self.frame.defaultCloseOperation=swing.JFrame.EXIT_ON_CLOSE
        self.frame.layout=awt.BorderLayout()
        self.panel1=swing.JPanel(awt.BorderLayout())
        self.panel2=swing.JPanel(awt.GridLayout(4,1))
        self.panel2.preferredSize = awt.Dimension(10,100)
        self.panel3=swing.JPanel(awt.BorderLayout())
        self.title=swing.JLabel("Text Rendering")
        self.button1=swing.JButton("Print Text", actionPerformed=self.printMessage)
        self.button2=swing.JButton("Clear Text", actionPerformed=self.clearMessage)
        self.textField=swing.JTextField(30)
        self.outputText=swing.JTextArea(4,15)

        self.panel1.add(self.title)
        self.panel2.add(self.textField)
        self.panel2.add(self.button1)
        self.panel2.add(self.button2)
        self.panel3.add(self.outputText)

        self.frame.contentPane.add(self.panel1, awt.BorderLayout.PAGE_START)
        self.frame.contentPane.add(self.panel2, awt.BorderLayout.CENTER)
        self.frame.contentPane.add(self.panel3, awt.BorderLayout.PAGE_END)

    def start(self):
        self.frame.visible=1

    def printMessage(self,event):
        print "Print Text!"
        self.text = self.textField.getText()
        self.outputText.append(self.text)

    def clearMessage(self, event):
        self.outputText.text = ""

넷빈스를 사용한다면, 프로젝트를 clean 및 빌드할 때 JAR 파일이 자동으로 생성될 것이다. 명령행 또는 터미널에서는, JythonSimpleSwing.py 모듈이 클래스 경로 내에 있다면 java -jar 옵션을 사용하여 JAR 파일을 쉽게 생성할 수 있다. 넷빈스와 같은 IDE를 사용할 때에 좋은 또 다른 점은, 프로젝트의 속성에 대한 몇 가지 설정을 통하여 웹 스타트 애플리케이션을 만들 수 있다는 것이다. 구체적으로는, 프로젝트 속성으로 가서 왼쪽 메뉴에서 Application - Web Start를 선택하고 Enable Web Start 옵션을 선택하면 필요한 파일의 생성을 IDE에서 처리해준다. 또한 넷빈스는 웹 스타트를 통하여 다른 장비에서 애플리케이션을 구동할 때에 필요한 JAR 파일의 self sign 옵션을 갖고 있다. 한번 사용해보고, 변경을 가한 후에는 프로젝트를 clean 및 빌드하도록 하자.

웹 스타트 애플리케이션에서 필요한 파일을 수동으로 생성하려면, 애플리케이션 JAR의 외부에 둘 두 개의 부가적인 파일을 생성할 필요가 있다. 늘 하는 것과 같이 프로젝트를 위한 JAR를 생성하고, 애플리케이션을 구동시키는 데에 필요한 관련 JNLP 파일, 그리고 JNLP를 참조하는 HTML 페이지를 작성한다. 웹으로부터 애플리케이션을 실행시킨다면 원하는 위치에 HTML 페이지가 있어야 할 것이다. 예제 13-10는 HTML에 포함되는 것과 마찬가지로 JNLP를 생성하는 코드이다.

예제 13-10. 웹 스타트를 위한 JNLP 코드

launch.jnlp

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<jnlp codebase="file:/path-to-jar/" href="launch.jnlp" spec="1.0+">
    <information>
        <title>JythonSwingApp</title>
        <vendor>YourName</vendor>
        <homepage href=""/>
        <description>JythonSwingApp</description>
        <description kind="short">JythonSwingApp</description>
    </information>
    <security>
        <all-permissions/>
    </security>
    <resources>
        <j2se version="1.5+"/>
        <jar eager="true" href="JythonSwingApp.jar" main="true"/>
        <jar href="lib/PlyJy.jar"/>
        <jar href="lib/jython.jar"/>
    </resources>
    <application-desc main-class="jythonswingapp.Main">
    </application-desc>
</jnlp>

launch.html

<html>
    <head>
        <title>Test page for launching the application via JNLP</title>
    </head>
    <body>
        <h3>Test page for launching the application via JNLP</h3>
        <a href="launch.jnlp">Launch the application</a>
        <!-- Or use the following script element to launch with the
        Deployment Toolkit -->
        <!-- Open the deployJava.js script to view its documentation -->
        <!--
        <script src="http://java.com/js/deployJava.js"></script>
        <script>
        var url="http://[fill in your URL]/launch.jnlp"
        deployJava.createWebStartLaunchButton(url, "1.6")
        </script>
        -->
    </body>
</html>

결론적으로, 자바 웹 스타트는 자이썬 애플리케이션을 웹에 배포하는 데에 있어 매우 좋은 방법이다.

독립 JAR를 통한 배포

자이썬 독립실행 JAR 옵션을 사용하여 웹 스타트 애플리케이션을 배포하는 것이 가능하다. 그렇게 하기 위해서는, 자이썬 독립실행 JAR 파일의 압축을 풀어서, 그 파일에 여러분의 코드를 추가하고, 배포를 위하여 다시 JAR로 만든다. 이 방법의 유일한 결점은, 올바로 작동하도록 하기 위해서는 파일들이 올바른 경로에 있는지 확인해야하며, 그러한 일들이 때로는 지루해질 수 있다는 점이다.

JAR를 통하여 자이썬 애플리케이션을 배포하기 위해서는, 먼저 자이썬 독립실행 배포본을 다운로드한다. 그런 다음에는, JAR를 확장하는 Stuffit 또는 7zip 등의 도구를 사용하여 jython.jar로부터 파일을 풀어낸다. JAR를 푼 후에는, 여러분의 .py 스크립트들은 Lib 디렉토리에, 자바 클래스들은 루트에 추가한다. 예를 들어 org.jythonbook.Book이라는 이름의 자바 클래스가 있다면, 그것을 패키지 구조에 따른 적절한 디렉토리에 위치시킨다. 애플리케이션에 추가적으로 포함시킬 JAR 파일이 있다면 그것들을 classpath에 두도록 해야한다. 이 셋업을 마치면, 조작된 독립실행 자이썬 JAR를 앞서 기술한 도구들을 사용하여 다시 ZIP 형식으로 압축한다. 그런 다음 ZIP을 JAR로 이름을 바꾼다. 이제 애플리케이션은 명령행에서 여러분의 애플리케이션을 호출하기 위한 추가적인 외부의 .py 파일을 사용하여 “-jar” 옵션을 사용하여 수행시킬 수 있게 되었다.

$ java -jar newStandaloneJar.jar {optional .py file}

이것은 여러분의 애플리케이션을 담기 위하여 JAR 파일을 사용하는 한 가지의 예일 뿐이다. 그러한 기법을 수행하는 다른 방법도 있지만, 이것이 가장 직관적이고 쉽게 할 수 있는 방법이다.

WSGI와 Modjy

웹 서버 게이트웨이 인터페이스의 약자인 WSGI는, 웹 서버와 웹 애플리케이션 간의 통신을 제공하는 저수준 API이다. 사실, WSGI는 그 이상이며 WSGI를 사용하여 완전한 웹 애플리케이션을 실제로 작성할 수 있다. 하지만 WSGI는 파이썬 메소드와 함수를 호출하는 표준 인터페이스에 가깝다. 파이썬 PEP 333은 다양한 웹 서버들 사이에서 웹 애플리케이션의 이식성을 높이기 위하여 제안된, 웹 서버들과 파이썬 웹 애플리케이션 또는 프레임워크 사이의 표준 인터페이스를 기술한다.

이 섹션에서는 modjy를 활용하여 매우 간단한 “Hello Jython” 애플리케이션을 생성하기 위하여 WSGI를 활용하는 방법을 보인다. Modjy는 자이썬을 위한 WSGI compliant 게이트웨이/서버로서, Java/J2EE 서블릿에 기반하고 있다. modjy 웹사이트(http://opensource.xhaus.com/projects/modjy/wiki)에 의하면, modjy는 다음과 같이 묘사된다.

Note

Java/J2EE 컨테이너 내부에서 동작하는 자이썬 WSGI 애플리케이션이며 들어오는 요청은 서블릿 컨테이너에서 처리된다. 컨테이너는 요청을 modjy 서블릿으로 라우트하기 위하여 구성된다. 그러면 modjy 서블릿은 서블릿 컨테이너 내에 임베드된 자이썬 인터프리터를 생성하고, 구성된 자이썬 웹 애플리케이션을 적재한다. 예를 들어, 장고 애플리케이션이 modjy를 통하여 적재될 수 있다. 그러면 modjy 서블릿은 구성된 WSGI 애플리케이션 또는 프레임워크로 요청을 위임한다. 끝으로, WSGI 응답은 서블릿 컨테이너를 거쳐 클라이언트로 다시 라우트된다.

글래스피쉬에서 Modjy 애플리케이션 구동

modjy 애플리케이션을 자바 서블릿 컨테이너에서 구동하기 위한 첫 번째 단계는 WAR 파일로 포장할 자바 웹 애플리케이션을 생성하는 것이다. 다른 도구의 도움 없이 애플리케이션을 작성할 수도 있고 넷빈스 6.7과 같은 IDE의 도움을 받을 수도 있다. modjy는 2.5.0 버전 이후부터 자이썬에 포함되므로, 일단 웹 애플리케이션을 작성한 후에는 jython.jar가 CLASSPATH에 있도록 해야한다. 끝으로, 애플리케이션 배포 기술자 (web.xml) 내에 modjy 서블릿에 대하여 설정할 필요가 있다. 이 예제에서는, 구글 앱 엔진을 위한 modjy 애플리케이션을, 로컬의 글래스피쉬 환경에 배포할 것이다.

modjy를 위한 애플리케이션 배포 기술자를 설정하기 위해서는, modjy 서블릿을 설정하고, 필요한 매개변수를 제공한 다음, 서블릿 매핑을 제공한다. 예제 13-11에 나타난 구성에서, modjy 서블릿 클래스는 com.xhaus.modjy.ModjyServlet이다. 서블릿에서 필요로하는 첫 번째 매개변수의 이름은 python.home이다. 이 매개변수의 값은 자이썬 홈과 같다. 다음으로, python.cachedir.skip 매개변수는 true로 한다. app_filename 매개변수에는 호출할 수 있는 애플리케이션의 이름을 넣는다. 다른 매개변수는 여러분이 설정하는 각 modjy 애플리케이션과 똑같이 설정될 것이다. web.xml의 끝 부분에는 서블릿 매핑이 필요하다. 예제에서, 모든 URL을 modjy 서블릿에 매핑한다.

예제 13-11. modjy 서블릿 설정

web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
    <display-name>modjy demo application</display-name>
    <description>
        modjy WSGI demo application
    </description>

    <servlet>
        <servlet-name>modjy</servlet-name>
        <servlet-class>com.xhaus.modjy.ModjyJServlet</servlet-class>

        <init-param>
            <param-name>python.home</param-name>
            <param-value>/Applications/jython/jython2.5.0/</param-value>
        </init-param>

        <init-param>
            <param-name>python.cachedir.skip</param-name>
            <param-value>true</param-value>
        </init-param>
        <!--
        There are two different ways you can specify an application to
        modjy
        1. Using the app_import_name mechanism
        2. Using a combination of
        app_directory/app_filename/app_callable_name
        Examples of both are given below
        See the documentation for more details.
        http://modjy.xhaus.com/locating.html#locating_callables
        -->
        <!--
        This is the app_import_name mechanism. If you specify a value
        for this variable, then it will take precedence over the other
        mechanism
        <init-param>
            <param-name>app_import_name</param-name>
            <param-value>my_wsgi_module.my_handler_class().handler_method</param-value>
        </init-param>
        -->
        <!--
        And this is the app_directory/app_filename/app_callable_name
        combo
        The defaults for these three variables are
        ""/application.py/handler
        So if you specify no values at all for any of app_* variables,
        then modjy
        will by default look for "handler" in "application.py" in the
        servlet
        context root.
        <init-param>
            <param-name>app_directory</param-name>
            <param-value>some_sub_directory</param-value>
        </init-param>
        -->
        <init-param>
            <param-name>app_filename</param-name>
            <param-value>demo_app.py</param-value>
        </init-param>
        <!--
        Supply a value for this parameter if you want your application
        callable to have a different name than the default.
        <init-param>
            <param-name>app_callable_name</param-name>
            <param-value>my_handler_func</param-value>
        </init-param>
        -->
        <!-- Do you want application callables to be cached?-->
        <init-param>
            <param-name>cache_callables</param-name>
            <param-value>1</param-value>
        </init-param>
        <!-- Should the application be reloaded if it's .py file changes?            -->
        <!-- Does not work with the app_import_name mechanism -->
        <init-param>
            <param-name>reload_on_mod</param-name>
            <param-value>1</param-value>
        </init-param>
        <init-param>
            <param-name>log_level</param-name>
            <param-value>debug</param-value>
            <!-- <param-value>info</param-value> -->
            <!-- <param-value>warn</param-value> -->
            <!-- <param-value>error</param-value> -->
            <!-- <param-value>fatal</param-value> -->
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>modjy</servlet-name>
    <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

demo_app은 예제 13-12에 보인 것과 같이 작성되어야 한다. WSGI 표준의 일부로서, 애플리케이션은 각 요청에 대한 서버 호출 함수를 제공한다. 이 경우, 그 함수의 이름은 handler이다. 그 함수는 두 매개변수를 취하여야 하며, 첫 번째 것은 CGI 정의 환경 변수 사전이 되어야 한다. 두 번째는 HTTP 헤더를 반환하는 콜백이다. 그 콜백 함수는 start_response(status, response_headers, exx_info=None)과 같이 호출되어야 하며, 이때 status는 HTTP 상태, response_headers는 HTTP 헤더의 목록, 그리고 exc_info는 예외처리를 위한 것이다. demo_app.py 애플리케이션을 살펴보고 방금 논의한 기능을 확인하도록 하자.

예제 13-12.

import sys
import string

def escape_html(s):
    return s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')

def cutoff(s, n=100):
    if len(s) > n: return s[:n]+ '.. cut ..'
    return s

def handler(environ, start_response):
    writer = start_response("200 OK", [ ('content-type', 'text/html') ])
    response_parts = '''<html><head>
        <title>Modjy demo WSGI application running on Local Server!</title>
        </head>
        <body>
        <p>Modjy servlet running correctly:
        jython $version on $platform:
        </p>
        <h3>Hello jython WSGI on your local server!</h3>
        <h4>Here are the contents of the WSGI environment</h4>'''
    environ_str = "<table border='1'>"
    keys = environ.keys()
    keys.sort()
    for ix, name in enumerate(keys):
        if ix % 2:
            background='#ffffff'
        else:
            background='#eeeeee'
        style = " style='background-color:%s;'" % background
        value = escape_html(cutoff(str(environ[name]))) or '&#160;'
        environ_str = "%s\\n<tr><td%s>%s</td><td%s>%s</td></tr>" % \\
            (environ_str, style, name, style, value)
    environ_str = "%s\\n</table>" % environ_str
    response_parts = response_parts + environ_str + '</body></html>\\n'
    response_text = string.Template(response_parts)
    return [response_text.substitute(version=sys.version, platform=sys.platform)]

이 애플리케이션은 그것이 동작하는 서버의 환경 구성을 반환한다. 보는 바와 같이, 페이지는 코딩하기가 아주 단순하며 서블릿과 정말로 흡사하다.

애플리케이션이 일단 셋업되고 구성되면, 간단히 컴파일하여 WAR 파일을 만들고 여러분이 선택한 자바 서블릿 컨테이너에 배포하도록 한다. 이 경우에는, 우리는 글래스피쉬 V2를 사용하여 잘 작동하였다. 하지만, 동일한 애플리케이션이 톰캣, JBoss 등에 대해서도 배포 가능할 것이다.

요약

간단한 웹 기반 애플리케이션을 생성하는 데에 자이썬을 사용할 수 있는 여러 방법이 있다. 자이썬 서블릿은 컨텐트를 웹에서 사용가능하도록 하는 데에 좋은 방법이며, 모델-뷰-컨트롤러 구성을 가능하게 해주는 JSP 페이지와 함께 활용할 수도 있다. 이것은 정교한 웹 애플리케이션을 개발하기에 좋은 기법이며, 특히 자바스크립트와 혼합하여 액션을 만들 때에 효과적으로 관리할 수 있다. 많은 자바 웹 애플리케이션은 MVC의 개념을 적용할 수 있도록 해주는 프레임워크 등의 기법을 사용한다. 자이썬으로도 그와 같은 일을 잘 해낼 수 있다.

또한 이 장에서는 modjy를 사용하는 자이썬에서의 WSGI 애플리케이션의 생성에 대하여 논의하였다. 이는 저수준에서 웹 애플리케이션을 생성하는 데에 있어 좋은 방법이며, modjy와 WSGI가 웹 프레임워크 등을 구현하는 데에 사용되곤 한다. 장고와 같은 솔루션은 파이썬 웹 프레임워크의 표준 PEP 333을 따르는 WSGI를 사용한다. WSGI 또한 자이썬으로 서블릿을 작성하는 것만큼이나 웹 애플리케이션을 작성하는 데에 있어 훌륭하고도 재빠른 방법임을 알 수 있을 것이다.

다음 장부터는 자이썬에서 사용할 수 있는 웹 프레임워크, 특히 장고와 파일론에 대하여 배울 것이다. 그 두 가지 프레임워크는 웹 개발을 보다 쉽게 만들어주며, 자이썬을 통하여 자바 플랫폼을 활용함으로써 더욱 강력해진다. 장고와 같은 템플릿 기법을 사용하여 훨씬 생산적이 되며, 이는 full-blown 웹 애플리케이션을 디자인하는 데에 있어 좋은 방법이다. 이 장에서 논의한 기법들을 사용하여 규모가 큰 웹 애플리케이션을 개발하는 것도 가능하지만, 그보다는 앞으로 다룰 표준 프레임워크의 사용을 우선적으로 고려하여야 할 것이다. 자이썬 웹 애플리케이션을 작성하는 데에 좋은 여러 방법이 있으며, 계속 발전해나가고 있다!