쓰리래빗츠 드라이브를 개발하면서 얻은 소소한 지식 중 HTTP Range 헤더에 대해 정리했습니다.
처음에는 동영상 스트리밍에 대한 지식이 없어서 요청받은 파일 전체를 내려보냈습니다. 파일 크기가 작을 때는 상관없지만 크기가 큰 동영상을 이렇게 서비스할 수는 없습니다. 그럴 때 쓰는 것이 HTTP Range 헤더입니다. HTTP Range 헤더로 파일 전체가 아니라 조금씩 나눠서 보낼 수 있습니다.1
웹 브라우저가 보내는 HTTP Range 헤더 요청은 다음과 같습니다.
Range: bytes=100-200
바이트가 단위입니다. 앞 숫자 100은 시작 위치를 200은 끝 위치를 의미합니다. 이 요청은 100바이트에서 200바이트까지의 데이터를 보내달라는 요청입니다. 시작 위치와 끝 위치 데이터를 포함하기 때문에 내려보내는 데이터 크기는 101바이트입니다.
HTTP Range 헤더 요청에 끝 위치가 없을 수 있습니다. 이 때는 임의로 끝 위치를 정합니다. 끝 위치에 따라 내려보내는 데이터 크기가 달라집니다. 끝 위치를 크게 설정하면 동영상을 보는 사용자가 기다리는 버퍼링 시간이 길어질 수 있습니다.
Range: bytes=100-
응답할 때는 HTTP Content-Range 헤더를 내려보냅니다.
Content-Range: bytes=100-200/1024
100과 200은 앞에서 설명한 것과 같고 1024는 전체 크기를 의미합니다. 끝 위치가 파일 크기에서 1을 뺀 것2보다 클 수 없습니다. 만약 파일 크기가 180이라면 다음과 같이 응답해야 합니다.
Content-Range: bytes=100-179/180
다음은 간단한 예제 코드입니다.
String range = request.getHeader("Range");
int i = range.indexOf("=");
int j = range.indexOf("-");
long start = Long.parseLong(range.substring(i + 1, j));
long end = 0;
if (j < range.length() - 1) {
end = Long.parseLong(range.substring(j + 1));
}
File file = getFile();
if (end == 0) {
end = file.length() - 1; 1
}
if (end > file.length() - 1) {
end = file.length() - 1; 2
}
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); 3
response.setContentType("video/mp4");
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + file.length());
response.setContentLength((int) (end - start + 1)); 4
RandomAccessFile rf = new RandomAccessFile(file, "r");
rf.seek(start);
byte[] buffer = new byte[1024];
int num = 0;
ServletOutputStream out = response.getOutputStream();
while (start < end && (num = rf.read(buffer)) != -1) {
out.write(buffer, 0, num);
out.flush();
start += 1024;
}1끝 위치가 없을 때는 파일의 크기를 끝으로 설정합니다.
2끝 위치가 파일 크기에서 1을 뺀 것보다 클 수 없습니다.
3응답 코드는 206 Partial Content입니다.
4시작과 끝 위치를 포함하기 때문에 내려보내는 데이터 크기는 end - start + 1입니다.
HTTP Range 헤더로 게시판 페이지 나눔을 구현하기도 합니다. 자세한 사항은 HTTP/1.1의 Range 요청과 이를 활용한 Pagination을 참고합니다.
다음을 참고합니다.
- 동영상을 보다가 재생 시간을 10분이나 1시간 앞으로 바꾸는 것도
HTTP Range헤더가 있어서 가능합니다. 동영상 파일에 재상 시간과 파일 바이트 위치 정보가 있는 것으로 짐작할 수 있습니다. - 1을 빼는 이유는 데이터 위치가 배열처럼 0에서 시작하기 때문입니다.