Akış Yanıtları ve Aralık İstekleri
Büyük dosyaları StreamingResponse ile sunun ve devam ettirilebilir indirmeler için HTTP aralık isteklerini destekleyin.
Akış Yanıtları ve Aralık İstekleri, CoddyKit'te ücretsiz bir FastAPI Backend Development Bootcamp dersidir. Bu, 4 dersinin 2. dersidir. Aşağıdan dersin tamamını ücretsiz okuyabilir, sonra tarayıcıda yerleşik kod editörü ve 7/24 yapay zeka koçu ile uygulamalı olarak pratik yapabilirsin. Bu, FastAPI Backend Development Bootcamp öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. FastAPI Backend Development Bootcamp kursu toplamda 4 dersten oluşur.
Bu dersin bazı bölümleri henüz çevrilmemiş olup İngilizce olarak gösterilmektedir.
Why Stream Responses?
By default, returning a file from FastAPI means loading the entire payload into memory before sending it. For a 2 GB video that is a disaster: memory spikes, slow first byte, and crashes under concurrency.
Streaming solves this by sending the body in small chunks as they become available. The server holds only one chunk at a time, and the client starts receiving data almost immediately.
StreamingResponse— wraps a generator/iterator that yields bytes.FileResponse— a convenience for serving a file from disk efficiently.- Range requests — let clients fetch only part of a file (seeking, resuming).
This lesson builds all three, ending with resumable downloads.
A Generator That Yields Bytes
Streaming starts with an iterable of bytes. The cleanest source is a Python generator that reads a file in fixed-size chunks instead of all at once.
Here is the core idea, isolated from any framework. The generator yields 1 MB at a time, so peak memory stays tiny no matter how large the file is.
def file_chunks(path, chunk_size=1024 * 1024):
with open(path, "rb") as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
yield chunk
if __name__ == "__main__":
import os
with open("sample.bin", "wb") as f:
f.write(b"x" * (3 * 1024 * 1024 + 17))
total = 0
pieces = 0
for chunk in file_chunks("sample.bin"):
total += len(chunk)
pieces += 1
print("bytes:", total)
print("chunks:", pieces)
os.remove("sample.bin")StreamingResponse Basics
StreamingResponse takes any sync or async iterable of bytes (or strings) as its first argument. You set the media_type so the browser knows how to handle the body.
Notice we pass the generator object itself, not its result — FastAPI iterates it lazily while sending.
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
def file_chunks(path, chunk_size=1024 * 1024):
with open(path, "rb") as f:
while chunk := f.read(chunk_size):
yield chunk
@app.get("/download/report")
def download_report():
return StreamingResponse(
file_chunks("report.pdf"),
media_type="application/pdf",
)Setting Content-Disposition
To make the browser download a file (instead of trying to display it) and choose a filename, send a Content-Disposition header.
attachment— force a download dialog.inline— display in the browser if possible.filename="..."— the suggested name.
Pass custom headers via the headers argument of StreamingResponse.
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
def file_chunks(path, chunk_size=1024 * 1024):
with open(path, "rb") as f:
while chunk := f.read(chunk_size):
yield chunk
@app.get("/export/users.csv")
def export_users():
headers = {
"Content-Disposition": 'attachment; filename="users.csv"'
}
return StreamingResponse(
file_chunks("users.csv"),
media_type="text/csv",
headers=headers,
)Streaming Generated Data On the Fly
Streaming is not limited to files on disk. You can generate the body incrementally — for example, exporting a huge CSV row by row from a database cursor without ever building the full string in memory.
The generator below yields one CSV line at a time. Each yield is flushed to the client as soon as it is produced.
import csv
import io
def csv_stream(rows):
buffer = io.StringIO()
writer = csv.writer(buffer)
writer.writerow(["id", "name", "score"])
yield buffer.getvalue()
for row in rows:
buffer.seek(0)
buffer.truncate(0)
writer.writerow(row)
yield buffer.getvalue()
if __name__ == "__main__":
data = [(i, f"user{i}", i * 10) for i in range(5)]
output = "".join(csv_stream(data))
print(output, end="")FileResponse: The Easy Path
When you just need to serve an existing file from disk, FileResponse is simpler than wiring a generator. Starlette streams it efficiently and sets sensible headers for you.
- Guesses
Content-Typefrom the extension. - Sets
Content-Lengthautomatically. - Adds ETag and Last-Modified for caching.
- Crucially, it already supports range requests out of the box.
For static, on-disk files, prefer FileResponse over a manual StreamingResponse.
from fastapi import FastAPI
from fastapi.responses import FileResponse
app = FastAPI()
@app.get("/media/{name}")
def serve_media(name: str):
return FileResponse(
path=f"media/{name}",
filename=name,
media_type="video/mp4",
)What Is an HTTP Range Request?
A range request lets a client ask for only part of a resource. The browser sends:
Range: bytes=1048576-2097151
The server replies with status 206 Partial Content and these headers:
Content-Range: bytes 1048576-2097151/5242880— the slice and the total size.Content-Length— the length of just this slice.Accept-Ranges: bytes— advertises that ranges are supported.
This powers video seeking (jump to minute 5 without downloading minutes 0–4) and resumable downloads (continue from where a dropped connection stopped).
Parsing the Range Header
To support ranges manually, you must parse the Range header. The format is bytes=start-end where either side may be omitted:
bytes=500-999— bytes 500 through 999.bytes=500-— from 500 to the end.bytes=-500— the last 500 bytes (suffix range).
This standalone parser returns inclusive (start, end) offsets for a given file size.
def parse_range(header, file_size):
units, _, rng = header.partition("=")
if units.strip() != "bytes":
raise ValueError("only byte ranges supported")
start_s, _, end_s = rng.strip().partition("-")
if start_s == "":
# suffix range: last N bytes
length = int(end_s)
start = max(file_size - length, 0)
end = file_size - 1
else:
start = int(start_s)
end = int(end_s) if end_s else file_size - 1
end = min(end, file_size - 1)
if start > end:
raise ValueError("unsatisfiable range")
return start, end
if __name__ == "__main__":
size = 5000
print(parse_range("bytes=0-499", size))
print(parse_range("bytes=4500-", size))
print(parse_range("bytes=-100", size))Reading Just the Requested Slice
Once you have (start, end), you must stream only that window. Use file.seek(start) to jump to the offset, then read in chunks while counting down the remaining bytes so you never overshoot end.
This generator yields exactly end - start + 1 bytes.
def ranged_chunks(path, start, end, chunk_size=1024 * 1024):
remaining = end - start + 1
with open(path, "rb") as f:
f.seek(start)
while remaining > 0:
chunk = f.read(min(chunk_size, remaining))
if not chunk:
break
remaining -= len(chunk)
yield chunk
if __name__ == "__main__":
import os
with open("blob.bin", "wb") as f:
f.write(bytes(range(256)) * 40) # 10240 bytes
got = b"".join(ranged_chunks("blob.bin", 100, 199, chunk_size=32))
print("length:", len(got))
print("first byte:", got[0])
os.remove("blob.bin")A Full Range-Aware Endpoint
Now we combine everything into one FastAPI endpoint that handles both full and partial downloads:
- No
Rangeheader → stream the whole file with 200 OK. - Valid
Range→ stream the slice with 206 Partial Content plusContent-Range. - Unsatisfiable range → return 416 with a
Content-Range: bytes */sizeheader.
Always advertise Accept-Ranges: bytes so clients know seeking is allowed.
import os
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse, Response
app = FastAPI()
VIDEO = "media/movie.mp4"
def ranged_chunks(path, start, end, chunk_size=1024 * 1024):
remaining = end - start + 1
with open(path, "rb") as f:
f.seek(start)
while remaining > 0 and (chunk := f.read(min(chunk_size, remaining))):
remaining -= len(chunk)
yield chunk
@app.get("/video")
def stream_video(request: Request):
size = os.path.getsize(VIDEO)
range_header = request.headers.get("range")
if range_header is None:
return StreamingResponse(
ranged_chunks(VIDEO, 0, size - 1),
media_type="video/mp4",
headers={"Accept-Ranges": "bytes",
"Content-Length": str(size)},
)
start, end = parse_range(range_header, size)
headers = {
"Content-Range": f"bytes {start}-{end}/{size}",
"Accept-Ranges": "bytes",
"Content-Length": str(end - start + 1),
}
return StreamingResponse(
ranged_chunks(VIDEO, start, end),
status_code=206,
media_type="video/mp4",
headers=headers,
)Async Streaming and Cleanup
For non-blocking I/O under load, use an async generator. Reading the disk inside a thread pool keeps the event loop free; libraries like aiofiles do this for you.
Two important rules:
- Streaming runs after your function returns, so resources opened inside the generator must be released in a
finallyblock. - If the client disconnects mid-stream, FastAPI raises inside the generator — that
finallystill runs, so handles never leak.
import aiofiles
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
async def async_chunks(path, chunk_size=1024 * 1024):
f = await aiofiles.open(path, "rb")
try:
while chunk := await f.read(chunk_size):
yield chunk
finally:
await f.close()
@app.get("/async-download")
async def async_download():
return StreamingResponse(
async_chunks("big.bin"),
media_type="application/octet-stream",
headers={"Accept-Ranges": "bytes"},
)Quick Check
A client sends Range: bytes=2000-2999 for a 10000-byte file. Which status code and headers should your endpoint return for a correct partial download?
Recap
You can now serve large media efficiently and support resumable, seekable downloads.
- StreamingResponse wraps a byte iterable so peak memory equals one chunk, not the whole file.
- FileResponse is the easy path for on-disk files and already supports ranges plus caching headers.
- A range request sends
Range: bytes=start-end; reply with 206,Content-Range, a slice-sizedContent-Length, andAccept-Ranges: bytes. - Parse the header (including suffix
bytes=-N),seek(start), and read while tracking remaining bytes so you never overshoot. - Use async generators with a
finallyblock to close handles even when clients disconnect mid-stream.
Sıkça Sorulan Sorular
“Akış Yanıtları ve Aralık İstekleri” dersi ücretsiz mi?
Evet — “Akış Yanıtları ve Aralık İstekleri” dersin tüm metni burada web'de ücretsiz olarak okunabilir. Etkileşimli olarak pratik yapmak (yerleşik kod editörü ve 7/24 yapay zeka koçu) ve FastAPI Backend Development Bootcamp kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. FastAPI Backend Development Bootcamp kursu toplamda 4 dersten oluşur.
“Akış Yanıtları ve Aralık İstekleri” dersinde ne öğreneceğim?
Büyük dosyaları StreamingResponse ile sunun ve devam ettirilebilir indirmeler için HTTP aralık isteklerini destekleyin. FastAPI Backend Development Bootcamp ile uygulamalı kodu tarayıcıda doğrudan çalıştırarak pratik yaparsın ve 7/24 yapay zeka koçu dersi çalışırken sorularını yanıtlar.
FastAPI Backend Development Bootcamp öğrenmeye başlamak için deneyim gerekli mi?
Önceden deneyim gerekmez. CoddyKit'te FastAPI Backend Development Bootcamp, başlangıçtan ileri seviyeye kadar yapılandırıldığı için buradan başlayabilir veya başından başlayıp kendi hızında ilerleme yapabilirsin. Bu, 4 dersinin 2. dersidir.
“Akış Yanıtları ve Aralık İstekleri” dersi ne kadar sürer?
Çoğu CoddyKit dersi yaklaşık 5–10 dakika sürer. Her biri kısa ve etkileşimli olduğu için sabit ilerleme yaparsın ve web ile uygulama arasında tam olarak bıraktığın yerden devam edebilirsin.
Bu FastAPI Backend Development Bootcamp dersinde kod yazıp çalıştırabilir miyim?
Evet. Her FastAPI Backend Development Bootcamp dersi yerleşik bir kod editörü içerir, bu sayede tarayıcıda gerçek kod yazıp çalıştırabilir ve anlık yapay zeka geri bildirimi alırsın — yerel kurulum gerekli değildir.
Bu kursun tüm dersleri
- Multipart Yüklemeleri ve İçerik Doğrulama
- Akış Yanıtları ve Aralık İstekleri
- Depolamayı S3 Uyumlu Paketlere Aktarma
- Asenkron Görüntü ve Belge Dönüşümü