MenuIcon

Owl-Networks Archive

LoginIcon

순수 Perl 웹 CGI 에서의 웹 페이지 gzip 출력 방법 + 다량의 넋두리

| 분류: Perl | 최초 작성: 2012-06-29 00:03:59 |

이 게시물은 Perl Community & Study 카페에도 등록되어 있습니다.

0. 시작하기 전 넋두리

최근 (특히 국내에서는) 웹 쪽에서 Perl 을 사용하는 경우, 순수하게 Perl CGI를 사용하는 경우는 거의 없다고 봐도 될 것 같습니다. (Perl CGI 자체가 거의 사용 안 되고 있다고 보는 게 좋겠죠.) Perl 을 사용하는 경우에도, 각종 프레임워크를 사용하여 생산성과 성능을 끌어올리는 것이 최근의 경향이라고 할 수 있습니다. 그러나 대부분의 개인 웹마스터들에게는 이런 솔루션이 사치일 뿐입니다. 독립 서버를 이용할 금전적 여유가 되지 않으니 보통 웹호스팅(그것도 중저가형) 업체를 이용할 수밖에 없을 것인데, 대부분의 국내 웹 호스팅 서비스 업체들은 Perl 에 대해서는 그야말로 지원한다고 말하기도 부끄러울 수준으로 서포트를 해 주는 것이 현실입니다.

단적으로, 거의 모든 국내 웹 호스팅 업체들의 서버 내 Perl 버전은 5.8.x 입니다. 이해는 합니다. 서버 안정성을 위해 최대한 환경을 보수적으로 가져갈 수밖에 없는 현실. 그러나 이미 Perl 버전이 안정 버전 5.16 까지 릴리즈된 상황입니다. 최근의 Perl 발전 성과를 거의 활용할 수 없죠. 게다가 대다수의 업체가 보안상의 이유를 들어 C(gcc) 권한을 주지 않습니다. 덕분에, 각종 웹 프레임워크의 사용 뿐만 아니라, Perl 의 존재 이유의 상당 부분을 차지한다고 할 수 있는 CPAN 모듈의 자유로운 사용도 제한을 받습니다. 그렇다고 필요한 CPAN 모듈의 설치를 업체 측에 요청하면 돌아오는 대답은 항상 판에 박은 답변이죠. 개인적인 모듈 설치가 필요하다면 서버 호스팅을 사용하라는 친절한 답변도 덤으로 따라옵니다.

결국, 현재의 웹 호스팅 환경에서는 Perl 의 장점을 전혀라고 말해도 틀리지 않을 정도로, 활용할 수 없는 현실입니다. 기본적인 개발과 성능 양쪽 모두 생산성이 너무 떨어집니다. 우주선을 만들기는 고사하고, 남들이 수도 없이 만들어놓은 바퀴를 순수한 Perl 코드만으로 주구장창 새로 만들고 있어야 하는 꼴이죠. 뭐 되는 게 없어요.

나도 그러고 싶어요. 그런데 그럴 수가 없네요. T.T
나도 그러고 싶어요. 그런데 그럴 수가 없네요. T.T

1. 문제 상황

얼마 전부터 블로그의 트래픽이 슬금슬금 오르는 눈치를 보여서(최근에는 다시 안정모드로 되돌아갔습니다만) 블로그 및 각 페이지의 출력단에 gzip 압축을 적용하려고 시도했었습니다. 최근에는 꼭 트래픽 때문이 아니라도 여러 가지로 사용하면 좋을 옵션으로 되어 있더군요. (현재 적용된 페이지의 전송량을 보면 동일한 히트 수 기준으로 이전보다 절반 이하로 줄어든 전송량을 보입니다.) 현재 이 홈 페이지의 경우에는 블로그의 경우 적용을 위해서는 약간의 수정이 필요한 상황이지만, 다른 페이지의 경우 별다른 수정 없이 적용이 가능했습니다. 그런데...

일단, Perl 5.10 버전 이후에서 기본 모듈(Core Module)로 제공하는 IO::Compress::Gzip 모듈만 사용할 수 있다면 정말 간단하게 처리할 수 있습니다. 출력할 HTML 페이지를 작성한 후, IO::Compress::Gzip 모듈로 메모리의 페이지 자체를 gzip 압축하고(이 모듈을 사용하면 파일이 아닌 스칼라 문자열 자체를 통째로 gzip 압축할 수 있습니다), 출력 헤더에 Content-Encoding: gzip 을 포함하여 gzip 압축된 HTML 페이지와 함께 그대로 출력해버리면 됩니다.

