@rumidier - 서산 야생마, 가슴 속에 화난새 키우는 어른이
펄에는 수 많은 웹프레임워크가 있습니다. 그 중 근래에 가장 많이 사용하는 프레임워크는 Catalyst와 Dancer, Mojolicious 세 가지 입니다. 복잡한 엔터프라이즈 환경은 Catalyst를 많이 사용하고 비교적 규모가 작은 웹 사이트는 Dancer나 Mojolicious를 많이 사용한다고 합니다. Mojolicious는 공식 홈페이지에 문서화가 잘 되있고 다루기 쉽다고 하지만 정말 아무 것도 모른채로 시작한다면 막히는 곳이 한 두 군데가 아닙니다. 문서를 보고도 어떻게 시작해야 될지 헤매실 분들에게 이 글을 바칩니다.
필요한 모듈은 다음과 같습니다.
직접 CPAN을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.
$ sudo cpan Mojolicious DBI DBD::mysql
사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나 perlbrew를 이용해서 자신만의 Perl을 사용하고 있다면 다음 명령을 이용해서 모듈을 설치합니다.
$ cpan Mojolicious DBI DBD::mysql
Mojolicious
모듈을 설치하면 같이 제공되는 mojo
명령은 다양한 기능을 제공합니다.
help
를 인자로 주면 자세한 기능을 살펴볼 수 있습니다.
$ mojo help usage: mojo COMMAND [OPTIONS] Tip: CGI and PSGI environments can be automatically detected very often and work without commands. These commands are currently available: daemon Start application with HTTP and WebSocket server. cpanify Upload distribution to CPAN. get Perform HTTP request. inflate Inflate embedded files to real files. eval Run code against application. version Show versions of installed modules. psgi Start application with PSGI. prefork Start application with preforking HTTP and WebSocket server. cgi Start application with CGI. test Run unit tests. routes Show available routes. generate Generate files and directories from templates. These options are available for all commands: -h, --help Get more information on a specific command. --home <path> Path to your applications home directory, defaults to the value of MOJO_HOME or auto detection. -m, --mode <name> Operating mode for your application, defaults to the value of MOJO_MODE/PLACK_ENV or "development". See 'mojo help COMMAND' for more information on a specific command.
Mojolicious를 가장 간단히 사용할 수 있는 Mojoliciois::Lite
모듈을 이용한 웹 앱을
작성하기 위해 mojo
명령의 generate
, lite_app
옵션을 이용해서 myapp.pl
을 생성합니다.
$ mojo generate lite_app myapp.pl [exist] /Users/mojo-test [write] /Users/mojo-test/myapp.pl [chmod] myapp.pl 744
앞의 명령으로 생성되는 myapp.pl
웹 앱을 실행하려면 Mojolicious
모듈과 함께
제공되는 morbo
명령을 사용해서 생성한 myapp.pl
파일을 인자로 줍니다.
$ morbo myapp.pl Listening at "http://*:3000". Server available at http://127.0.0.1:3000.
특별한 옵션을 지정하지 않을 경우 3000
포트를 사용합니다.
특정 포트를 지정하려면 -l
옵션을 사용합니다.
$ morbo myapp.pl -l http://*:5001 Listening at "http://*:5001". Server available at http://127.0.0.1:5001.
실행 후 웹브라우저를 이용해 명령줄에 출력되는 주소로 접속하면
myapp.pl
웹 앱을 브라우저에서 확인할 수 있습니다.
그림 1. Mojolicious::Lite 실행화면 (원본)
이제 Mojolicious를 사용해 개발할 수 있는 환경이 마련되었습니다.
GET
/POST
를 이용한 읽기, 쓰기, 수정, 삭제 기능을 지원하는
아주 간단한 게시판을 만들어보며 감을 잡아봅시다.
우선 간단한 게시판을 만들기 위해 myapp.pl
파일을 열어 뼈대를 잡아보죠.
#!/usr/bin/env perl use v5.16; use utf8; use Mojolicious::Lite; get '/' => sub { }; get '/list' => sub { }; get '/write' => sub { }; post '/write' => sub { }; get '/read/:id' => sub { }; get '/edit/:id' => sub { }; post '/edit' => sub { }; get '/delete/:id' => sub { }; app->start; __DATA__ @@ layouts/default.html.ep <!DOCTYPE html> <html> <head> <title><%= title %></title> </head> <body> <%= content %> </body> </html> @@ write.html.ep % layout 'default'; % title 'WRITE'; @@ list.html.ep % layout 'default'; % title 'LIST'; @@ read.html.ep % layout 'default'; % title 'READ'; @@ edit.html.ep % layout 'default'; % title 'EDIT';
뼈대 코드는 크게 __DATA__
섹션 위와 아래로 구성됩니다.
__DATA__
섹션 위는 컨트롤러에 해당하며 아래는 템플릿에 해당합니다.
컨트롤러 즉 URL 라우팅에 해당하는 코드는 다음과 같습니다.
get '/' => sub { }; get '/list' => sub { }; get '/write' => sub { }; post '/write' => sub { }; get '/read/:id' => sub { }; get '/edit/:id' => sub { }; post '/edit' => sub { }; get '/delete/:id' => sub { };
전체 페이지의 템플릿을 구성하는 코드는 @@ layouts/default.html.ep
영역입니다.
@@ layouts/default.html.ep <!DOCTYPE html> <html> <head> <title><%= title %></title> </head> <body> <%= content %> </body> </html>
그 외의 템플릿은 모두 % layout 'default'
구문을 이용해 layouts/default.html.ep
를
큰 틀로 사용하며 각각의 템플릿에서 표현하는 부분은 layouts/default.html.ep
템플릿에서
<%= content %>
영역에 해당하는 부분과 대치됩니다.
@@ XXXX.html.ep % layout 'default'; # layouts/default.html.ep를 사용한다는 의미 % title 'WRITE'; # <%= title %> 영역과 대치됨 ... 이 부분은 <%= content %> 영역과 대치됨
간단한 게시판이라 하더라도 입력 받은 자료, 즉 글을 어딘가에 저장해야 합니다. MySQL을 이용해 DB를 생성하고 DBI 모듈, DBD::mysql 모듈을 이용해 생성한 데이터베이스에 접속하겠습니다.
우선 Book
데이터베이스를 생성합니다.
$ mysql -u root -p 'create database Book;'
Book
데이터베이스에 다음 스키마를 참고해서 memo
테이블을 생성합니다.
CREATE TABLE `memo` ( `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, `name` VARCHAR(20) NOT NULL DEFAULT '', `title` VARCHAR(70) NOT NULL, `content` TEXT NOT NULL, `wdate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Mojolicious에서 생성한 데이터베이스에 연결하기 위해 다음 코드를 추가합니다.
MySQL 접속 계정은 memouser
, 비밀 번호는 memopass
라고 가정합니다.
#!/usr/bin/env perl use v5.16; use utf8; use Mojolicious::Lite; use DBI; my $DBH = DBI->connect( 'dbi:mysql:Book', 'memouser', 'memopass', { RaiseError => 1, AutoCommit => 1, mysql_enable_utf8 => 1, }, ); get '/' => sub { }; ...
웹페이지에서 쓰기 기능을 사용하기 위해 필요한 것은 두 가지 입니다.
글을 쓰기 위한 페이지는 GET /write
요청을 통해 접근합니다.
get '/write' => sub { my $self = shift; $self->render('write'); };
그림 2. 메모 쓰기화면 (원본)
메모 쓰기 HTML 양식 입니다. 이름, 제목, 내용 세 가지를 입력합니다.
@@ write.html.ep % layout 'default'; % title 'WRITE'; <form action="/write" method="post"> <table width=580 border=0 cellpadding=2 cellspacing=1 bgcolor=#777777> <tr> <td height=20 colspan=4 align=center bgcolor=#999999> <font color=white><b>글쓰기</b></font> </td> </tr> <tr> <td bgcolor=white> <table bgcolor=white> <tr> <td>이름</td> <td><input type="text" name="name"></td> </tr> <tr> <td>제목</td> <td><input type="text" name="title"></td> </tr> <tr> <td>내용</td> <td colspan=4> <textarea name="content" cols=80 rows=5></textarea> </td> </tr> <tr> <td colspan=10 align=center> <input type="submit" value="저장"> </td> </tr> </table> </td> </tr> <tr> <td bgcolor=#999999> <table width=100%> <tr> <td> <a href='/list' style="text-decoration:none;"><font color=white>[목록보기]</font></a> </td> </tr> </table> </td> </tr> </table> </form>
동일한 /write
주소라도 GET
요청이냐 또는 POST
요청이냐에 따라
get '/write'
와 post '/write'
로 구분이 되므로 주의하세요.
POST
로 전달된 값은 $self->param('name')
으로 값을 얻을 수 있습니다.
post '/write' => sub { my $self = shift; my $name = $self->param('name'); my $title = $self->param('title'); my $content = $self->param('content'); my $sth = $DBH->prepare(qq{ INSERT INTO `memo` (`name`,`title`,`content`) VALUES (?,?,?) }); $sth->execute( $name, $title, $content ); $self->redirect_to( $self->url_for('/list') ); };
$self->param(...)
으로 얻은 값은 DBI
를 통해 저장합니다.
사용법은 다음과 같습니다.
my $sth = $DBH->prepare(qq{ INSERT INTO `memo` (`name`,`title`,`content`) VALUES (?,?,?) }); $sth->execute( $name, $title, $content );
문제가 없다면 데이터베이스에는 다음처럼 저장됩니다.
mysql> select * from memo; +----+----------+--------------+-------------------------------------------------------+---------------------+ | id | name | title | content | wdate | +----+----------+--------------+-------------------------------------------------------+---------------------+ | 1 | rumidier | book-test-01 | 웹페이지 제작을 위해 Mojolicious를 사용하고 있습니다. | 2013-11-01 20:00:00 | +----+----------+--------------+-------------------------------------------------------+---------------------+
저장한 글 목록을 확인할 수 있는 페이지가 필요하겠죠.
/
로 접속했을 때는 /list
로 바로 이동하도록 변경합니다.
get '/' => sub { my $self = shift; $self->redirect_to( $self->url_for('/list') ); };
GET /list
요청을 받을 수 있는 /list
를 작성합니다.
get '/list' => sub { my $self = shift; my $sth = $DBH->prepare(qq{ SELECT id, name, title, content, wdate FROM memo }); $sth->execute(); my %articles; while ( my @row = $sth->fetchrow_array ) { my ( $id, $name, $title, $content, $date ) = @row; my ($wdate) = split / /, $date; $articles{$id} = { name => $name, title => $title, content => $content, wdate => $wdate, }; } $self->stash( articles => \%articles ); };
$self->stash()
함수를 이용해 템플릿 쪽으로 \%articles
해시 레퍼런스를 전달합니다.
\%articles
해시 레퍼런스를 이용하는 템플릿 쪽 코드는 다음과 같습니다.
@@ list.html.ep % layout 'default'; % title 'LIST'; <table width=580 border=0 cellpadding=2 cellspacing=1 bgcolor=#999999> <tr height=20 colspan=4 align=center bgcolor=#CCCCCC > <td color=white>No. </td> <td>제목</td> <td>글쓴이</td> <td>date</td> </tr> % foreach my $id ( keys %$articles ) { <tr bgcolor="white"> <td><%= $id %></td> <td><a href="/read/<%= $id %>"><%= $articles->{$id}{title} %></a></td> <td><%= $articles->{$id}{name} %></td> <td><%= $articles->{$id}{wdate} %></td> </tr> % } <tr> <td colspan=4 bgcolor=#999999> <table width=100%> <tr> <td width=2000 align=center height=20> <a href='/write' style="text-decoration:none;"><font color=white>[글쓰기]</font></a> </td> </tr> </table> </td> </tr> </table>
컨트롤러로 부터 넘겨 받은 데이터를 출력해보면 데이터베이스의 id
를 기준으로
출력이 되는데 이때 id
는 정렬되지 않은 상태, 즉 무작위로 출력됩니다.
그림 3. 해쉬 무작위 정렬 (원본)
정렬 연산자인 sort
를 추가합니다.
sort
만으로는 가장 처음에 입력된 id=1
인 값이 맨위로 올라오므로
역순으로 출력하기위해 reverse
도 같이 사용합니다.
% foreach my $id ( reverse sort keys %$articles ) {
그림 4. 역순 정렬 (원본)
1번이 맨아래로 갔지만 10번 이상이 중간에 섞여 있습니다.
이것은 sort
함수가 문자열 기준 정렬을 하기 때문인데 이를 보완하기 위해 숫자 정렬을 해야겠죠.
우주선 연산자(<=>
)를 사용해서 다시 정렬해보죠.
% foreach my $id ( reverse sort { $a <=> $b } keys %$articles ) {
그림 5. 숫자 정렬 (원본)
이제 저장한 글 목록이 화면에 출력됩니다.
각각의 글 내용을 확인하기 위한 페이지는 /read/1
, /read/2
, /read/N
등의 주소로 연결했습니다.
다음은 /read/N
으로 접속했을 때 처리할 수 있는 컨트롤러 코드입니다.
get '/read/:id' => sub { my $self = shift; my $input_id = $self->param('id'); my $sth = $DBH->prepare(qq{ SELECT * FROM memo WHERE id=$input_id }); $sth->execute(); my %articles; my ( $id, $name, $title, $content, $date ) = $sth->fetchrow_array; my ($wdate) = split / /, $date; $articles{$id} = { name => $name, title => $title, content => $content, wdate => $wdate, }; $self->stash( articles => \%articles, id => $id, ); $self->render('read'); };
다음은 /read/N
으로 접속했을 때 처리할 수 있는 템플릿 코드입니다.
@@ read.html.ep % layout 'default'; % title 'READ'; <table width=580 border=0 cellpadding=2 cellspacing=1 bgcolor=#777777> <tr> <td height=20 colspan=4 align=center bgcolor=#999999> <font color=white><b><%= $articles->{$id}{title} %></b></font> </td> </tr> <tr> <td width=50 height=20 align=center bgcolor=#EEEEEE> 글쓴이 </td> <td width=240 bgcolor=white> <%= $articles->{$id}{name} %> </td> <td width=50 height=20 align=center bgcolor=#EEEEEE> 날짜 </td> <td width=240 bgcolor=white> <%= $articles->{$id}{wdate} %> </td> </tr> <tr> <td bgcolor=white colspan=4> <font color=black> <pre><%= $articles->{$id}{content} %></pre> </font> </td> </tr> <tr> <td colspan=4 bgcolor=#999999> <table width=100%> <tr> <td width=2000 align=left height=20> <a href='/list' style="text-decoration:none;"><font color=white>[목록보기]</font></a> <a href='/write' style="text-decoration:none;"><font color=white>[글쓰기]</font></a> <a href='/edit/<%= $id %>' style="text-decoration:none;"><font color=white>[수정]</font></a> <a href='/delete/<%= $id %>' style="text-decoration:none;"><font color=white>[삭제]</font></a> </td> </tr> </table> </td> </tr> </table>
이제 글 목록에서 각각의 글 제목을 클릭하면 글의 내용을 확인할 수 있습니다.
그림 6. 글 내용 보기 (원본)
쓰고 읽은 후 내용을 수정하고 싶을 경우를 위해서 수정 기능이 필요하겠죠.
다음은 /edit/N
으로 접속했을 때 처리할 수 있는 컨트롤러 코드입니다.
get '/edit/:id' => sub { my $self = shift; my $input_id = $self->param('id'); my $sth = $DBH->prepare(qq{ SELECT * FROM memo WHERE id=$input_id }); $sth->execute(); my %articles; my ( $id, $name, $title, $content, $date ) = $sth->fetchrow_array; my ($wdate) = split / /, $date; $articles{$id} = { name => $name, title => $title, content => $content, wdate => $wdate, }; $self->stash( articles => \%articles, id => $id, ); $self->render('edit'); };
수정하는 페이지는 쓰기 페이지와 유사하지만 기존의 정보가 표시되야 한다는 차이점이 있습니다.
다음은 /edit/N
으로 접속했을 때 처리할 수 있는 템플릿 코드입니다.
@@ edit.html.ep % layout 'default'; % title 'EDIT'; <form action="/edit" method="post"> <input type="hidden" name="id" value="<%= $id %>"> <table width=580 border=0 cellpadding=2 cellspacing=1 bgcolor=#777777> <tr> <td height=20 colspan=4 align=center bgcolor=#999999> <font color=white><b>수정</b></font> </td> </tr> <tr> <td bgcolor=white> <table bgcolor=white> <tr> <td>이름</td> <td><input type="text" name="name" value="<%= $articles->{$id}{name} %>"></td> </tr> <tr> <td>제목</td> <td><input type="text" name="title" value="<%= $articles->{$id}{title} %>"></td> </tr> <tr> <td>내용</td> <td colspan=4> <textarea name="content" cols=80 rows=5><%= $articles->{$id}{content} %></textarea> </td> </tr> <tr> <td colspan=10 align=center> <input type="submit" value="수정확인"> </td> </tr> </table> </td> </tr> <tr> <td bgcolor=#999999> <table width=100%> <tr> <td> <a href='/list' style="text-decoration:none;"><font color=white>[목록보기]</font></a> <a href='/write' style="text-decoration:none;"><font color=white>[글쓰기]</font></a> <a href='/read/<%= $id %>' style="text-decoration:none;"><font color=white>[취소]</font></a> <a href='/delete/<%= $id %>' style="text-decoration:none;"><font color=white>[삭제]</font></a> </td> </tr> </table> </td> </tr> </table> </form>
내용을 수정한 후 POST
로 전달하므로 그에 맞는 컨트롤러가 필요합니다.
post '/edit' => sub { my $self = shift; my $id = $self->param('id'); my $name = $self->param('name'); my $title = $self->param('title'); my $content = $self->param('content'); my $sth = $DBH->prepare(qq{ UPDATE `memo` SET `name`=?,`title`=?,`content`=? WHERE `id`=$id }); $sth->execute( $name, $title, $content ); $self->redirect_to( $self->url_for('/list') ); };
수정이 끝난 후 /list
로 페이지를 이동시키는 부분을 유의해주세요.
페이지를 리다이렉트 시킬 때는 $self->redirect_to()
함수를 이용합니다.
그림 7. 수정된 1번 내용 (원본)
수정 기능도 이제 잘 동작하는군요.
삭제는 비교적 간단합니다.
다음은 /delete/N
으로 접속했을 때 처리할 수 있는 컨트롤러 코드입니다.
get '/delete/:id' => sub { my $self = shift; my $id = $self->param('id'); my $sth = $DBH->prepare(qq{ DELETE FROM `memo` WHERE `id`=$id }); $sth->execute(); $self->redirect_to( $self->url_for('/list') ); };
삭제 후 /list
로 이동하기 때문에 따로 템플릿 코드가 필요하지 않죠.
자세히 살펴보면 /read/$id
와 /edit/$id
에서 동일한 코드를 사용하고 있습니다.
코드가 중복되는 것은 좋지 않죠. 중복된 코드를 줄이기 위해 helper
를 사용합니다.
다음과 같이 동일 코드를 helper
로 작성합니다.
helper db_select => sub { my ( $self, $input_id ) = @_; my $sth = $DBH->prepare(qq{ SELECT * FROM memo WHERE id=$input_id}); $sth->execute(); my %articles; my ( $db_id, $name, $title, $content, $date ) = $sth->fetchrow_array; my ($wdate) = split / /, $date; $articles{$db_id} = { name => $name, title => $title, content => $content, wdate => $wdate, }; return \%articles; };
방금 작성한 헬퍼 코드를 호출하려면 $self->db_select()
형식으로 사용합니다.
my $articles = $self->db_select($input_id); my ($id) = keys %$articles; $self->stash( articles => $articles, id => $id, );
다음은 전체 코드와 SQL 스키마 코드입니다.
잘못되거나 개선할 부분이 있다면 갱신이 될 예정이니 이 점 참고해주세요. :-)
웹에 대한 많은 이해와 실전 경험이 있다면 잘 정리된 Mojolicious
문서만 보더라도 금방 이해하고 응용할 수 있습니다.
다만 웹이 익숙치 않다면 문서만 보았을때 힘든 부분이 꽤 많습니다(제가 힘듭니다).
비록 CSS나 자바스크립트도 사용하지 않고 구식의 HTML 코드를 사용하고는 있지만
간단하고 짧기 때문에 이해하기에는 더 용이하지 않을까 바래봅니다.
이 예제와 글이 저처럼 문서를 보다가 헤매시는 다른 분들께도 작은 도움이 되었으면 합니다.
Artwork by
@namanvara,
Hyungsuk Hong
& Inkyung Park.
Designed by
Hojung Youn
& Keedi Kim.
Articles by
Seoul Perl Mongers.
Edited by
Keedi Kim.
Hosting generously sponsored by
Yuni Kim.
Sponsored by
SILEX.
.-''' __ __ / \/ \/ \ =-_- | \. -____- / \ // /|| '' //| //|| == = == ==