본문 바로가기
연구_고민/PHP & MySql

PHP 출력 제어하기

by DevG 2008. 2. 11.
Controlling PHP Output: Caching and compressing dynamic pages
(PHP 출력 제어하기 : 동적 페이지를 케싱하고 압축하기)

mod_gzip 은 gzip encoding을 지원하는 브라우저(IE Netscape 등)를 위한 IETF 표준에 따라 Gzip을 사용해서 정적 html 페이지를 압축하는 아파치 모듈입니다. mod_gzip은 다운로드시간은 4/5배 정도 더 빠르게 만들어 줍기 때문에 웹 서버에서 mod_gzip을 사용하길 강력 추천합니다. 그러나 Apache 1.x.x에서 모듈간의 필터링 메커니즘이 없기 때문에 PHP가 생성한 출력을 mod_zip으로 압축하는 것은 불가능합니다. 그렇기 때문에 직접 PHP로 압축엔진을 제작해 주어야합니다. 이 글에서는 PHP출력 제어 함수를 사용해서 페이지가 빨리! 뜨게 하는 방법을 설명합니다.

- PHP 출력 제어 함수란

PHP4의 좋은 점 중 하나는 PHP의 모든 출력을 버퍼에 담을 수 있다는 점입니다. 즉 출력이 직접 그 내용을 전송하기 전까지 브라우저로 전송되지 않게 할 수 있습니다. 이 함수는 header()나 setcookie()함수와 같이 내용이 전송되기 전에 호출되어야 하는 함수와 같이 사용됩니다. 그러나 이 용법은 출력 제어함수의 이점의 일부밖에 되지 않습니다.

void ob_start(void);

이 함수는 PHP 실행기에게 스크립트의 모든 출력을 내부 버퍼에 저장하게 합니다. ob_start()함수가 호출된 이후에는 이떠한 출력도 브라우저로 전송되지 않습니다.

string ob_get_contents(void);

이 함수는 내부 버퍼의 내용을 문자열로 리턴해 줍니다. 이 함수를 사용해서 버퍼에 축적된 출력을 (버퍼 기능을 off한 다음에) 읽어올 수 있습니다.

int ob_get_length(void);

버퍼에 축적된 내용의 길이를 리턴합니다.

void ob_end_clean(void);

버퍼 내용을 지우고 버퍼 기능을 off합니다. 내용을 브라우저로 전송하려면 이 함수를 사용해서 버퍼링 기능을 off해야합니다.

void ob_implicit_flush ([int flag])

이 함수는 자동 플러쉬(implicit flush)를 하게 합니다. 기본 값은 off입니다. 이 값이 on이면 모든 print/echo 함수가 호출될 때마다 flush가 실행되어서 즉시 브라우저로 전송되는 효과를 가져옵니다.

- PHP 출력을 압축하기 위해 출력 제어 함수 사용하기

출력을 압축하려면 PHP4를 컴파일 할 때 Zlib 확장기능을 추가해야 합니다.

1단계 : 출력 버퍼링을 초기화해줍니다.

<?php

ob_start();
ob_implicit_flush(0);

?>

2 단계 : 출력하고자 하는 내용을 print 든 echo든 뭐든지 간에 그냥 출력합니다. (브라우저에는 출력되지 않고 버퍼에 내용이 쌓입니다)

<?php

print("Hey this is a compressed output!");

?>

3단계 : 이렇게 버퍼에 쌓인 페이지를 읽어 옵니다.

<?php

$contents = ob_get_contents();
ob_end_clean();

?>

4단계 : 브라우저가 압축된 데이터를 지원하는지 검사합니다. 지원 여부는 브라우저가 서버에 request할 때 ACCETP_ENCODING 헤더(http 헤더)를 포함하고 있다는 점을 이용합니다. 즉 $HTTP_ACCEPT_ENCODING 변수에 "gzip, deflate"라는 내용이 있는지 검사합니다.

<?php

if(ereg('gzip, deflate',$HTTP_ACCEPT_ENCODING)) {
// 여기서 gzip으로 압축해서 내용을 전송
} else {
echo $contents;
}

?>

그렇게 복잡하지 않습니다. 이제 gzip으로 압축한 정보를 어떻게 생성하는지 한번 살펴봅시다.

(php.net에서 인용한 소스)

<?php

// Tell the browser that they are going to get gzip data
// Of course, you already checked if they support gzip or x-gzip
// and if they support x-gzip, you'd change the header to say
// x-gzip instead, right?
header("Content-Encoding: gzip");

// Display the header of the gzip file
// Thanks ck@medienkombinat.de!
// Only display this once
echo "x1fx8bx08x00x00x00x00x00";

// Figure out the size and CRC of the original for later
$Size = strlen($contents);
$Crc = crc32($contents);

// Compress the data
$contents = gzcompress($contents, 9);

// We can't just output it here, since the CRC is messed up.
// If I try to "echo $contents" at this point, the compressed
// data is sent, but not completely. There are four bytes at
// the end that are a CRC. Three are sent. The last one is
// left in limbo. Also, if we "echo $contents", then the next
// byte we echo will not be sent to the client. I am not sure
// if this is a bug in 4.0.2 or not, but the best way to avoid
// this is to put the correct CRC at the end of the compressed
// data. (The one generated by gzcompress looks WAY wrong.)
// This will stop Opera from crashing, gunzip will work, and
// other browsers won't keep loading indefinately.
//
// Strip off the old CRC (it's there, but it won't be displayed
// all the way -- very odd)
$contents = substr($contents, 0, strlen($contents) - 4);

