use strict; use lib "/path/to/secure/webapp/libs"; use XSLGateway; use CGI qw(:standard); my $q = CGI->new(); my %context = (); my $gateway_name = "stylechooser";적절한 모듈들과 스크립트 전체에서 사용될 몇몇 변수부터 설정한 후, 애플리케이션 로직 전체를 핸들링할 클래스에게 넘겨줄 %context 해시에 필드들을 추가하는 것으로 작업을 시작하자. 이 애플리케이션을 위해 요청된 URL 스크립트 이름 오른쪽에 있는 부분 (REQUEST 엔트리)과, 쿼리스트링 중 "style" 이라는 이름으로 저장된 자료를 넘겨주게 될 것이다.
$context{REQUEST} = $q->url(-path => 1); $context{REQUEST} =~ s/^$gateway_name\/?//; $context{REQUEST} ||= "index.xml"; $context{STYLE} = $q->param("style") if $q->param("style");마지막으로 XSLGateway 로직 클래스의 인스턴스를 만들고 run 메소드를 호출하여 %context 해시를 유일한 인자로 넘겨주어 요청을 처리한다.
my $app = XSLGateway->new(); $app->run(%context);CGI 스크립트는 이것이 전부다. 이제 대부분의 작업을 처리하는 XSLGateway 모듈을 만들어보자.
package XSLGateway; use strict; use vars qw(@ISA); use CGI::XMLApplication; use XML::LibXML; @ISA = qw(CGI::XMLApplication);앞에서 언급했듯이 CGI::XMLApplication은 콜백 이벤트들을 이용해 동작한다. 애플리케이션 클래스에 주어진 메소드는 특정 입력 필드 (보통은 폼을 제출하는데 사용된 버튼의 이름)의 값에 따라 실행된다. 두 가지 상이한 콜백이 구현되어야 하는데, 하나는 selectStylesheet 메소드, 또 하나는 requestDOM 메소드다.
sub selectStylesheet { my $self = shift; my $context = shift; my $style = $context->{STYLE} || "default"; my $style_path = "/opt/www/htdocs/stylesheets/"; return $style_path . $style . ".xsl"; }이제는 requestDOM 메소드를 만들어야 한다. 이 메소드는 사용자에게 전달되기 위해 변환될 XML 문서의 XML::LibXML 형식 DOM을 리턴해 주어야 한다. 만들고 있는 게이트웨이가 정적인 파일만 서비스할 것이기 때문에 적절한 문서를 XML::LibXML을 이용해 파싱하고 그 결과로 만들어지는 트리를 돌려주기만 하면 된다.
sub requestDOM { my $self = shift; my $context = shift; my $xml_file = $context->{REQUEST} || "index.xml"; my $doc_path = "/opt/www/htdocs/xmldocs/"; my $requested_doc = $doc_path . $xml_file; my $parser = XML::LibXML->new; my $doc = $parser->parse_file($requested_doc); return $doc; }방금 만든 CGI 스크립트가 서버의 cgi-bin에서 안전하게 실행되는지 확인하고 XML 문서 몇 개와 XSLT 스타일시트 한 두 개를 적당한 디렉토리에 업로드하자. 이제 준비가 끝났다. http://localhost/cgi-bin/stylechooser/mydocs/somefile.xml을 요청하면 /opt/www/htdocs/xmldocs/ 디렉토리의 mydocs/somefile.xml 파일이 선택되어 /opt/www/htdocs/stylesheets/ 폴더의 default.xsl 스타일시트를 이용해 변환되고 변환된 자료가 클라이언트에게 전달될 것이다.
use strict; use CGI qw(:standard); use lib "/path/to/secure/webapp/libs"; use CustomerOrder; my $q = CGI->new(); my %context = (); $context{PARAMS} = $q->Vars; my $app = CustomerOrder->new(); $app->run(%context);여기서 든 예제를 살펴보기 위해, 주문 애플리케이션을 위한 제품정보는 관계형 데이터베이스에 저장되어 있다고 가정하자. 제품목록은 적절한 크기이기 때문에 우리가 만들 애플리케이션은 세 개의 화면이면 충분하다. 주 자료입력화면에서는 사용자들이 주문하기 원하는 하나 이상의 제품에 대한 양을 입력한다. 확인 화면에서는 장바구니의 내용을 보여주고 선택한 항목의 가격 합계를 보여준다. 완료 화면에서는 주문이 성공적으로 처리되었다는 메시지를 보여준다. 이미 제목에서 밝혔듯이 단순하게 만들기 위해 배송이나 과금정보를 입력하는 화면은 언급하지 않겠다.
package CustomerOrder; use strict; use vars qw(@ISA); use CGI::XMLApplication; use XML::LibXML::SAX::Builder; use XML::Generator::DBI; use DBI; @ISA = qw(CGI::XMLApplication);필요한 모듈들을 로드하고 CGI::XMLApplication에서 상속했다는 선언을 해 준 다음, 애플리케이션의 여러 상태(혹은 여러 화면이라고 할 수도 있겠다)와 연관되는 여러 개의 이벤트 콜백들을 만드는 것부터 해보자. 첫번째로 registerEvents() 콜백이 핸들러들을 돌려주도록 이벤트들을 등록해야 한다. registerEvents 콜백이 돌려주는 핸들러들은 기본으로 호출되는 핸들러들에 추가하여 우리가 만드는 애플리케이션이 구현할 것이다. 여기에서는 order_confirm과 order_send 콜백을 등록하여 %context 해시의 SCREENSTYLE 필드를 셋팅하도록 할 것이다. 나중에 이 프로퍼티를 사용하여 세 가지 XSLT 스타일시트 중 어느 것이 클라이언트에게 보내줄 주문 자료를 렌더링하는데 사용될 것인지 결정할 것이다.
# event registration and event callbacks sub registerEvents { return qw( order_confirm order_send ); } sub event_order_confirm { my ($self, $context) = @_; $context->{SCREENSTYLE} = "order_confirm.xsl"; } sub event_order_send { my ($self, $context) = @_; $context->{SCREENSTYLE} = "order_send.xsl"; }event_default 콜백은 아무 핸들러도 요청되지 않을 경우 실행된다. 여기서는 SCREENSTYLE 필드를 적절한 값으로 설정하기 위해서만 사용한다.
sub event_default { my ($self, $context) = @_; $context->{SCREENSTYLE} = "order_default.xsl"; }event_init 콜백은 요청때마다 다른 핸들러들이 호출되기 전에 호출된다. 따라서 다른 핸들러들에 의해 사용되는 애플리케이션의 일부분들을 초기화하는데 매우 유용하다. 이 경우 fetch_recordset() 메소드를 이용해 데이터베이스에서 가져온 제품정보를 담고 있는 초기 DOM 트리를 돌려주도록 하여 이 트리를 %context 해시에 저장해 나중에 사용할 수 있다.
sub event_init { my ($self, $context) = @_; $context->{DOMTREE} = $self->fetch_recordset(); }상태 핸들러 메소드들을 완성했으면 필수적인 selectStylesheet와 requestDOM 메소드를 구현해야 한다.
# app config and helpers sub selectStylesheet { my ($self, $context) = @_; my $style = $context->{SCREENSTYLE}; my $style_path = "/opt/www/htdocs/stylesheets/cart/"; return $style_path . $style; }requestDOM 핸들러를 살펴보기 전에 fetch_recordset라는 (도우미) 메소드를 먼저 보면 문맥을 보다 더 확실하게 이해할 수 있을 것이다.
sub fetch_recordset { my $self = shift; my $sql = "select id, name, price from products"; my $dbh = DBI->connect("dbi:Oracle:webclients", "chico", "swordfish") || die "database connection couldn"t be initialized: $DBI::errstr \n"; my $builder = XML::LibXML::SAX::Builder->new(); my $gen = XML::Generator::DBI->new(Handler => $builder, dbh => $dbh, RootElement => "document", QueryElement => "productlist", RowElement => "product"); my $dom = $gen->execute($sql) || die "Error Building DOM Tree\n"; return $dom; }fetch_recordset 메소드는 복잡한 일을 간단하게 만들어 준다. 하지만 여기에서 리턴되는 DOM 트리는 클라이언트에게 보내기 원하는 정보만을 담고 있다. 이 외에도 이 세션 안에서 이전에 입력되었던 제품 수량도 가져와야 하고 주문한 제품 가격의 총 합계도 보여주어야 한다.
sub requestDOM { my ($self, $context) = @_; my $root = $context->{DOMTREE}->getDocumentElement(); my $grand_total = "0";현재의 주문량을 큰 문서의 일부로 포함시키기 위해 제품 요소들을 루프로 돌리면서
foreach my $row ($root->findnodes("/document/productlist/product")) { my $id = $row->findvalue("id"); my $cost = $row->findvalue("price"); my $quantity = $context->{PARAMS}->{$id} || "0"; my $item_total = $quantity * $cost; $grand_total += $item_total; # add the order quantity and item totals to the tree. $row->appendTextChild("quantity", $quantity); $row->appendTextChild("item-total", $item_total); }마지막으로 루트 요소에 현재 선택된 아이템 값의 합계를 담고 있는 자식 요소를 가지는 요소를 덧붙여 주문에 대한 메타정보를 추가한다.
$grand_total ||= "0.00"; my $info = XML::LibXML::Element->new("instance-info"); $info->appendTextChild("order-total", $grand_total); $root->appendChild($info); return $context->{DOMTREE}; } 1;세심한 독자라면 방금 만든 간단한 장바구니가 order_send 이벤트를 수행하는 동안 전송된 자료를 가지고 실제로는 아무것도 하지 않는다는 것을 눈치챘을 것이다. 사실 자료가 어디로 어떻게 가는지를 프로그래밍하는 것은 각 사이트의 고유한 특화된 기능이기 때문에 여기서는 특별하게 언급하지 않았다. 스타일시트를 포함해 전체 애플리케이션은 예제코드를 참고하면 된다.
이전 글 : 파이썬으로 게임 개발하기
다음 글 : Castor를 이용한 XML 데이터 바인딩
최신 콘텐츠