Resume a download with Range

이미 받은 만큼 이후 바이트만 요청해 끊긴 다운로드를 이어받는 레시피입니다. `Range` 헤더와 206 Partial Content를 활용합니다.

개요

이미 받은 만큼 이후 바이트만 요청해 끊긴 다운로드를 이어받는 레시피입니다. `Range` 헤더와 206 Partial Content를 활용합니다.

코드로 보기

# resume a partially downloaded file automatically:
curl -C - -O https://example.com/big.iso

# or request an explicit byte range:
curl -r 1048576- -o part.bin https://example.com/big.iso
#   -> 206 Partial Content
// fetch bytes starting at an offset (e.g. after 1 MiB already saved)
const res = await fetch('https://example.com/big.iso', {
  headers: { Range: 'bytes=1048576-' }
});
console.log(res.status); // 206 Partial Content
console.log(res.headers.get('Content-Range')); // bytes 1048576-.../<total>
import os, requests

path, url = 'big.iso', 'https://example.com/big.iso'
have = os.path.getsize(path) if os.path.exists(path) else 0
headers = {'Range': f'bytes={have}-'}

with requests.get(url, headers=headers, stream=True) as r:
    r.raise_for_status()  # 206 if resumed, 200 if server ignores Range
    with open(path, 'ab') as f:  # append mode
        for chunk in r.iter_content(8192):
            f.write(chunk)

설명

curl의 `-C -`는 로컬 파일 크기를 보고 자동으로 이어받기 오프셋을 계산합니다. 직접 지정하려면 `-r 시작-끝`(예: `-r 1048576-`)이나 `Range: bytes=<start>-`를 씁니다. 서버가 부분 요청을 지원하면 206과 함께 `Content-Range: bytes start-end/total`을 돌려줍니다.

서버가 Range를 지원하는지는 응답의 `Accept-Ranges: bytes`로 알 수 있습니다. 지원하지 않으면 Range를 무시하고 200과 전체 파일을 보내니, 클라이언트는 200이 오면 처음부터 다시 저장(append가 아니라 덮어쓰기)하도록 분기해야 합니다.

이어받는 동안 원본이 바뀌면 조각들이 서로 맞지 않아 파일이 깨집니다. 이를 막으려면 `If-Range: "<etag>"`를 함께 보내세요. 리소스가 그대로면 206으로 이어주고, 바뀌었으면 200으로 전체를 새로 보내 무결성을 지킵니다. 범위가 파일 크기를 벗어나면 416 Range Not Satisfiable이 납니다.

관련 헤더

관련 상태코드