PHP에서 파일 다운로드를 강제하는 방법
웹 개발을 하다 보면 텍스트 파일이나 CSV, JS 같은 파일을 브라우저에서 바로 열어 보여주는 대신, 사용자가 클릭하는 순간 즉시 다운로드되도록 처리해야 하는 경우가 자주 있습니다. PHP에서는 header() 함수를 활용하면 이러한 동작을 간단하게 구현할 수 있습니다.
방법 1: header() 함수로 다운로드 강제하기
아래 코드는 JavaScript 파일을 브라우저에서 실행하지 않고 다운로드하도록 강제하는 기본적인 예제입니다.
<?php
header('Content-Type: text/javascript');
header('Content-Disposition: attachment; filename="file.js"');
readfile('file.js'); // 다운로드할 실제 파일 경로
?>
여기서 핵심 역할을 하는 것은 Content-Disposition 헤더입니다. 값을 attachment로 지정하면 브라우저는 해당 파일을 화면에 렌더링하지 않고 첨부 파일로 취급하여 다운로드 창을 띄웁니다. filename 속성에는 사용자에게 표시될 저장 파일명을 지정합니다.
주의: header() 함수는 반드시 HTML 출력, 공백 문자, echo 문 등 어떠한 출력도 발생하기 전에 호출해야 합니다. 출력이 이미 시작된 후 헤더를 전송하면 "headers already sent" 오류가 발생하며, 다른 연산의 불필요한 출력 결과가 다운로드 파일에 섞여 들어가 파일이 손상될 수 있습니다.
방법 2: .htaccess로 서버 전체에 적용하기
특정 확장자를 가진 모든 파일을 항상 다운로드되도록 만들고 싶다면, Apache 웹 서버의 .htaccess 파일을 활용하는 방법이 유용합니다. 아래 한 줄을 추가하면 서버에 있는 모든 CSV 파일이 브라우저에서 열리지 않고 다운로드됩니다.
AddType application/octet-stream .csv
반면, PHP 스크립트에서 CSV 파일을 동적으로 생성하여 내려보내야 하는 경우에는 다음과 같이 헤더를 구성할 수 있습니다.
<?php
header('Content-Type: application/csv');
header('Content-Disposition: attachment; filename="data.csv"');
header('Pragma: no-cache');
readfile('경로/data.csv');
exit;
?>
Pragma: no-cache 헤더를 함께 지정하면 브라우저나 프록시가 응답을 캐싱하지 않도록 하여, 항상 최신 파일을 받도록 할 수 있습니다. 파일 전송이 끝난 후에는 exit;를 호출해 이후의 불필요한 출력이 파일에 포함되지 않도록 하는 것이 좋습니다.
두 방법의 차이점 비교
| 구분 | header() 함수 방식 | .htaccess 방식 |
|---|---|---|
| 적용 범위 | 개별 스크립트 단위 | 서버(디렉터리) 전체 |
| 유연성 | 동적 파일 생성·파일명 변경 가능 | 확장자 기반 일괄 적용 |
| 적합한 상황 | 조건에 따라 파일을 생성해 제공할 때 | 정적 파일을 항상 다운로드시키고 싶을 때 |
상황에 맞는 방법을 선택하면, 사용자에게 더 안정적이고 깔끔한 파일 다운로드 경험을 제공할 수 있습니다.