그러나, 앞에서도 말씀드렸듯이 대부분의 웹호스팅 서버는 Perl 5.8.8 버전을 사용합니다. IO::Compress::Gzip 모듈은 둘째 치고, 일반적인 zlib 지원 모듈인 Compress::zlib 모듈조차 제공되지 않습니다. 혹시나 싶어서 문의해보니 역시나 설치 못 해준답니다.

최근 개인적으로 서버 이전 가능 여부를 타진했던 모 업체는, 심지어 IO::Compress::Gzip 모듈 깔아줄 수 있느냐고 했더니 Perl 버전이 5.8.8 이라 못 깔아준다는 도대체 말인지 막걸린지 모를 답을 주기도 하더군요. (IO::Compress::Gzip 모듈이 Perl 5.10 에서 기본 모듈로 포함됐다는 이야기를, IO::Compress::Gzip 모듈이 Perl 5.10 이상에서만 동작한다는 소리로 알아듣는 사람이 우리나라 최상위권의 웹호스팅 업체 CS팀에서 고객응대를 하고 있는 현실입니다.)

2. 해결 과정

결국, 현실을 바탕으로 하여 이 기능을 지원하기 위해서는 순수한 Perl 코드 + Perl 5.8.8 의 기본 모듈 (+ 서버에서 사용가능한 자원) 만으로 구현을 해야 한다는 이야깁니다. 그러나, 아무리 구글링을 해 봐도, Pure Perl 5.8 환경에서 다른 CPAN 모듈의 도움 없이 웹 gzip 압축 출력을 지원할 방법은 나오지 않았습니다. 그렇다고 RFC 1952 규약(gzip 규약입니다)을 통으로 코딩한다는 것도 미친 짓이죠. 더구나 지금 내 처지에..

결국 차선책을 선택했습니다. Perl 또는 그 모듈을 사용하여 메모리의 내용을 바로 gzip 압축 출력하는 방법을 포기하고, 일단 페이지를 생성한 후 파일로 저장하고, 그 파일을 gzip 으로 압축하여 출력하는, 한 단계 돌아가는 방법으로 전환하였습니다. 이 방법이라면 일반적인 서버에 기본적으로 따라와 있는 gzip 압축/해제 바이너리를 사용할 수 있을 것 같습니다. (예전에 유니코드 지원 제대로 안되던 5.6 버전 시절에 system() 문을 사용하여 iconv 를 이용했던 것을 상기하시면 됩니다.) 출력할 내용을 임시 파일로 저장하고 gzip 압축을 시도하여 출력하기 때문에 디스크 입출력에서 잡아먹는 시간이 부담이 될 것 같지만, 일단 시도는 해 볼 만 합니다.

3. 구현

다음 코드는 위 방법을 실제 코드로 구현한 것입니다. 참고로, 이 코드는 과거에 제 홈페이지에서 실제로 사용했던 코드이지만, 현재는 웹호스팅 업체를 변경하면서 좀 더 자유로운 모듈 사용이 가능하게 되었기 때문에, 이 코드는 더 이상 사용하지 않습니다.

package HTML;

our $Header = "";   # 헤더 저장. 헤더의 마지막은 항상 \n\n 으로 끝나야 함
our $Output = "";   # HTML 페이지 내용 저장

sub OUTPUT_STREAM {

    if ( $ENV{'HTTP_ACCEPT_ENCODING'} =~ /gzip/ ) {

        my ( $ip1, $ip2, $ip3, $ip4 ) = split( /./, $Param{'IP'}, 4 );
        my $makefilename = sprintf "main%x%x%x%x-%x-%x", $ip1, $ip2, $ip3, $ip4, time, $$;

        open my $fHandle, ">", "./temp/$makefilename.html";
        print $fHandle qq{$Output};
        close $fHandle;

        print "Content-Encoding: gzip\n";
        print $Header;

        system( "gzip -c ./temp/$makefilename.html" );
        unlink( "./temp/$makefilename.html" );
    }
    else {

        print $Header;
        print $Output;
    }

    undef $Header;
    undef $Output;

    return 0;
}

