HTTP Cheatsheet

Range Requests and Uploads

Use this HTTP reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Advertising and requesting ranges

A server that supports partial transfers says so, and clients then ask for byte windows instead of whole files.

HEAD /video.mp4 HTTP/1.1

HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 146515
ETag: "9c1f2a"
GET /video.mp4 HTTP/1.1
Range: bytes=0-1023

HTTP/1.1 206 Partial Content
Content-Range: bytes 0-1023/146515
Content-Length: 1024
ETag: "9c1f2a"
Accept-Ranges: bytes
Range formMeaning
bytes=0-1023the first 1024 bytes
bytes=1024-from byte 1024 to the end
bytes=-500the last 500 bytes
bytes=0-0just the first byte, a cheap support probe
bytes=0-99,200-299multiple ranges, answered as multipart/byteranges

Offsets are zero-based and inclusive on both ends, so bytes=0-1023 is 1024 bytes. Getting that off by one is the classic resumable-download bug.

Status codes and headers for ranges

CodeWhen
200server ignored the Range header, sent the whole body
206partial content, Content-Range present
416the range is outside the resource, send Content-Range: bytes */146515
412If-Match or If-Range validator failed
HeaderDirectionValue
Accept-Rangesresponsebytes, or none to decline
Rangerequestbytes=0-1023
Content-Rangeresponsebytes 0-1023/146515, or bytes 0-1023/*
If-Rangerequestan ETag or date, makes the range conditional

If-Range is the correct way to resume: if the validator still matches the server sends 206 and you continue, and if it does not it sends the whole resource as 200 so you restart cleanly. Without it a changed file yields a corrupt stitch of two versions.

Only strong validators work for ranges. A weak ETag: W/"abc" cannot be used with If-Range, because weak equivalence does not guarantee identical bytes.

Resumable downloads

# probe support
curl -sI https://example.com/big.iso | grep -iE 'accept-ranges|content-length'

# one explicit window
curl -r 0-1023 -o head.bin https://example.com/big.iso
curl -r -500 -o tail.bin https://example.com/big.iso

# resume an interrupted download from wherever the local file stopped
curl -C - -O https://example.com/big.iso
wget -c https://example.com/big.iso

# parallel chunks, then concatenate
curl -r 0-9999999      -o part0 https://example.com/big.iso &
curl -r 10000000-19999999 -o part1 https://example.com/big.iso &
wait && cat part0 part1 > big.iso

Ranges are also what makes media seeking work. A browser video player issues Range requests as the user scrubs, so a server without Accept-Ranges forces a full download before playback and breaks seeking entirely.

Upload bodies: form-urlencoded vs multipart

application/x-www-form-urlencodedmultipart/form-data
Shapea=1&b=hello%20worldboundary-delimited parts
Binary safeno, everything is percent-encodedyes
Size overhead~3× for binary dataa few bytes per part
Per-field metadatanonefilename and Content-Type per part
Use forsmall text fieldsany file upload
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----HUb0undary
Content-Length: 431

------HUb0undary
Content-Disposition: form-data; name="title"

Q3 report
------HUb0undary
Content-Disposition: form-data; name="file"; filename="q3.pdf"
Content-Type: application/pdf

%PDF-1.7 ...binary...
------HUb0undary--

The boundary must not appear anywhere in the data, the final boundary carries a trailing --, and every line break in the framing is CRLF.

curl -F 'title=Q3 report' -F 'file=@q3.pdf' https://example.com/upload
curl -F 'file=@q3.pdf;type=application/pdf;filename=report.pdf' \
  https://example.com/upload
# raw body, no multipart wrapper, the simplest API upload
curl -X PUT --data-binary @q3.pdf \
  -H 'content-type: application/pdf' https://example.com/files/q3.pdf

Filenames from a client are attacker-controlled. Never use one to build a filesystem path, and never trust the part's Content-Type for validation, sniff the bytes instead.

Expect: 100-continue and large bodies

For a large upload, ask before sending the payload so a rejection costs nothing.

PUT /files/big.iso HTTP/1.1
Content-Length: 4294967296
Expect: 100-continue

HTTP/1.1 100 Continue      ← now send the body

... or ...

HTTP/1.1 413 Content Too Large   ← nothing was uploaded
CodeMeaning for an upload
100proceed with the body
413body over the server limit, reject before transfer
415wrong or missing Content-Type
417the server cannot honor the Expect header

curl sends Expect: 100-continue automatically for bodies over 1 KB and waits up to a second. Some servers and proxies never answer it, which shows as a consistent one-second stall on every upload. Suppress it with -H 'Expect:'.

Server body limits to know when a 413 appears: nginx client_max_body_size (default 1 MB), Apache LimitRequestBody, and framework defaults such as Express express.json({ limit }).

Chunked and streaming uploads

When the size is unknown up front, stream it.

POST /ingest HTTP/1.1
Transfer-Encoding: chunked
Content-Type: application/x-ndjson

2a
{"event":"click","ts":1789000000}
2b
{"event":"scroll","ts":1789000001}
0
# stream stdin, no Content-Length
tail -f app.log | curl -X POST --data-binary @- \
  -H 'transfer-encoding: chunked' https://example.com/ingest

# server-sent events, a streaming download
curl -N -H 'accept: text/event-stream' https://example.com/events

Transfer-Encoding: chunked is HTTP/1.1 only. HTTP/2 and /3 have length-prefixed frames, so streaming needs no special header there, and sending Transfer-Encoding over HTTP/2 is a protocol error.

Never send both Content-Length and Transfer-Encoding: chunked. That disagreement between a proxy and an origin is exactly what request smuggling exploits, and compliant servers reject such a message outright.

Resumable and multipart upload protocols

Plain HTTP has no standard resumable upload, so real systems build one. Three patterns dominate.

PatternHow it worksUsed by
Ranged PUTclient PUTs windows, HEAD reports the received length, resume from therecustom APIs
Session based (tus)POST creates an upload URL, PATCH appends at an Upload-Offset, HEAD reports progresstus.io, Vimeo
Multipart object storageinitiate, upload numbered parts in parallel each returning an ETag, then complete with the part listS3, GCS, Azure
# tus-style flow
curl -X POST https://example.com/files -i \
  -H 'Upload-Length: 4294967296' -H 'Tus-Resumable: 1.0.0'
# → 201 Created, Location: /files/abc123

curl -X HEAD https://example.com/files/abc123 -i \
  -H 'Tus-Resumable: 1.0.0'
# → Upload-Offset: 1048576

curl -X PATCH https://example.com/files/abc123 \
  -H 'Tus-Resumable: 1.0.0' -H 'Upload-Offset: 1048576' \
  -H 'content-type: application/offset+octet-stream' \
  --data-binary @chunk.bin

For very large files the practical answer is usually a presigned direct upload to object storage, which keeps the bytes off your application servers entirely. Whatever the mechanism, make the completion step idempotent, because a client that times out after the server committed will retry.