PHP에서 500MB 이상 대용량 파일 업로드하기
PHP에서 대용량 파일을 업로드하는 방법은 크게 두 가지가 있습니다. 하나는 서버의 업로드 제한 설정 자체를 늘리는 방식이고, 다른 하나는 파일을 작은 조각으로 나누어 전송한 뒤 다시 하나로 합치는 청크(chunk) 업로드 방식입니다.
- php.ini 파일에서 upload_max_filesize 제한을 변경하는 방법
- 파일을 여러 개의 작은 조각으로 분할해 업로드하고, 업로드가 완료되면 조각들을 다시 조립하는 청크 업로드 방식
1. php.ini 파일 수정
php.ini 파일에서 아래 항목을 찾아 값을 수정하거나 추가합니다.
upload_max_filesize = 50M post_max_size = 50M max_input_time = 300 max_execution_time = 300
각 항목의 의미는 다음과 같습니다.
- upload_max_filesize : 업로드할 수 있는 파일의 최대 크기
- post_max_size : POST 요청으로 전송 가능한 데이터의 최대 크기(upload_max_filesize와 같거나 더 크게 설정해야 함)
- max_input_time : 입력 데이터를 파싱하는 데 허용되는 최대 시간(초)
- max_execution_time : 스크립트가 실행될 수 있는 최대 시간(초)
다만 이 방법은 서버 전체 설정을 변경하는 것이므로, 같은 서버에서 운영 중인 다른 프로젝트에도 영향을 줄 수 있다는 점을 감안해 신중하게 적용하는 것이 좋습니다.
2. .htaccess 파일 수정
php.ini 파일에 직접 접근하기 어려운 호스팅 환경이라면, 웹 루트 디렉터리의 .htaccess 파일에 아래 설정을 추가하는 방법도 있습니다.
php_value upload_max_filesize 50M php_value post_max_size 50M php_value max_input_time 300 php_value max_execution_time 300
3. 코드 내에서 ini_set()으로 설정 변경
업로드를 처리하는 PHP 스크립트 상단에서 ini_set() 함수를 호출하면, 해당 스크립트에 한해서만 업로드 관련 제한을 일시적으로 조정할 수 있습니다.
<?php
// 업로드 제한 변경
ini_set('upload_max_filesize', '50M');
ini_set('post_max_size', '50M');
ini_set('max_input_time', 300);
ini_set('max_execution_time', 300);
// 저장 위치(목적지 폴더) 지정
$source = $_FILES["file-upload"]["tmp_name"];
$destination = $_FILES["file-upload"]["name"];
// 업로드된 파일을 목적지로 이동
move_uploaded_file($source, $destination);
?>
4. 청크(Chunk) 단위 업로드
이 방식은 대용량 파일을 여러 개의 작은 조각으로 나눈 뒤 순차적으로 전송하고, 서버에서 이 조각들을 하나의 파일로 병합하는 방법입니다. 클라이언트 쪽에서는 'Plupload'와 같은 자바스크립트 라이브러리를 활용하면 비교적 간단하게 구현할 수 있습니다. 아래는 서버 측(PHP) 처리 코드 예제입니다.
<?php
// 응답 처리 함수
function verbose($ok=1,$info=""){
// 업로드 실패 시 400 에러 반환
if ($ok==0) { http_response_code(400); }
die(json_encode(["ok"=>$ok, "info"=>$info]));
}
// 유효하지 않은 업로드
if (empty($_FILES) || $_FILES['file']['error']) {
verbose(0, "Failed to move uploaded file.");
}
// 업로드 경로 지정
$filePath = __DIR__ . DIRECTORY_SEPARATOR . "uploads";
if (!file_exists($filePath)) {
if (!mkdir($filePath, 0777, true)) {
verbose(0, "Failed to create $filePath");
}
}
$fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : $_FILES["file"]["name"];
$filePath = $filePath . DIRECTORY_SEPARATOR . $fileName;
// 청크 처리
$chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;
$chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 0;
$out = @fopen("{$filePath}.part", $chunk == 0 ? "wb" : "ab");
if ($out) {
$in = @fopen($_FILES['file']['tmp_name'], "rb");
if ($in) {
while ($buff = fread($in, 4096)) { fwrite($out, $buff); }
} else {
verbose(0, "Failed to open input stream");
}
@fclose($in);
@fclose($out);
@unlink($_FILES['file']['tmp_name']);
} else {
verbose(0, "Failed to open output stream");
}
// 모든 청크가 업로드되었는지 확인 후 임시 파일을 최종 파일로 변경
if (!$chunks || $chunk == $chunks - 1) {
rename("{$filePath}.part", $filePath);
}
verbose(1, "Upload OK");
?>
마무리
위와 같이 청크 업로드 방식을 적용하면 500MB가 넘는 대용량 파일도 시간 초과나 메모리 부족 없이 성공적으로 업로드할 수 있습니다. 실제 운영 환경에서는 nginx의 client_max_body_size, Apache의 LimitRequestBody처럼 웹 서버 차원의 요청 크기 제한도 함께 확인하고 조정해야 하며, 업로드되는 파일명 검증과 저장 경로 보안 처리도 반드시 병행하는 것이 안전합니다.