$HTML::Header 에는 쿠키 등 헤더의 내용이 들어가 있고 끝 부분에는 HTML 의 시작을 알리는 Content-type: text/html\n\n 이 들어 있습니다. HTML 페이지의 내용은 $HTML::Output 에 들어 있고요. CGI 가 실행되면서 생성되는 HTML 페이지의 내용이 위의 두 스칼라 변수에 차곡차곡 쌓입니다. 모든 페이지가 완성된 후, HTML::OUTPUT_STREAM() 서브루틴이 호출되면서 페이지가 클라이언트 웹 브러우저로 전송되는데, 이 과정에서 gzip 압축이 이루어집니다.

최근에는 거의 모든 브라우저가 gzip 압축을 지원하기 때문에 웹 브라우저가 gzip 압축을 지원하는지 여부는 굳이 확인할 필요가 없기도 하겠지만, 만약의 경우를 대비하여 현재 접속한 클라이언트의 브라우저가 gzip 압축을 풀 수 있는지를 먼저 확인합니다. 클라이언트의 웹 브라우저가 gzip 압축을 지원하는지는 $ENV{'HTTP_ACCEPT_ENCODING'} 의 값에 gzip 이 포함되어 있는지 보면 됩니다. 만약 포함되어 있다면 gzip 압축한 페이지를 전송하고, 없다면 일반 텍스트 페이지를 전송하면 되는 것이지요.

만약 gzip 출력을 하는 경우, gzip 압축을 위해 임시로 저장할 파일명을 정해야 합니다. 그러나 동시에 수 개의 요청이 들어오는 경우, 파일명이 동일하면 오류가 발생하거나 잘못된 페이지가 날아가는 등의 문제가 생길 수 있으므로, 각 요청마다 할당되는 임시 파일의 이름은 어느 정도 고유성을 가진 것이어야 합니다. 그래서 요청자의 IP 주소, 요청 시간, Process ID 등을 요소로 하여 어느 정도 고유성을 갖는 임시 파일명을 생성합니다. 그 결과물이 $makefilename 스칼라 변수에 저장되어 사용됩니다.

이렇게 임시 HTML 파일이 생성되면, system( "gzip -c ./temp/$makefilename.html" ); 줄이 만들어진 파일 내용을 gzip 압축 상태로 출력합니다. (gzip 바이너리가 지원하는 -c 인자를 사용하면 압축한 결과가 저장되지 않고 바로 표준 출력으로 전송되므로 굳이 압축된 파일을 다시 읽어서 전송할 필요가 없습니다. 수고를 덜었네요.) 실행이 끝나면 바로 다음 줄의 unlink 명령이 임시 파일을 지워버리죠. 예제 코드에는 임시 파일이 제대로 생성되었는지 확인하는 코드가 없는데, 좀 더 완전을 기한다면 그 부분도 확인하여 처리하면 좋을 것 같습니다.

4. 또 넋두리

어찌어찌 해서 구현은 했습니다만, 실제 퍼포먼스가 어떤지는 좀 더 사용해 봐야 알 것 같습니다. 게다가, 모듈 안 깔아줘서 개고생시킨 한은 이걸로도 풀리지가 않습니다. 호스팅 업체에 얹혀사는 한, 이런 헛수고를 계속 해야 한다는 것은 그냥 숙명이네요. 하아..

최근에 웹 호스팅 서비스를 사용하면서 Perl CGI를 실제 운영에 사용하는 분이 얼마나 되려나 싶습니다만, 혹시라도 관련 기능을 고민하시는 분께서는 이런 방법도 고려해 보실 수 있겠습니다.

☞ 태그: HTML, CGI, gzip 압축, GZIP,

☞ 트랙백 접수 모듈이 설치되지 않았습니다.

☞ 덧글이 없고, 트랙백이 없습니다.

덧글을 남기시려면 여기를 클릭하십시오.
[483] < [442] [441] [439] [438] [436] ... [435] ... [434] [432] [429] [426] [424] > [19]

(C) 2000-2018, Owl-Networks. Powered by Perl. 이 페이지는 HTML 5 표준에 따라 작성되었습니다.