내가 경험한 웹의 역사 share
World Wide Web 의 역사는 오래되지 않았다.
내가 야자를 하던 시절 세상에 처음 소개되었으며, 군을 제대하고 복학을 할 때 즈음 국내에도 막 붐이 일기 시작했었다. 대학을 진학하면서 내가 처음 가진 PC 는 486SX 33MHz 였던 것으로 기억한다. 작전병으로 군생활을 처음 시작했을 때는 전자 타자기에 먹지를 댄 종이를 끼우고 보고서를 작성했고, 곧 MS-DOS 로 운영되는 PC 가 보급되었다. 군 생활 내내 MS-DOS 환경에서 \>hwp
를 쳐서 실행되는 아래아 한글을 사용했으며, 병장이 되어서야 Windows 95 가 보급되었다.
그 당시의 웹은 모두 Static 페이지들이었다. 방문자의 컨텍스트나 그들의 추가적인 액션에 따라 동적으로 변하는 문서가 아닌, 모든 사용자에게 동일한 화면를 보여준다는 의미이다. 국내에 Dynamic Web 이 태동한 시기는 90 년 대 후반으로 기억되며 그 때 당시의 Dynamic 웹 들은 이렇게 생겼었다.
처음 웹 개발을 시작하게된 계기는 군 제대 후 복학 전, 학교에서 시행하는 “교내 홈페이지 경진대회” 소식을 접하고 부터였다. 경영학과 2 학년 1학기 휴학하고 군 입대! 게임이나 할 줄 알고, Lotus123, 아래아 한글 정도 할 줄 아는 완전 개발 초보였다. 속해 했던 클래식 기타 동아리의 홈페이지를 만들겠노라고 무작정 덤빈 것이다. 복학 전 3개월을 주경야독으로 개발했고, 결과는 말도 안되게 우수상~
웹 개발에는 입문을 했고, 기업들 홈페이지 알바 뛰면서 번 돈을 모아서, 방학 중에 C 와 Oracle 특강을 들었다. 그 덕에 98년 즈음에 Dynamic Web 을 C 라는 언어로 시작할 수 있었다.
오래된 zip 파일을 풀었다. (난 대학 시절에 만든 리포트를 포함한 모든 자료를 아직도 보관하고 있다.) 아래는 cgic
라이브러리를 이용하여 html 폼 입력을 파싱하여 화면에 뿌리는 코드이다.
// write.c
#include "cgic.h"
#define MAX_LEN 255
int str_check(char *str) {
if (str === NULL) {
return 0;
}
if (strlen(str) === 0) {
return 0;
}
return 1;
}
void cgi_error_message(char *msg) {
cgiHeaderContentType("text/plain");
printf("%s", msg);
exit(1);
}
int cgi_main(void) {
char name[MAX_LEN], title[MAX_LEN], doc[MAX_LEN], email[MAX_LEN], homepage[MAX_LEN];
cgiFormString("writer", name, MAX_LEN);
cgiFormString("title", title, MAX_LEN);
cgiFormString("doc", doc, MAX_LEN);
cgiFormString("email", email, MAX_LEN);
cgiFormString("homepage", homepage, MAX_LEN);
if (! str_check(name)) {
cgi_error_message("Please provide you name");
}
if (! str_check(title)) {
cgi_error_message("Please provide a title");
}
if (! str_check(doc)) {
cgi_error_message("Please provide post contents");
}
cgiHeaderContentType("text/html");
printf("<html>\n");
printf("<head><title>GuestBook</title></head>\n");
printf("<body>\n");
printf("<h2>GuestBook</h2>\n");
printf("<hr>\n");
printf("<p>\n");
if (str_check(email)) {
printf("<a href=\"mailto:%s\">%s</a>\n", email, name);
} else {
printf("%s\n", name);
}
if (str_check(homepage)) {
printf("<small><a href=\"%s\">%s</small>\n", homepage, homepage);
}
printf("</p>\n");
printf("<p>What you wrote is:</p>\n")
printf("<table border=\"1\">\n");
printf("<tr>\n");
printf("<td><b>Title</b> : %s</td>\n", title);
printf("</tr>\n");
printf("<tr>\n");
printf("<td>%s</td>\n", doc);
printf("</tr>\n");
printf("</table>\n");
printf("<p>Your post has been saved !</p>\n");
printf("</body>\n");
printf("</html>");
}
C 는 굉장히 견고한 언어이지만, 내가 하려는 웹 개발에서는 너무나도 불편함이 많았다. printf()
함수를 이용하여 HTML 코드를 한 줄씩 stdout 으로 출력하는 방법만이 C 에서 웹 서버로 무엇인가를 전달하는 유일한 방법이었다.
\> gcc write.c cgic.c -o write.cgi
또, 이렇게 개발된 코드는 컴파일해서 서버에 올려야 했다. 컴파일 언어의 특성상 코드 수정 -> 확인의 순회 과정의 불편함으로 인해 생산성이 떨어지는 문제도 있었다.
해서, 웹 프로그래머들은 Perl 언어로 자연히 눈을 돌리게되었다. Perl 은 HEREDOC 문법과 String Interpolation 을 이용해서 C 의 불편함을 해소해 주었다. 하지만, Url 및 Form 데이터 파싱을 위해 너무 많은 노력을 들여야 했고, 작은 실수 하나에도 아주 민감하게 동작하는 녀석이었다.
# member.cgi
# @see http://happycgi.com/4570
# ...
sub parse_arguments {
local($pair, $name, $value, $content_type, $content_length, $buffer, $dump, $boundary, $line, $array_value, $i);
local(@pairs, @column);
if ($ENV{'QUERY_STRING'}) {
@pairs = split(/&/, $ENV{'QUERY_STRING'});
foreach $pair (@pairs) {
($name, $value) = split(/=/, $pair);
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
if ($name eq 'head' || $name eq 'foot') {
$value =~ s/\n/\\n/g ;
$value =~ s/\n/\\n/g ;
} elsif ($name ne 'profile') {
$value =~ s/\n//g ;
}
$FORM{$name} = $value;
}
} else {
$content_type = $ENV{'CONTENT_TYPE'};
$content_length = $ENV{'CONTENT_LENGTH'};
binmode STDIN;
read(STDIN,$buffer,$content_length);
if ((!$content_type) || ($content_type eq 'application/x-www-form-urlencoded')) {
@pairs = split(/&/, $buffer);
foreach $pair (@pairs) {
($name, $value) = split(/=/,$pair);
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
$value =~ s/\n//g unless ($name eq 'body');
$FORM{$name} = $value;
}
} elsif ($content_type =~ m#^multipart/form-data#) {
($boundary = $content_type) =~ s/^.*boundary=(.*)$/\1/;
@pairs = split(/--$boundary/, $buffer);
@pairs = splice(@pairs,1,$#pairs-1);
$i = 0;
for $pair (@pairs) {
($dump,$line,$value) = split(/\r\n/,$pair,3);
if ($line =~ /filename/) {
$gRealFile[$i++] = $line;
}
next if $line =~ /filename=\"\"/;
$line =~ s/^Content-Disposition: form-data; //;
(@column) = split(/;\s+/, $line);
($name = $column[0]) =~ s/^name="([^"]+)"$/\1/g;
if ($#column > 0) {
if($value =~ /^Content-Type:/) {
($dump,$dump,$value) = split(/\r\n/,$value,3);
} else {
($dump,$value) = split(/\r\n/,$value,2);
}
} else {
($dump,$value) = split(/\r\n/,$value,2);
if (grep(/^$name$/, keys(%CGI))) {
if (@{$FORM{$name}} > 0) {
push(@{$FORM{$name}}, $value);
} else {
$array_value = $FORM{$name};
undef $FORM{$name};
$FORM{$name}[0] = $array_value;
push(@{$FORM{$name}}, $value);
}
} else {
next if $value =~ /^\s*$/;
$FORM{$name} = $value;
chop($FORM{$name});
chop($FORM{$name});
}
next;
}
$FORM{$name} = $value;
}
} else {
print "Invalid content type!\n";
exit(1);
}
}
}
sub print_head {
$ver = " Ver$gCgiVer" if ($gMode eq "admin");
# HEREDOC 문법
# $gJavaScript 등은 글로벌 변수에 대한 Interpolation
print <<EOT;
Content-type: text/html
<html>
<head>
<title>Title</title>
<link rel="stylesheet" href="./ie4css.css">
$gJavaScript
</head>
<body>
$gConfPreBody
EOT
}
잠깐 Perl 과 PHP 3 가 혼재하던 시기가 있었으나, 2000 년 접어 들면서 Perl 을 이용한 웹 프로그래밍은 PHP 쪽으로 완전히 넘어갔던 것으로 기억한다. PHP 4 가 출시되었기 때문이다.
PHP 사용자 수의 급속한 상승은 웹 프로그래밍에서 C 와 Perl 의 불편함 때문으로 생각된다. 1) Template 엔진 처럼 쓸 수 있다는 점과, 2) $_GET
, $_POST
와 같은 전역 변수를 통해 사용자 입력값을 쉽게 가져올 수 있는 기능은 당시 많은 웹 프로그래머들을 매료시켰다.
PHP 가 인기를 얻게된 또 하나의 이유는 LAMP 스택의 부상을 들 수 있다. 정확히 기억나진 않지만 10 사용자용 윈도우 2000 서버 1 카피의 가격이 당시엔 200 만원이 넘었던 것으로 기억한다. 닷컴 열기와 함께 Linux 를 이용한 호스팅이 활기를 띠면서, 서버에 기본으로 탑재되어 있는 PHP 를 쓰지 않을 이유는 없었을 것이다. 이런 이유로 LAMP 스택이 인터넷 발전에 지대한 영향을 미쳤다고 나는 평가하고 있다.
소속된 대학원 홈페이지를 비롯해 꽤 여러 프로젝트를 PHP 로 했던 것 같다. 개발자의 삶을 살기로 결심하고 다시 돌아 오기 전에, 마지막으로 웹 개발 프로젝트를 진행한 것이 2003 년 이었던 것으로 기억하고, 그 전에 꽤 몇 년동안 PHP 를 사용한 것 같다.
<!--imceo.com/template/header/header/classic/search.php-->
<table width="780" border="0" cellspacing="1" cellpadding="0" bgcolor="#D9D1C4">
<FORM ACTION='./biz.php' METHOD=POST>
<tr>
<td bgcolor="FEFAEE">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<SELECT NAME=cat_num1 STYLE='width:110' onchange='javascript:this.form.submit();'>
<OPTION VALUE=''>Selece One</OPTION>
<OPTION VALUE=''>-------------------</OPTION>
<?
$QUERY = "SELECT * FROM iamceo_category WHERE CAT_DEPTH='1' ORDER BY GID DESC";
$CATEGORY_DATA = mysql_query($QUERY, $DB_CONNECT);
while($CATEGORY_DATA && $LIST = mysql_fetch_array( $CATEGORY_DATA ) ) {
echo "<OPTION VALUE='$LIST[CAT_NUM]'"; if ($cat_num1==$LIST[CAT_NUM]) {echo "selected"; $SCAT_NUM1 = $LIST[CAT_NUM];}
echo ">".stripslashes($LIST[CAT_NAME])."</OPTION>";
}
?>
</SELECT>
<SELECT NAME=cat_num2 STYLE='width:110' onchange='javascript:this.form.submit();'>
<OPTION VALUE=''>-------------------</OPTION>
<?
$QUERY = "SELECT * FROM iamceo_category WHERE CAT_DEPTH='2' AND LEFT(CAT_NUM,3)='".substr($SCAT_NUM1,0,3)."' ORDER BY GID DESC";
$CATEGORY_DATA1 = mysql_query($QUERY, $DB_CONNECT);
while($CATEGORY_DATA1 && $LIST1 = mysql_fetch_array( $CATEGORY_DATA1 ) ) {
echo "<OPTION VALUE='$LIST1[CAT_NUM]'"; if ($cat_num2==$LIST1[CAT_NUM]) {echo "selected"; $SCAT_NUM2 = $LIST1[CAT_NUM];}
echo ">".stripslashes($LIST1[CAT_NAME])."</OPTION>";
}
?>
</SELECT>
<SELECT NAME=cat_num3 STYLE='width:110' onchange='javascript:this.form.submit();'>
<OPTION VALUE=''>-------------------</OPTION>
<?
$QUERY = "SELECT * FROM iamceo_category WHERE CAT_DEPTH='3' AND LEFT(CAT_NUM,6)='".substr($SCAT_NUM2,0,6)."' ORDER BY GID DESC";
$CATEGORY_DATA2 = mysql_query($QUERY, $DB_CONNECT);
while($CATEGORY_DATA2 && $cat_num1 && $LIST2 = mysql_fetch_array( $CATEGORY_DATA2 ) ) {
echo "<OPTION VALUE='$LIST2[CAT_NUM]'"; if ($cat_num3==$LIST2[CAT_NUM]) {echo "selected";}
echo ">".stripslashes($LIST2[CAT_NAME])."</OPTION>";
}
?>
</SELECT>
</td>
<td>
<input type="text" name="keyword" size="20" style='border:solid 1 gray;'>
</td>
<td>
<div align="center"><input type=image src="./template/header/<?echo$Header_Skin;?>/image/btn_search.gif" width="58" height="20" border=0></div>
</td>
<td>
<div align="center"><a href='./search.php'><img src="./template/header/<?echo$Header_Skin;?>/image/btn_search_m.gif" width="58" height="20" border=0></a></div>
</td>
<td>
<div align="center"><a href='./search.php?query=help'><img src="./template/header/<?echo$Header_Skin;?>/image/btn_search_h.gif" width="70" height="20" border=0></a></div>
</td>
<td>
<div align="center"><A HREF='#' onclick="javascript:window.external.AddFavorite('http://www.iamceo.com','IamCEO.com')"><img src="./template/header/<?echo$Header_Skin;?>/image/add_site.gif" width="98" height="26" border=0></a></div>
</td>
</tr>
</table>
</td>
</tr>
</FORM>
</table>
뷰 코드와 DB 쿼리를 포함한 비즈니스로직이 막 뒤죽박죽이다. 지금 보면 완전 웃기지만, 당시에는 다 저렇게 짰다.
혹, 이 글을 읽으시는 방문자 중에, “개발 실무”에서 지금도 PHP 코드를 이렇게 짜고 계시다면, 절대 절대 안 됩니다. 12 년 전의 개발 방식 입니다.
Ruby (일본 밖에 알려진 것은 2000 년) 가 점점 사용자를 확산해 가던 시기 즈음에 웹 프로그래밍과 관련된 취미 활동을 모두 접고, 생계를 위해 직업 세계에 뛰어 들었다. 10 년의 공백을 가진 후, 다시 돌아온 2012 년 웹 개발 생태계의 모습은 너무나도 달랐다.
그림 출처 : The Laravel Ecosystem, by Dries Vints, at Laracon 2015
우선, Ruby 생태계는 Ruby on Rails (2004) 란 걸출한 웹 프레임웍을 중심으로 성장해 있었고, Github (2005) 란 대형 서비스가 그 중심에 있었다. 듣도 보도 못했던 Python 은 Google/Youtube 의 성공으로 꽤 저변이 확산되어 있었다. PHP 또한 Facebook, Wikipedia 와 같은 대형 서비스를 성공 시키면서 PHP 생태계를 이끌어 가고 있었다. Stack Overflow (2005) 와 Amazon Web Service (2006) 는 개발자들의 생산성을 높임과 동시에, 한편으론 개발자들간의 격차를 더 크게 만들었다. 변화를 추구하는 개발자와 머물러 있으려는 개발자 사이의 격차를 말하는 것이다.
나를 더 놀라게 만든것은 웹의 가능성과, 그에 따라 수반될 수 밖에 없는 개발의 복잡도 였다. Ruby on Rails 로 개발 프로젝트를 다시 시작했지만, Ruby 의 문법적인 이질감은 1 년이 지나도 극복되지 않았고, PHP 에 대한 향수로 다시 돌아오게 되었다. 다행히 내 눈에 처음 들어온 것은 라라벨 (Laravel) 프레임웍이었고, 이 때문에 마흔 넘어 내 인생은 조금씩 달라지고 있다.