// Show only the compressed data
echo $contents;

// Output the CRC, then the size of the original
gzip_PrintFourChars($Crc);
gzip_PrintFourChars($Size);

// Done. You can append further data by gzcompressing
// another string and reworking the CRC and Size stuff for
// it too. Repeat until done.

function gzip_PrintFourChars($Val) {
for ($i = 0; $i < 4; $i ++) {
echo chr($Val % 256);
$Val = floor($Val / 256);
}
}

?>

If you want to test it as a working example, the whole script is:

<?php

// Start the output buffer
ob_start();
ob_implicit_flush(0);

// Output stuff here...
print("I'm compressed!n");

$contents = ob_get_contents();
ob_end_clean();

// Tell the browser that they are going to get gzip data
// Of course, you already checked if they support gzip or x-gzip
// and if they support x-gzip, you'd change the header to say
// x-gzip instead, right?
header("Content-Encoding: gzip");

// Display the header of the gzip file
// Thanks ck@medienkombinat.de!
// Only display this once
echo "x1fx8bx08x00x00x00x00x00";

// Figure out the size and CRC of the original for later
$Size = strlen($contents);
$Crc = crc32($contents);

// Compress the data
$contents = gzcompress($contents, 9);

// We can't just output it here, since the CRC is messed up.
// If I try to "echo $contents" at this point, the compressed
// data is sent, but not completely. There are four bytes at
// the end that are a CRC. Three are sent. The last one is
// left in limbo. Also, if we "echo $contents", then the next
// byte we echo will not be sent to the client. I am not sure
// if this is a bug in 4.0.2 or not, but the best way to avoid
// this is to put the correct CRC at the end of the compressed
// data. (The one generated by gzcompress looks WAY wrong.)
// This will stop Opera from crashing, gunzip will work, and
// other browsers won't keep loading indefinately.
//
// Strip off the old CRC (it's there, but it won't be displayed
// all the way -- very odd)
$contents = substr($contents, 0, strlen($contents) - 4);

// Show only the compressed data
echo $contents;

// Output the CRC, then the size of the original
gzip_PrintFourChars($Crc);
gzip_PrintFourChars($Size);

// Done. You can append further data by gzcompressing
// another string and reworking the CRC and Size stuff for
// it too. Repeat until done.


function gzip_PrintFourChars($Val) {
for ($i = 0; $i < 4; $i ++) {
echo chr($Val % 256);
$Val = floor($Val / 256);
}
}

?>

(주 : ^^; 주석이 넘 길군요.....ㅠ,.ㅠ 회사일이 넘 많아서 번역 생략. 용서 ok?)

- PHP 출력을 caching하기

PHP4가 나오기 전에는 PHP3로 데이터베이스와 파일시스템 등에 걸리는 부하를 줄이기 위해 caching 메커니즘을 개발하는데 관심이 많았습니다. PHP4에서는 출력 버퍼링이라는 기능 대문에 훨씬 간단하게 구현할 수 있게 되었습니다.
다음은 그 간단한 예제입니다.

<?php

// 요청한 URI에 대한 cache 파일명을 임의로 생성
$cached_file=md5($REQUEST_URI);

// cache파일이 존재하지 않거나
// cache파일을 새로 만들어야 할 때에만 if절 내부 실행
if((!file_exists("/cache/$cached_file"))||(!is_valid("/cache/$cached_file"))) {
// is_valid함수는 cache파일을 검사하기 위해 자신이 직접 작성합니다.
// 즉 파일이 오래 되었는지...등의 검사를 해서 cache파일이 새로 작성되어야
// 하는지를 알 수 있도록 합니다.

ob_start();
ob_implicit_flush(0);

// 실제 이 페이지의 출력 부분.......

$contents = ob_get_contents();
ob_end_clean();

// cache파일 생성
$fil=fopen($cached_file,"w+");
fwrite($fil,$contents,$strlen($contents));
fclose($fil);
}

// cache 파일 출력
readfile($cached_file);

?>

이 예제는 간단한 예제입니다. 출력 버퍼링을 사용하면 꽤 진보된 내용 생성 시스템을 구축할 수 있습니다. 출력 버퍼링을 사용하면 XML과 XSLT를사용해서 XML제어에도 많은 이득을 볼 수 있습니다.

- 역자 주
여기서 서술한 내용은 여러분들의 gzip과 mod_gzip 출력 제어 함수 등의 내용을 공부하시는데 도움이 되었으면 하고 번역했습니다. 실제로 이 과정은 단 한 줄로 끝낼 수도 있고 php.ini등으로 자동으로 되게끔 셋팅할 수도 있습니다.
다음 라인을 스크립트 가장 처음에 추가하면 출력되는 정보가 자동으로 압축됩니다.

ob_start("ob_gzhandler");

또는 다음 라인을 php.ini에 넣으면 자동으로.... ^^;

output_handler = ob_gzhandler

출력 버퍼링이 대한 좋은 글은 http://www.zend.com/zend/art/buffering.php를 참고합니다.