본문 바로가기

Common Gateway Interface/Perl

[옛 강좌] 22. Perlprog - BBS 1

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

 이 게시물은 지금은 폐쇄되어 접속되지 않는 Kim Young Soo(http://hours.interpia98.net/~unisoo/)님의 웹 사이트에 2001년경 게시된 내용을 바탕으로 오늘날 웹 환경에 맞게 내용을 덧붙였습니다.

Perlprog - BBS 1

Description

게시판 만들기 1 - 라이브러리, 서브루틴


BBS 만들기 1

 제 강좌 중 의문나는 점이나, 마음에 안드는점, 소스가 이상하다던가, 알고리즘이 마음에 안든다던가, 더 좋은 생각을 가지고 계시면 동정의 여지없이, 게시판에 남겨 주시기 바랍니다.

 그러면 게시판을 만들기 전에 준비해야 할것 몇 가지를 이번에 얘기하겠습니다. 당장에 들어가고 싶지만서리.... 문법얘기를 할때 얘기하지 않은 부분이 있구여, 좀 더 효율적인 프로그램이 되기 위해선 꼭 알아야 할 사항들이기 때문입니다.

라이브러리(Library)

 그럼... 각자의 스타일이 모두 다르고, 프로그래밍의 성향이 다르기 때문에 우선은 어떻게 작성을 할 것인지를 미리 생각해보죠.

 지금부터 말씀드리는 건 제 생각이지 모두가 그렇다는 건 아닙니다. 여러분들 나름대로 멋찐걸 생각해 보세요. 제가 드리는 말씀은 참고 사항이라 생각하시구여...

 먼저 말씀 드릴건 프로그램을 모듈화하는 겁니다. 프로그램을 하나의 파일에 모두 넣는다는건 그리 효율적이지 못하죠. 가뜩이나 게시판같은 프로그램은 덩치가 커지게 되고, 나중에 소스를 수정할려구 해도 엄두가 안나죠.

 그래서 자주 쓰는 프로그램들을 함수화 해서 하나의 라이브러리를 만들어서 필요할 때마다 꺼내서 쓰고, 가능하면 프로그램을 작은 함수로 나누어서 작업하는게 보는것두 편하고, 수정할때도 편하겠죠.

 다른 소스들을 보면 require "...";라는 문구를 매우 자주 볼 수 있습니다. 이 require 문은 Perl 인터프리터에게 매개변수로 제시된 파일을 찾아서 그것이 프로그램의 일부분인 것처럼 사용할수 있게 해달라고 요구하는 부분입니다.

 헤더를 찍어주는 print "Content-type: text/html\n\n";와 넘어온 데이터를 파싱하는 작업등은 매우 자주 쓰이게 되죠. 이런 것들을 미리 만들어 놓고, 필요할 때 require 문을 사용해서 쓰는 겁니다.

 만약에 board.cgi라는 파일에

#! /usr/local/bin/perl
require "my-lib.pl";
# ......
					

이라고 선언 하였다면, my-lib.pl에 정의되어 있는 함수들을 board.cgi 프로그램 내에서 마음껏 사용할 수 있습니다. 재정의할 필요는 없죠.

 그러면 my-lib.pl을 만드는 것은 그 파일안에 서브루틴을 정의하여 두기만 하면 됩니다.

# my-lib.pl
sub print_header
{
	print "Content-type: text/html\n\n";
}
return 1;
					

my-lib.pl 내용이 위와 같다면, board.cgi 파일에서는 &print_header라고만 써주면 되는거죠.

 위의 내용에서 sub print_header{}는 리턴하는 값이 없기 때문에 그냥 두었지만, my-lib.pl 마지막에 return 1;을 사용했는데요, 펄에서는 "널(NULL) 값 또는 0"거짓(=false)으로 판별하고, 어떤값이 존재한다거나 1참(=true)으로 판별하죠.

덧붙임

 0 또는 NULL = 거짓, false

 그 외의 값 = 참, true

 그러니 my-lib.pl 파일을 불러서 쓴다면 그 파일이 참 값을 가지고 있어야 제대로 수행이 됩니다. 만약 my-lib.pl 의 마지막에 어떤값도 리턴하지 않는다면 프로그램은 에러를 낼 겁니다.

서브루틴(Subroutine)

 서브루틴을 만드는 방법은 sub 함수명 { 코드 }라고 정의하시면 됩니다. 그리고 호출 할때는 앞에 '&'를 붙여서 '&함수명' 하시면 되구여. 여기서 서브함수에 대해서 좀 더 알아 보겠습니다.

 매개 변수를 필요로 할때가 있을겁니다.

$url = "<a href=\"http://user.chollian.net/~unisoo\">http://user.chollian.net/~unisoo</a>";
&location_print($url) ;
					

라고 매개변수를 넘겨서 어디론가 이동하고자 한다면,

sub location_print
{
	my ($location) = @_;
	print "Location: $location\n\n";
}
					

이렇게 하시면 됩니다. @_변수는 펄에서 쓰이는 특수 변수 중 하나인데여.. 서브함수로 넘겨지는 매개변수를 담고 있습니다. 물론 여러개의 값을 넘길 수도 있죠.

합을 구하는 함수를 만든다고 하면여..

sub get_sum
{
	my ($first, $second, $third) = @_;
	my $total = $first + $second + $third;
	return $total;
}
					

이렇게 하시면 되구여, 값을 받으실때는

$total_sum = &get_sum(2, 3, 5)

하시면 10의 값을 가지게 됩니다.

 값을 받는 또다른 방법은

my (@number) = @_;

하신다음

$number[0], $number[1], $number[2]

하셔두 되구여,

my $first = $_[0];
my $second = $_[1];
my $third = $_[2];
					

이렇게 하셔두 됩니다. 어떤걸 쓰느냐는 프로그램 하는 사람 맴이죠.

 그리고 my 함수는 리스트된 각각의 변수들을 블록 내에서 local()로 선언합니다. 근데 my()함수가 local() 함수보다는 더 안전합니다.. 그 이유는 my 함수는 전역변수의 공간을 침범하지 않으며, 각각의 서브루틴이 실행될 때마다 새로운 복사본을 생성하기 때문입니다.

 local() 함수도 이 안에 정의된 변수들을 블록에만 적용되도록 정의합니다. 이 함수를 사용한다고 하셔도 전역변수로의 침범은 없습니다. my()와는 다릅니다.

 local() 함수는 1개 이상의 전역 변수를 특정 내부의 블록, 서브루틴, 혹은 파일에 한정하여 변수의 값을 제한하게 됩니다.

이런점들을 생각하지 않고 지역변수와 전역변수를 사용하실거면 my()가 안전하죠.

마지막으로 넘어온 변수들을 파싱하는 함수 하나만 더 생각해보고 이것에 대한 얘기를 마치도록 하겠습니다.

sub parse_input
{
	my ($temp, $list, @pairs);
	local(*input) = @_;
	
	if (&form_method eq 'POST')
	{
		read(STDIN, $temp, $ENV{'CONTENT_LENGTH'});
	}
	else
	{
		$temp = $ENV{'QUERY_STRING'};
	}
	
	@pairs = split(/&/, $temp);
	
	foreach $list(@pairs)
	{
		($key, $value) = split(/=/, $list);
		$value =~ tr/+/ /;
		$value =~ s/%(..)/pack("c", hex($1))/ge;
		$input{$key} = $value;
	}
	
	return 1;
}
					

 위의 서브루틴을 잠깐 보죠... 많이 본 코드죠? 이 함수를 등록한다면 어떤 폼에서 어떤 데이터를 보내더라도 &parse_input(*fields); 하면 모든 절차는 끝나게 되죠. '*'에 대한 얘기를 할려구 그랬습니다.

 해쉬를 그냥 서브루틴으로 보내서 데이터의 작업을 하게되면 원하는 결과 값이 나오질 않습니다. 펄에서는 %해쉬 자체가 넘어가는 것이 아니라 해쉬가 가지고 있는 값을 가지고 작업을 하려고 하기 때문이죠.

 '*' 는 타입글로브(typeglob)라고 하죠. 요즘엔 실 레퍼런스(real reference) 타입이 있어서 자주 사용 안한다고는 하지만 제가 배운건 * 이기 때문에.... *foo = *bar; 라고 한다면, 변수의 타입에 관계없이 foo는 bar와 동일한 값을 갖게 됩니다.

그리니깐 위에서 *fields*input으로 넘긴거니깐, input으로 사용된 모든 값들은 fields에서 사용할 수 있는 겁니다.

그래서 input을 my가 아닌 local로 선언하죠. 이 예로 my와 local의 차이점을 생각해 보시는 것두 좋을 듯....

 또 한가지 위의 서브루틴은 데이타 전송방식까지 알아서 처리 해준다는거죠. 가장 대표적인 라이브러리가 있다면, "cgi-lib.pl" 이 있죠. perl 5 부터는 모듈이라는 개념을 도입해서 좀 더 발전된 Perl 프로그램을 할 수 있도록 하죠.

 여기서두 가장 대표적인 모듈이 있다면, "CGI.pm" 이죠. 시간 있을 때 이 소스를 한번 보시는 것도 매우 좋습니다.

basic.pl - 기본 라이브러리

 먼저 위에서 배운 내용을 토대로 우리만의 라이브러리를 하나 만들죠. 물론 "cgi-lib.pl"을 사용한다면 이런 수고는 덜수 있죠. 그래도 직접해보는 것이 훨 낫겠죠?

 그럼 우리만의 라이브러리 이름은 "basic.pl"이라고 하죠. 그리고 다음과 같이 함수 몇 개만을 우선 넣어 두도록 합니다.

# basic.pl
# 나만의 라이브러리
sub form_method
{
	my ($method);
	$method = $ENV{'REQUEST_METHOD'};
}

sub print_header
{
	print "Content-type: text/html\n\n";
}

sub parse_input
{
	my ($temp, , $temp_1, @pairs);
	local (*input) = @_;
	
	if (&form_method eq 'POST')
	{
		read(STDIN, $temp, $ENV{'CONTENT_LENGTH'});
	}
	else
	{
		$temp = $ENV{'QUERY_STRING'};
	}
	
	@pairs = split(/&/, $temp);
	
	foreach $temp_1(@pairs)
	{
		($key, $value) = split(/=/, $temp_1);
		
		$value =~ tr/+/ /;
		$value =~ s/%(..)/pack("c", hex($1))/ge;
		$input{$key} = $value;
	}
	
	return 1;
}

return 1;
					

 우선 이렇게 3개만 넣죠. 위에서 보면 어디는 return을 쓰고 어디는 쓰지 않았는데, 펄에서 는 특정한 값이 return문에 의해 선언되어 있지 않으면 서브루틴내에서 수행되는 마지막 할당문이 서브루틴의 값으로서 리턴되게 됩니다. sub parse_input {}과 같이 리턴되는 값이 참(1)이라고 하지 않으면 이썅한 값을 넘기게 되겠죠.

 그리고 위에서도 말씀 드렸지만, basic.pl의 마지막에 참이라고 리턴하지 않으면 아예 실행이 안되죠. 그럼 하나씩 보도록 하죠.

 sub form_method {} 함수는 데이터 전송방식을 담은 환경변수를 사용해서 그 전송방식을 알아내는 함수죠.

 sub print_header {} http 헤더를 찍어주는 함수구여, sub parse_input {} 함수는 넘어온 데이터를 디코딩하죠. 여기에 &form_method 함수를 호출해서 전송방식을 알아낸 후 그에 맞게 디코딩하는 함수 입니다.

board_write.cgi - 입력 폼

 자. 이제 준비는 다 되었다구 생각이 드네요.

 그럼 게시판 만들기를 시작하죠. 이것저것 복잡한건 나중으로 하고, 우선 입력폼부터 생각해요. 그리고 저장. 입력폼의 이름은 ``board_write.cgi'' 라고 하죠. 그리고,

#! /usr/local/bin/perl
require "basic.pl";

&print_header;

print "...     # <--- 여기서 부터 입력폼을 만듭니다.
					

참고로,

print << HTML; 
프린트할 내용     
HTML
					

이렇게 하시면 중간의 '프린트할 내용' 이 출력이 되죠.

 또 어떤 분은 print qq( 프린트할 내용 ); 이렇게도 하더군요. 저는

print " <-- 이렇게 해 놓구 마지막에 가서 \n''; 이렇게 한답니다. 위 또한 어떤걸 쓰느냐는 프로그램하는 사람 맴이죠.

 입력폼에 어떤걸 넣을까요.

  1. 제목
  2. 이름
  3. 전자우편
  4. 홈페이지
  5. 내용

이렇게만 넣죠.

 다 됐습니다. 저장하고, 브라우저 url에 ..../cgi-bin/board_write.cgi 하고 엔터.

 그러면 입력폼이 뜨겠죠? 물론 <form action="./add_data.cgi" method=post> 입니다. 입력한 내용을 저장하는 파일을 "add_data.cgi"로 하겠습니다.

 자.. 이젠 데이터를 보냈으니 저장을 해야겠죠. 하지만 방명록처럼 하진 않을 겁니다. 하나의 파일에 무~씩하게 다 넣지 않을겁니다. 나중에 검색을 한다거나, 하나의 글이 읽혀질 때마다 메모리를 너무 많이 잡아먹게 되죠. 삭제를 하더라도 좀 불편하게 아니죠.

 그래서 저는 리스트를 보여줄 때와 검색할때 쓰기위한 "board.idx"파일과, 사용자가 입력을 하면 각각의 내용을 다 따로 저장할 겁니다. "board1.dat, board2.dat...." 이렇게 말이죠. 그래야 나중에 좀 편하지 않을까.... 저장 방식은 여러분들 각자의 맘입니다. 가장 효율적인 방법을 찾아서 해야죠. 만약에 1000명이 저장을 했는데 하나의 파일에 저장을 하였다면..... 후.... 읽어들일때 @data = <FILE>; 이라는 명령어를 썼다고 생각해 보세용.... 정말 비효율적이죠.... 물론 그리 많은 사용자가 오지 않는 곳이라면 별 상관은 없겠지만요... 그래도 프로그램을 하는 사람은 여러가지를 염두에 두고 하지 않으면 안되겠져....

NOTES

 이번엔 여기까지만 하겠습니다. 다음에 저장하는 방식에 대해서 다뤄보죠. 각자 생각해 보시고 프로그램도 맹글어 보시고, 그래서 제가 한 내용과 여러분이 한 내용을 비교도 해보구여... 물론 제가 한게 여러분들이 만든것 보다 우수한건 아닙니다.


이 문서는 Perl 패키지내의 pod2html를 이용하여 만들었습니다. - Kim Young Soo