HTTP Range 헤더

쓰리래빗츠 드라이브를 개발하면서 얻은 소소한 지식 중 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을 참고합니다.

다음을 참고합니다.


  1. 동영상을 보다가 재생 시간을 10분이나 1시간 앞으로 바꾸는 것도 HTTP Range 헤더가 있어서 가능합니다. 동영상 파일에 재상 시간과 파일 바이트 위치 정보가 있는 것으로 짐작할 수 있습니다.
  2. 1을 빼는 이유는 데이터 위치가 배열처럼 0에서 시작하기 때문입니다.