본문 바로가기

Common Gateway Interface/Perl

[옛 강좌] 23. Perlprog - BBS 2

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

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

Perlprog - BBS 2

Description

게시판 만들기 2 - 입력, 환경설정, 저장


BBS 만들기 2

 저번에 입력폼을 만들 때 하지 않은 게 있네여... 암호를 넣을 수 있는 입력 폼도 하나 만들어 야죠. 수정과 삭제 시 필요하니깐여....

 다시 한번 말씀드리면 입력 폼의 파일 이름은 "board_write.cgi"이며, 들어갈 내용은 제목, 이름, 전자우편, 홈페이지, 내용, 비밀번호.. 이렇게 6개의 내용을 입력 받습니다.

 <form action=./cgi-bin/add_data.cgi method=post> 방식은 post이며, 전송된 내용을 받아서 입력하는 프로그램명은 "add_data.cgi"입니다. 이 파일 이름들이야 여러분들 마음대로 하셔두 됩니다.

board_write.cgi - 입력

## board_wirte.cgi
#! /usr/local/bin/perl

require "./basic.pl";
require "./board.conf"; # <- 요건 아래서 말씀 드리겠습니다.  
# 미리 정의한 파일을 불러옴.

&print_header;    
# http헤더를 찍습니다.

print "
	<html>
		<head>
			<title></title>
			<script language=JavaScript>
				function frm_send()
				{
					var strvl = new Array(5);
					strvl[0] = document.frm.title.value;
					strvl[1] = document.frm.name.value;
					strvl[2] = document.frm.email.value;
					strvl[3] = document.frm.body.value;
					strvl[4] = document.frm.pawd.value;
					
					if (strvl[0] == \"\")
					{
						alert(\"제목을 쓰셔야 되여\");
					}
					else
					{
						if (strvl[1] == \"\")
						{
							alert(\"이름을 쓰세요\");
                        }
						else
						{
							if (strvl[2] == \"\")
							{
								alert(\"전자우편을 써주세요\");
							}
							else
							{
								if (strvl[3] == \"\")
								{
									alert(\"내용은 필수 입니다\");
								}
								else
								{
									if (strvl[4] == \"\")
									{
										alert(\"암호를 써주세여\");
									}
									else
									{
										document.frm.submit();
										return (true);
									}
								}
							}
						}
					}
				}
			</script>
		</head>
		<body bgcolor=white>
			<form action=\"./add_data.cgi\" method=post name=frm>\n";
			
&print_content;

print "
				<table border=0 width=550 bgcolor=white>
					<tr>
						<td align=right width=150><font size=2 color=black><b>제 목 : </b></font></td>
						<td><input type=text name=title size=35 maxlength=80></td>
					</tr>
					<tr>
						<td align=right><font size=2 color=black><b>이 름 : </b></font></td>
						<td><input type=text name=name size=16 maxlength=80></td>
					</tr>
					<tr>
						<td align=right><font size=2 color=black>
						<b>전자우편 : </b></font></td>
                        <td><input type=text name=email size=25 maxlength=80></td>
					</tr>
					<tr>
						<td align=right><font size=2 color=black><b>홈페이지 : </b></font></td>
						<td><input type=text name=homepage size=30 maxlength=80 value=http://></td>
					</tr>
					<tr>
						<td align=right valign=top><font size=2 color=black><b>내 용 : </b></font></td>
						<td><textarea name=body rows=11 cols=60></textarea></td>
					</tr>
					<tr>
						<td align=right><font size=2 color=black><b>비밀번호 : </b></font></td>
						<td><input type=password name=pawd size=8 maxlength=8><font size=2 color=black>(8자 까지)</font></td>
					</tr>
					<tr>
						<td></td>
						<td><font size=2 color=blue>(참고사항 : HTML은 지원 되지 않습니다.)</font></td>
					</tr>
					<tr>
						<td colspan=2><hr width=100% color=#bedaff size=3></td>
					</tr>
					<tr>
						<td colspan=2 align=center><a href=\"javascript:frm_send()\">$WRITE_IMAGE</a></td>
					</tr>
				</table>
			</form>
		</body>
	</html>\n";
					

$WRITE_IMAGE는 우리가 미리 정의 하였습니다.

board.conf - 환경설정

 "add_data.cgi"를 프로그램 하기전에 한 가지 먼저하죠. 여러 환경변수들을 넣어두는 파일을 하나 만듭니다. 이곳에 들어가는 내용은 본인의 url 주소, 각 이미지 파일들의 위치, 저장될 파일들의 위치 등....

 board.conf라고 이름하시고,

### board.conf

### 게시판 환경 변수 파일
###########################################
## 사용자 디렉토리 및 파일 정의          ##
###########################################

$url = "http://web.archive.org/web/20001018041543/http://user.chollian.net/~unisoo";
$data_dir = "./data";
$idx_dir = "./data/idx";
$idx_file = "$idx_dir/board.idx";
$pwd_file = "$idx_dir/pass.pwd";

###보여주는 목록 수 정의
$list_number = 10;

###########################################
## 사용자 이미지 파일 정의               ##
###########################################

$WRITE_IMAGE = "<img src=./icons/i_write.gif border=0 alt=\"글쓰기\">";
$REPLAY_IMAGE = "<img src=./icons/i_reply.gif border=0 alt=\"답변하기\">";
$ADMIN_IMAGE = "<img src=./icons/i_admin.gif border=0 alt=\"관리자\">";
$HOME_IMAGE = "<img src=./icons/i_home.gif border=0 alt=\"홈으로\">";
$LIST_IMAGE = "<img src=./icons/i_list.gif border=0 alt=\"목록\">";
$MODI_IMAGE = "<img src=./icons/i_modify.gif border=0 alt=\"수정하기\">";
$NEXT_IMAGE = "<img src=./icons/i_next.gif border=0 alt=\"이전목록\">";
$PREV_IMAGE = "<img src=./icons/i_prev.gif border=0 alt=\"다음목록\">";
$DELE_IMAGE = "<img src=./icons/i_delete.gif border=0 alt=\"삭제\">";

###########################################
##  제목 입력                            ##
###########################################
sub print_content
{
	print "
		<table width=550 border=0>
			<tr>
				<td bgcolor=#bedaff valign=center height=35><font size=5 color=#376ed><b>BlackUnicorn's Board</b></font></td>
			</tr>
		</table>
		<br>\n";
}
1;
					

 위의 내용이 그렇게 어렵지 않죠? 보여주는 목록 수 정의는 게시판 목록 수를 몇 개씩 보여줄 것인가 하는 내용입니다.

 그리고, 게시판의 제목을 일일이 바꿔 준다는 건 무쟈게 귀찮죠.. 그래서 print_content라는 서브루틴을 만들어서 출력하게끔 하였구여, 마지막에 1은 true를 리턴하는 겁니다. 안쓰면 안되여.... 각각의 이미지또한 미리 지정하여서, 언제 어디서든 가져다 쓰시면 되구여, 이미지가 마음에 안들면 언제든 이 환경변수 파일만 수정하면 됩니다. 모든게 그렇죠. 이 파일만 만지게끔 하면 되는 겁니다.

 그럼 이제 만들어진 파일이 모두 세개지요. basic.pl, board.conf, board_write.cgi 자 이제 보내진 파일을 저장하는걸 보도록 하겠습니다.

add_data.cgi - 데이터 저장

### add_data.cgi
#! /usr/local/bin/perl
# 이 부분에 대해선 더이상 말씀드리진 않겠습니다.

require "./basic.pl";
require "./board.conf";
# 우리가 미리 만들어 놓은 환경파일과 라이브러리 파일을 불러 오는 곳입니다.
# require "basic.pl"; 하셔도 되긴 되지만, 디버깅을 해보면 에러가 뜹니다. 정확한 경로(./)를 표시하는 것이 안전합니다.

&parse_input(*fields);
# 우리가 만들어 놓은 parse_input()함수를 불러 옵니다. 설명은 지난주에 했으니깐, 하지 않겠습니다. 단, 이 라인은 넘어온 변수들은 %fields 에 저장하는 라인입니다.

$date = &get_date;
# 여기서도 우리가 미리 만들어 놓은 get_date()함수를 호출하고 그 리턴 값을 $data변수에 저장합니다.
					

get_data()

 basic.pl 화일 안에 get_data() 함수를 적어 넣습니다.

sub get_date
{
	($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst, $date) = localtime(time);
	
	if(length ($min) == 1) { $min = '0'.$min; }
	
	$mon = $mon + 1;
	$date = "$year/$mon/$mday/$hour:$min";
	
	return $date;
}
						

 이 함수는 방명록 만들때 날짜 구하는걸 해 보았기 때문에 여기선 설명을 생략하겠습니다.

$ip = $ENV{'REMOTE_ADDR'};

그리고, 접속한 사람의 IP주소를 알아냅니다.

 여기까지가 우리가 필요로 하는 모든 변수들을 초기화 하였습니다. 입력 폼에서 넘어온 각종 데이터들, 그리고 나름대로 필요한 날짜와 IP주소....

 이제 본격적으로 파일에 직접 저장해야죠. 우선 제가 한 방법으로는 파일이 총 3개가 만들어 집니다. 하나는 인덱스 번호, 이름, 제목, 조회수, 날짜만이 저장되어 있는 board.idx, 그리고 각 사용자의 패스워드가 저장되는 pass.pwd, 마지막으로 패스워드를 제외한 모든 정보를 기록하는 board().dat 파일입니다. 이 board().dat 파일은 저장할 때마다 인덱스 번호에 따라 따로 저장을 하게 됩니다. board1.dat, board2.dat... 이렇게 하는 것이 속도도 빠르고, 메모리도 덜 잡아먹고, 관리하기도 편하고 등등등...

 그리고 board.idx, pass.pwd는 data/idx폴더에 넣었구여, board().dat는 data폴더에 넣었습니다. 정의는 환경파일에 다 있습니다.

 제 생각이 그렇다는 겁니다. 머 여러분들은 다르게 생각하실수도 있는거구여....

 그렇다면, 누군가 저장을 하였다면, 그 사람이 저장한 데이터의 고유 인덱스 번호를 생성해야 합니다. 인덱스 번호가 겹치면 엉망이 되죠.

$idx = &get_idx($idx_file);

 이 라인이 바로 그 라인입니다. 여기서 우리는 get_idx() 함수를 만들어 줘야 합니다. 이 함수는 기존의 파일을 조사해서 마지막으로 저장된 인덱스 번호가 어떤건지 알아내는 거죠. 인수로는 $idx_file(board.conf에서 벌써 지정했음) 파일을 넘기고, 그걸 $idx에 저장합니다.

get_idx()

 get_idx() 함수의 내용 : 이 내용은 basic.pl에 넣어 줍니다.

sub get_idx
{
	my ($filename) = @_;
	my ($max_idx, $i, @b_idx);
	$i = 1;
	
	open (FILE, "$filename") || die "Can't open file\n";
	
	while (<FILE>)
	{
		$max_idx = $_;
		last if ($i == 1);
	}
	
	close (FILE);
	
	@b_idx = split(/::/, $max_idx);
	
	$max_idx = $b_idx[0];
	
	return $max_idx;
}
						

 참고로 이 인덱스 파일만은 미리 아무 내용없는 파일을 만들어 놓으셔야 합니다. 패스워드 저장 파일인 pass.pwd 파일도 마찬가지입니다. 아니면 없으면 만들도록 프로그램 하시던지요.

 위의 내용에서 파일의 모든내용을 배열에 저장하지 않았습니다. 효율적이지 못하죠. 맨 윗줄의 내용만을 뽑아서 그 인덱스 번호만 알면 되죠. 그게 마지막 인덱스 번호니깐여.. 처음 입력하는 사람을 위해서 없으면 하나 만들죠.

 그리고 아무 내용이 없으면 인덱스 번호를 1로 하구여, 그렇지 않으면 기존의 수에 1을 더합니다.

if (!$idx)
{
	$idx = 1;
}
else
{
	$idx += 1;
}
						

 그리고 조회수를 0으로 초기화 해야죠. 처음이니깐...

$count = 0;

 그리고, 우리가 정한 분리자인 :: 가 들어 오면 안되니깐, 확인합니다. 한꺼번에 다 검사하죠. 그리고 입력을 했는지, 안 했는지는 자바스크립트로 확인을 했습니다.

if ($fields{'title'} =~ /::/ || $fields{'name'} =~ /::/ || $fields{'email'} =~ /::/ || $fields{'homepage'} =~ /::/ || $fields{'body'} =~ /::/)
{
	print "Content-type: text/html\n\n";
	print "<html><body bgcolor=white>";
	print "
	<table width=550 border=0>
		<tr>
			<td height=35 bgcolor=#bedaff valign=center><font size=5 color=#376ed><b>Q & A - Web board</b></font></td>
		</tr>
	</table><br>\n";
	print "<font size=2><b>사용할수 없는 문자 <font color=red><b>::</b></font> 를 사용하셨습니다.</b></font><p>";
	print "<font size=2><b>다시 한번 확인하여 주십시요.</b></font>";
	print "</body></html>";
	
	exit 0;
}
						

 입력이 부정확 하면 에러 메세지를 출력하고 프로그램을 끝내게 됩니다.

 그럼.. 인덱스 파일을 엽니다. 그리고 :: 를 사용해서 '인덱스 번호::이름::제목::조회수::날짜' 만을 저장합니다. 아래의 알고리즘은 방명록 만들기 때와 비슷하니깐 보고 참고하세요. 최근의 것이 가장 위에 올라오게 하기 위해섭니다.

open (FILE, "$idx_file") || die "Can't open file\n";
@data = <FILE>;
close (FILE);

open (FILE, ">$idx_file") || die "Can't open file\n";
print FILE $idx,"::$fields{'name'}::$fields{'title'}::",$count,"::",$date,"\n";

foreach (@data)
{
	print FILE $_;
}

close (FILE);
						

 그리고 모든 데이터를 저장 할 파일명을 설정합니다. $data_dir 안에 board에 $idx번호를 붙이고, ".dat"를 붙여서 board(인덱스번호).dat 라는 파일명을 만들어 냅니다. 그래야 겹치지 않죠.

$datafile = $data_dir."/board".$idx.".dat";
						

 그리고 그 데이터 파일을 만들어서 필요한 내용을 일정한 형식에 맞게 저장합니다.

open (FILE, ">$datafile") || die "Can't open file\n";

print FILE "IDX=$idx\n";
print FILE "COUNT=$count\n";
print FILE "DATE=$date\n";
print FILE "HOMEPAGE=$fields{'homepage'}\n";
print FILE "NAME=$fields{'name'}\n";
print FILE "IP=$ip\n";
print FILE "EMAIL=$fields{'email'}\n";
print FILE "TITLE=$fields{'title'}\n";
print FILE "BODY=$fields{'body'}\n";

close (FILE);
						

 이곳에 들어간 내용은 인덱스번호, 조회수, 날짜, 홈페이지주소, 이름, IP, 이메일, 제목, 내용, 이렇게 비밀번호를 제외하고는 모두 들어갑니다.

 패스워드 파일에 패스워드를 적습니다. 이때 인덱스 번호와 같이 적습니다.

open (FILE, "$pwd_file") || die "Can't open file\n";
@pwd_data = <FILE>;
close (FILE);

open (FILE, ">$pwd_file") || die "Can't open file\n";
$pwd = &get_password($fields{'pawd'});
print FILE $idx,"::",$pwd,"\n";

foreach (@pwd_data)
{
	print FILE $_;
}

close (FILE);
						

get_password()

get_password() 함수를 잠깐 살펴보죠. basic.pl에 다음과 같이 프로그램합니다.

sub get_password
{
	my ($pass) = @_;
	my @salt_char = (a..z, A..Z, 0..9);
	
	srand(time || $$);
	$salt = $salt_char[rand($#salt_char)].$salt_char[rand($#salt_char)];
	
	return crypt($pass, $salt);
}
						

 이 함수는 폼에서 들어온 패스워드를 암호와 하는 과정입니다. crypt($pass, $value)함수는 두 번째 인수를 이용해서, 첫 번째의 문자열을 암호와 하는 것이지요. 암호화 된 문자열을 역으로 풀어낸다는건 거의 불가능 합니다. 하지만 두 개의 값을 비교 하는 것은 무지 쉽게 되죠.

 위에서 $salt 변수는 a-z, A-Z, 0-9 까지의 문자열에서(@salt_char) 임의의 숫자를 만들어서 (rand) 그 문자로 암호화 하죠.

 srand(time || $$); # <- 펄 프로세스 ID와 현재 시간의 조합으로 임의의 숫자를 만듭니다. 그 다음 줄이 위에서 만들어진 임의의 숫자를 이용해서 $salt 문자를 만듭니다. 이 $salt 문자는 두번에 걸쳐서 만들게 되고 '.'연산자를 사용해서 두 문자를 이어줍니다.

 그리고 crypt 함수를 호출해서 주어진 값을 이용해서 암호화된 문자열을 되돌립니다. 그 값이 암호화된 암호라고 볼수 있습니다.

 위의 패스워드를 암호화하는 알고리즘은 유용하게 쓰일 겁니다. 잘 보아 두세요.

 저장이 모두 끝이 났습니다. 이제 목록을 보여주는 프로그램인 view_list.cgi로 페이지를 이동 시킵니다.

print ``Location: $url/cgi-bin/view_list.cgi\n\n'';

NOTES

 add_data.cgi 가 끝났네여... 이것이 끝나면서 basic.pl 안에는 get_date(), get_idx(), get_password() 이렇게 세개의 함수가 추가가 되었네여....

 그렇게 짧지만은 아닌 내용이었던거 같습니다. 헐~~~ 다음내용 부터는 지금까지의 내용과는 사뭇다른... 만만치 않은 내용입니다. 각자 생각해 보시구여, 그럼 다음에 이어서 계속하겠습니다.

 의문 사항은 게시판에 남겨주세요.


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