- Home
- HTTP Headers
- Range Header
Header
Range Header
Learn how the Range header requests partial content from servers to enable resumable downloads, video streaming, and efficient large file transfers.
What is Range?
TL;DR: Requests specific byte ranges of a resource instead of the entire file. Enables resumable downloads, video streaming, and efficient large file handling.
The Range header requests only part of a resource instead of the entire thing. It’s like asking for “pages 10-20 of a book” instead of the whole book. This enables resumable downloads, video streaming, and efficient large file handling.
This header is essential for media streaming, download managers, and mobile apps with limited bandwidth.
How Range Works
Client requests partial content:
GET /video.mp4 HTTP/1.1
Host: cdn.example.com
Range: bytes=1000-2000
```text
**Server responds with partial content:**
```http
HTTP/1.1 206 Partial Content
Content-Range: bytes 1000-2000/5000000
Content-Length: 1001
Content-Type: video/mp4
[Binary data from bytes 1000-2000]
Range Syntax
Byte Ranges
Range: bytes=0-1023 # First 1024 bytes
Range: bytes=1024-2047 # Next 1024 bytes
Range: bytes=1024- # From byte 1024 to end
Range: bytes=-1024 # Last 1024 bytes
```text
### Multiple Ranges
```http
Range: bytes=0-499, 1000-1499, 2000-2499
Real-World Examples
Video Streaming
GET /movie.mp4 HTTP/1.1
Host: streaming.example.com
Range: bytes=0-1048575
Accept: video/mp4
```text
Request first 1MB of video for initial playback.
### Resumable Download
```http
GET /large-file.zip HTTP/1.1
Host: downloads.example.com
Range: bytes=50331648-
Resume download from 48MB mark.
PDF Page Loading
GET /document.pdf HTTP/1.1
Host: docs.example.com
Range: bytes=102400-204800
```text
Load specific pages of a PDF document.
### Image Thumbnail Generation
```http
GET /high-res-image.jpg HTTP/1.1
Host: images.example.com
Range: bytes=0-8192
Get image header for metadata without full download.
Server Response Patterns
Partial Content (206)
HTTP/1.1 206 Partial Content
Content-Range: bytes 1000-2000/5000000
Content-Length: 1001
Accept-Ranges: bytes
Content-Type: video/mp4
[Partial content data]
```text
### Range Not Satisfiable (416)
```http
HTTP/1.1 416 Range Not Satisfiable
Content-Range: bytes */5000000
Content-Type: text/plain
Requested range not satisfiable
Full Content (200)
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 5000000
Content-Type: video/mp4
[Full content - server ignores Range header]
```javascript
## Client Implementation
### Resumable Download
```javascript
async function resumableDownload(url, filename) {
let startByte = 0
// Check if partial file exists
try {
const stats = await fs.stat(filename)
startByte = stats.size
} catch {
// File doesn't exist, start from beginning
}
const response = await fetch(url, {
headers: {
Range: `bytes=${startByte}-`
}
})
if (response.status === 206) {
// Partial content - resume download
const stream = fs.createWriteStream(filename, { flags: 'a' })
response.body.pipe(stream)
} else if (response.status === 200) {
// Full content - start over
const stream = fs.createWriteStream(filename)
response.body.pipe(stream)
}
}
Video Streaming
class VideoStreamer {
constructor(videoUrl) {
this.videoUrl = videoUrl
this.chunkSize = 1024 * 1024 // 1MB chunks
}
async getChunk(startByte) {
const endByte = startByte + this.chunkSize - 1
const response = await fetch(this.videoUrl, {
headers: {
Range: `bytes=${startByte}-${endByte}`
}
})
if (response.status === 206) {
return response.arrayBuffer()
}
throw new Error('Range not supported')
}
}
```javascript
## Server Implementation
### Express.js Range Support
```javascript
const fs = require('fs')
const path = require('path')
app.get('/files/:filename', (req, res) => {
const filename = req.params.filename
const filePath = path.join(__dirname, 'files', filename)
fs.stat(filePath, (err, stats) => {
if (err) return res.status(404).send('File not found')
const fileSize = stats.size
const range = req.headers.range
if (range) {
// Parse range header
const parts = range.replace(/bytes=/, '').split('-')
const start = parseInt(parts[0], 10)
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1
if (start >= fileSize || end >= fileSize) {
res
.status(416)
.set({
'Content-Range': `bytes */${fileSize}`
})
.send('Range Not Satisfiable')
return
}
const chunkSize = end - start + 1
const stream = fs.createReadStream(filePath, { start, end })
res.status(206).set({
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunkSize,
'Content-Type': 'application/octet-stream'
})
stream.pipe(res)
} else {
// Send full file
res.set({
'Accept-Ranges': 'bytes',
'Content-Length': fileSize,
'Content-Type': 'application/octet-stream'
})
fs.createReadStream(filePath).pipe(res)
}
})
})
Multiple Range Support
function parseRanges(rangeHeader, fileSize) {
const ranges = []
const rangeSpecs = rangeHeader.replace(/bytes=/, '').split(',')
for (const spec of rangeSpecs) {
const [startStr, endStr] = spec.trim().split('-')
let start, end
if (startStr === '') {
// Suffix range: -500 (last 500 bytes)
start = Math.max(0, fileSize - parseInt(endStr))
end = fileSize - 1
} else if (endStr === '') {
// Prefix range: 500- (from byte 500 to end)
start = parseInt(startStr)
end = fileSize - 1
} else {
// Full range: 500-999
start = parseInt(startStr)
end = parseInt(endStr)
}
if (start <= end && start < fileSize) {
ranges.push({ start, end: Math.min(end, fileSize - 1) })
}
}
return ranges
}
```javascript
## Use Cases
### Media Streaming
```javascript
// Video player requesting chunks
const player = {
async loadVideo(url) {
// Load first chunk for immediate playback
const firstChunk = await this.requestRange(url, 0, 1048576)
this.initializePlayer(firstChunk)
// Preload next chunks
this.preloadChunks(url, 1048576)
},
async requestRange(url, start, end) {
const response = await fetch(url, {
headers: { Range: `bytes=${start}-${end}` }
})
return response.arrayBuffer()
}
}
Download Manager
class DownloadManager {
async downloadWithResume(url, filename, onProgress) {
let downloaded = 0
// Check existing file
try {
const stats = await fs.stat(filename)
downloaded = stats.size
} catch {}
while (true) {
try {
const response = await fetch(url, {
headers: { Range: `bytes=${downloaded}-` }
})
if (response.status === 416) {
// Download complete
break
}
const reader = response.body.getReader()
const writer = fs.createWriteStream(filename, { flags: 'a' })
while (true) {
const { done, value } = await reader.read()
if (done) break
writer.write(value)
downloaded += value.length
onProgress(downloaded)
}
writer.close()
break
} catch (error) {
// Retry after delay
await new Promise((resolve) => setTimeout(resolve, 1000))
}
}
}
}
```javascript
## Best Practices
### Check Accept-Ranges
```javascript
// First, check if server supports ranges
const headResponse = await fetch(url, { method: 'HEAD' })
const acceptRanges = headResponse.headers.get('Accept-Ranges')
if (acceptRanges === 'bytes') {
// Server supports byte ranges
const rangeResponse = await fetch(url, {
headers: { Range: 'bytes=0-1023' }
})
}
Handle Range Errors
async function safeRangeRequest(url, start, end) {
const response = await fetch(url, {
headers: { Range: `bytes=${start}-${end}` }
})
if (response.status === 416) {
throw new Error('Range not satisfiable')
} else if (response.status === 200) {
// Server doesn't support ranges, got full content
console.warn('Server does not support range requests')
return response
} else if (response.status === 206) {
// Partial content as expected
return response
}
throw new Error(`Unexpected status: ${response.status}`)
}
```javascript
### Optimize Chunk Sizes
```javascript
const getOptimalChunkSize = (fileSize, connectionSpeed) => {
// Smaller chunks for slow connections
if (connectionSpeed < 1000000) return 64 * 1024 // 64KB
// Medium chunks for average connections
if (connectionSpeed < 10000000) return 256 * 1024 // 256KB
// Large chunks for fast connections
return 1024 * 1024 // 1MB
}
Testing Range Requests
Using curl
# Request first 1024 bytes
curl -H "Range: bytes=0-1023" https://example.com/file.zip
# Request last 1024 bytes
curl -H "Range: bytes=-1024" https://example.com/file.zip
# Request from byte 1000 to end
curl -H "Range: bytes=1000-" https://example.com/file.zip
# Multiple ranges
curl -H "Range: bytes=0-499,1000-1499" https://example.com/file.zip
```javascript
### Browser Testing
```javascript
// Test range support
fetch('/large-file.zip', {
method: 'HEAD'
}).then((response) => {
const acceptRanges = response.headers.get('Accept-Ranges')
console.log('Range support:', acceptRanges === 'bytes')
})
Related Headers
- Content-Range - Server’s partial content info
- Accept-Ranges - Server’s range support
- Content-Length - Partial content size
- If-Range - Conditional range requests
Resumable Downloads and Video Streaming
Range requests are the foundation of resumable downloads. When a download is interrupted, the client can resume from where it stopped by sending Range: bytes=<bytes-already-downloaded>-. The server responds with 206 Partial Content and the remaining bytes. This requires the server to support range requests (indicated by Accept-Ranges: bytes in the response) and the file to not have changed between the initial and resumed requests.
For video streaming, browsers use range requests to implement seeking. When a user jumps to a specific position in a video, the browser sends a range request for the bytes corresponding to that timestamp. The server does not need to know about video timestamps — it just serves the requested byte range, and the video player handles the decoding. This is why video files served over HTTP can be seeked without downloading the entire file first.
The If-Range header is important for resumable downloads. It allows the client to say “give me bytes 1000 onwards, but only if the file has not changed since I last checked.” If the file has changed (different ETag or newer Last-Modified), the server returns the full file with 200 instead of a partial response with 206. Without If-Range, a resumed download might silently combine bytes from two different versions of the file, producing a corrupt result.
Frequently Asked Questions
What is the Range header?
The Range header requests specific byte ranges of a resource. Servers respond with 206 Partial Content containing only the requested bytes, enabling resume and streaming.
How do I request a byte range?
Use Range: bytes=0-999 for first 1000 bytes, bytes=1000- for everything after byte 1000, or bytes=-500 for last 500 bytes. Multiple ranges are comma-separated.
How do I resume a download?
Send Range: bytes=X- where X is the number of bytes already downloaded. Combine with If-Range to handle file changes. The server returns remaining bytes with 206.
What if the server does not support ranges?
Check Accept-Ranges header in responses. If absent or "none", the server does not support ranges and will return the full resource with 200 OK.