내가 경험한 웹의 역사 share

today 2016-02-19 face Posted by appkr turned_in Learn & Think forum 0

World Wide Web 의 역사는 오래되지 않았다.

내가 야자를 하던 시절 세상에 처음 소개되었으며, 군을 제대하고 복학을 할 때 즈음 국내에도 막 붐이 일기 시작했었다. 대학을 진학하면서 내가 처음 가진 PC 는 486SX 33MHz 였던 것으로 기억한다. 작전병으로 군생활을 처음 시작했을 때는 전자 타자기에 먹지를 댄 종이를 끼우고 보고서를 작성했고, 곧 MS-DOS 로 운영되는 PC 가 보급되었다. 군 생활 내내 MS-DOS 환경에서 \>hwp 를 쳐서 실행되는 아래아 한글을 사용했으며, 병장이 되어서야 Windows 95 가 보급되었다.

그 당시의 웹은 모두 Static 페이지들이었다. 방문자의 컨텍스트나 그들의 추가적인 액션에 따라 동적으로 변하는 문서가 아닌, 모든 사용자에게 동일한 화면를 보여준다는 의미이다. 국내에 Dynamic Web 이 태동한 시기는 90 년 대 후반으로 기억되며 그 때 당시의 Dynamic 웹 들은 이렇게 생겼었다.

90 년대 후반의 Yahoo!

• • •

처음 웹 개발을 시작하게된 계기는 군 제대 후 복학 전, 학교에서 시행하는 “교내 홈페이지 경진대회” 소식을 접하고 부터였다. 경영학과 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 년 웹 개발 생태계의 모습은 너무나도 달랐다.

Web Programming until 2002

그림 출처 : 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) 는 개발자들의 생산성을 높임과 동시에, 한편으론 개발자들간의 격차를 더 크게 만들었다. 변화를 추구하는 개발자와 머물러 있으려는 개발자 사이의 격차를 말하는 것이다.

Web Programming in 2015

나를 더 놀라게 만든것은 웹의 가능성과, 그에 따라 수반될 수 밖에 없는 개발의 복잡도 였다. Ruby on Rails 로 개발 프로젝트를 다시 시작했지만, Ruby 의 문법적인 이질감은 1 년이 지나도 극복되지 않았고, PHP 에 대한 향수로 다시 돌아오게 되었다. 다행히 내 눈에 처음 들어온 것은 라라벨 (Laravel) 프레임웍이었고, 이 때문에 마흔 넘어 내 인생은 조금씩 달라지고 있다.

comments powered by Disqus
keyboard_arrow_up