HEAD /downloads/large-file.zip
→ Content-Length: 1073741824
// 1 GB—다운로드 전에 사용자에게 먼저 물어보기
리소스 최신 여부 확인:
HEAD /data/report.pdf
→ Last-Modified: Wed, 15 May 2024 10:00:00 GMT
→ ETag: "abc123"
// 캐시된 버전과 비교
콘텐츠 유형 파악:
HEAD /file-without-extension
→ Content-Type: application/pdf
// PDF 파일임을 확인
HEAD와 캐싱
HEAD 응답은 GET과 동일한 캐싱 규칙을 따릅니다. 브라우저와 프록시는 HEAD 응답을 캐시하고, 원본 서버에 다시 접속하지 않고도 반환할 수 있습니다.
덕분에 HEAD는 캐시 유효성 검사에 유용합니다:
HEAD /api/data HTTP/1.1
If-Modified-Since: Wed, 15 May 2024 10:00:00 GMT
→
HTTP/1.1 304 Not Modified
// 캐시가 여전히 유효함, 다운로드 불필요
구현 요구 사항
RFC 91101은 서버가 HEAD 요청에 대해 GET과 동일한 헤더를 응답으로 보내야 한다고 명시합니다. "must"가 아닌 "should"를 사용하는 이유는, 일부 헤더가 콘텐츠를 실제로 생성하는 과정에서만 결정될 수 있기 때문입니다—어차피 버릴 콘텐츠를 생성하는 것은 HEAD의 목적에 어긋납니다.
일반적인 기준: 존재 여부나 메타데이터만 확인할 때는 HEAD를 사용하세요. 리소스가 존재한다면 콘텐츠도 필요할 때는 GET을 사용하세요.
// 다운로드 전에 대용량 파일 존재 여부 확인const headResponse = awaitfetch(url, { method: 'HEAD' });
if (headResponse.ok) {
const size = headResponse.headers.get('Content-Length');
if (confirm(`${formatBytes(size)} 다운로드하시겠습니까?`)) {
const getResponse = awaitfetch(url);
// 파일 다운로드
}
}
보안 고려 사항
HEAD 요청으로도 정보가 노출될 수 있습니다. GET과 동일한 보호 조치를 적용하세요:
인증 필요: HEAD에도 GET과 동일한 인증 및 권한 검사를 적용해야 합니다:
HEAD /users/123/private-data
→ 401 Unauthorized (인증되지 않은 경우)
→ 403 Forbidden (인증은 됐지만 권한이 없는 경우)
정보 노출 위험: 헤더가 민감한 정보를 드러낼 수 있습니다:
HEAD /internal-document.pdf
→ Last-Modified: 문서 수정 시각이 노출됨
→ Content-Length: 문서 크기가 노출됨
→ X-Author: 작성자 이름이 노출될 수 있음
HEAD를 Range 헤더와 함께 사용하면 서버가 부분 콘텐츠를 지원하는지 확인할 수 있습니다:
HEAD /large-file.zip HTTP/1.1
Range: bytes=0-0
→
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Length: 1
Content-Range: bytes 0-0/1073741824
// 서버가 Range 요청을 지원하며, 전체 파일 크기는 1 GB
다운로드 관리자는 이를 통해 병렬 청크 다운로드가 가능한지 판단합니다.
API에서의 HEAD
RESTful API는 리소스 엔드포인트에 대해 HEAD를 지원해야 합니다:
HEAD /api/v1/users/12345
→ 200 OK (사용자 존재)
→ 404 Not Found (사용자 없음)
HEAD /api/v1/articles/latest
→ Last-Modified: Wed, 15 May 2024 10:00:00 GMT
→ ETag: "abc123"
일부 API는 상태 확인(헬스 체크)에 HEAD를 활용합니다:
HEAD /health
→ 200 OK (서비스 정상)
→ 503 Service Unavailable (서비스 다운)
const head = awaitfetch(downloadUrl, { method: 'HEAD' });
const size = parseInt(head.headers.get('Content-Length'));
if (size > MAX_SIZE) {
thrownewError('파일이 너무 큽니다');
}
리소스 가용성 폴링:
asyncfunctionwaitForResource(url) {
while (true) {
const response = awaitfetch(url, { method: 'HEAD' });
if (response.ok) {
returntrue;
}
if (response.status === 404) {
awaitsleep(1000);
continue;
}
thrownewError(`예상치 못한 상태 코드: ${response.status}`);
}
}
HTTP HEAD 메서드 자주 묻는 질문
GET을 지원하면서 HEAD를 지원하지 않는 서버가 있는 이유는 무엇인가요?
일부 웹 프레임워크는 GET 핸들러에서 HEAD 핸들러를 자동으로 만들어주지 않아 개발자가 별도로 구현해야 합니다. 대충 만들어진 구현은 HEAD 지원을 그냥 빠뜨립니다. 또한 HEAD를 고려하지 않고 허용 메서드를 명시적으로 제한해서 405 Method Not Allowed를 반환하는 경우도 있습니다.
HEAD 요청을 캐시할 수 있나요?
네. HEAD 응답은 GET과 동일한 캐싱 규칙을 따릅니다. 브라우저와 프록시는 HEAD 응답을 캐시하고, 이후 HEAD 요청에 대해 원본 서버에 접속하지 않고도 반환할 수 있습니다. Cache-Control, ETag, Last-Modified 같은 캐시 헤더는 GET과 동일하게 작동합니다.
HEAD가 GET보다 빠른가요?
네트워크 전송 측면에서는 그렇습니다—본문이 없으니 데이터가 적습니다. 하지만 서버 측에서는 구현 방식에 따라 다릅니다. 잘 구현된 HEAD 핸들러는 메타데이터만 조회합니다. 제대로 구현되지 않은 경우 전체 응답을 생성한 뒤 본문을 버리므로, 전송 시간만 절약되는 셈입니다.
API 엔드포인트가 존재하는지 확인하기 위해 호출 전에 HEAD를 사용해야 하나요?
일반적으로는 그럴 필요가 없습니다. 데이터가 필요하다면 그냥 GET을 호출하세요—요청 두 번보다 한 번이 낫습니다. HEAD가 의미 있는 경우는 메타데이터만 진짜로 필요할 때입니다: 다운로드 버튼을 표시하기 전에 파일이 있는지 확인하거나, 캐시를 갱신하기 전에 콘텐츠가 바뀌었는지 확인하거나, 페이지를 다운로드하지 않고 링크를 검증할 때가 그런 경우입니다.