Kem Intensif Pembangunan Bahagian Belakang FastAPI · Pelajaran

Tindak Balas Penstriman dan Permintaan Julat

Hidangkan fail besar dengan StreamingResponse dan sokong permintaan julat HTTP untuk muat turun yang boleh disambung semula.

Pelajaran 2 daripada 413 langkah

Tindak Balas Penstriman dan Permintaan Julat ialah pelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI percuma di CoddyKit. Ini ialah pelajaran 2 daripada 4. Sebanyak 3 pelajaran dalam laluan pembelajaran ini boleh dibaca sepenuhnya secara percuma — selepas itu, CoddyKit PRO membuka akses kepada semua pelajaran, serta latihan praktikal dengan penyunting kod terbina dalam dan tutor kecerdasan buatan yang tersedia 24/7. Pelajaran ini merupakan sebahagian daripada laluan pembelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI, dan kemajuan anda disegerakkan merentas web serta aplikasi CoddyKit. Kursus Kem Intensif Pembangunan Bahagian Belakang FastAPI merangkumi sejumlah 4 pelajaran.

Mengapa Respons Perstriman?

Secara lalai, mengembalikan fail daripada FastAPI bermaksud memuatkan keseluruhan kandungan ke dalam memori sebelum menghantarnya. Untuk video 2 GB, ini membawa masalah besar: penggunaan memori melonjak, bait pertama lambat diterima dan pelayan ranap apabila berlaku keserentakan.

Penstriman menyelesaikan masalah ini dengan menghantar badan dalam bahagian kecil apabila bahagian tersebut tersedia. Pelayan hanya menyimpan satu bahagian pada satu-satu masa, dan klien mula menerima data hampir serta-merta.

  • StreamingResponse — membungkus penjana/lelaran yang menghasilkan bait.
  • FileResponse — kemudahan untuk menyajikan fail daripada cakera dengan cekap.
  • Permintaan julat — membolehkan klien mengambil hanya sebahagian fail (melompat, menyambung semula).

Pelajaran ini membina ketiga-tiganya dan berakhir dengan muat turun yang boleh disambung semula.

Penjana yang Menghasilkan Bait

Penstriman bermula dengan boleh lelar bait. Sumber yang paling kemas ialah penjana Python yang membaca fail dalam bahagian bersaiz tetap dan bukannya sekaligus.

Berikut ialah idea teras yang diasingkan daripada sebarang rangka kerja. Penjana menghasilkan 1 MB pada satu-satu masa, jadi penggunaan memori puncak kekal sangat kecil tanpa mengira saiz fail.

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")

Asas StreamingResponse

StreamingResponse menerima sebarang boleh lelar bait (atau rentetan) segerak atau tak segerak sebagai argumen pertamanya. Anda menetapkan media_type supaya pelayar tahu cara mengendalikan badan tersebut.

Perhatikan bahawa kita menghantar objek penjana itu sendiri, bukannya hasilnya — FastAPI melakukan lelaran secara malas semasa menghantar.

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",
    )

Menetapkan Content-Disposition

Untuk membuat pelayar memuat turun fail (bukannya cuba memaparkannya) dan memilih nama fail, hantar pengepala Content-Disposition.

  • attachment — paksa dialog muat turun.
  • inline — paparkan dalam pelayar jika boleh.
  • filename="..." — nama yang dicadangkan.

Hantar pengepala tersuai melalui argumen headers bagi 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,
    )

Menstrim Data Dijana Secara Langsung

Penstriman tidak terhad kepada fail pada cakera. Anda boleh menjana badan secara berperingkat — contohnya, mengeksport baris CSV yang besar satu demi satu daripada kursor pangkalan data tanpa membina keseluruhan rentetan dalam memori.

Penjana di bawah menghasilkan satu baris CSV pada satu-satu masa. Setiap yield dihantar kepada klien sebaik sahaja ia dihasilkan.

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: Cara Mudah

Apabila anda hanya perlu menyediakan file sedia ada daripada cakera, FileResponse lebih mudah daripada menyediakan penjana. Starlette menstrimkannya dengan cekap dan menetapkan pengepala yang sesuai untuk anda.

  • Mengagak Content-Type berdasarkan sambungan.
  • Menetapkan Content-Length secara automatik.
  • Menambah ETag dan Last-Modified untuk penc cache.
  • Yang penting, ia sudah menyokong permintaan julat secara terbina dalam.

Untuk file statik yang disimpan pada cakera, utamakan FileResponse berbanding StreamingResponse manual.

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",
    )

Apakah Permintaan Julat HTTP?

Permintaan julat membolehkan client meminta hanya sebahagian daripada sumber. Pelayar menghantar:

Range: bytes=1048576-2097151

Pelayan membalas dengan status 206 Partial Content dan pengepala berikut:

  • Content-Range: bytes 1048576-2097151/5242880 — bahagian dan jumlah saiz.
  • Content-Length — panjang bahagian ini sahaja.
  • Accept-Ranges: bytes — memaklumkan bahawa julat disokong.

Ini membolehkan lompatan dalam video (terus ke minit 5 tanpa memuat turun minit 0–4) dan muat turun yang boleh disambung semula (sambung dari tempat sambungan terputus).

Menghuraikan Pengepala Range

Untuk menyokong julat secara manual, anda mesti menghuraikan pengepala Range. Formatnya ialah bytes=start-end, dengan mana-mana bahagian boleh ditinggalkan:

  • bytes=500-999 — bait 500 hingga 999.
  • bytes=500- — dari 500 hingga akhir.
  • bytes=-500 — 500 bait terakhir (julat akhiran).

Penghurai kendiri ini mengembalikan ofset (start, end) inklusif untuk saiz file yang diberikan.

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))

Membaca Hanya Bahagian yang Diminta

Setelah anda memperoleh (start, end), anda mesti menstrim hanya tetingkap tersebut. Gunakan file.seek(start) untuk melompat ke ofset, kemudian baca dalam ketulan sambil mengira bait yang berbaki supaya anda tidak melepasi end.

Penjana ini menghasilkan tepat end - start + 1 bait.

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")

Titik Akhir Penuh yang Menyokong Julat

Sekarang kita gabungkan semuanya menjadi satu titik akhir FastAPI yang mengendalikan muat turun penuh dan separa:

  • Tiada pengepala Range → strim seluruh file dengan 200 OK.
  • Range yang sah → strim bahagian tersebut dengan 206 Partial Content serta Content-Range.
  • Julat yang tidak boleh dipenuhi → kembalikan 416 dengan pengepala Content-Range: bytes */size.

Sentiasa umumkan Accept-Ranges: bytes supaya client tahu bahawa lompatan dibenarkan.

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,
    )

Penstriman Tak Segerak dan Pembersihan

Untuk I/O tanpa sekatan ketika beban tinggi, gunakan penjana tak segerak. Membaca cakera dalam kumpulan thread memastikan gelung peristiwa bebas; pustaka seperti aiofiles melakukan perkara ini untuk anda.

Dua peraturan penting:

  • Penstriman berjalan selepas fungsi anda kembali, jadi sumber yang dibuka dalam penjana mesti dilepaskan dalam blok finally.
  • Jika client terputus di tengah-tengah penstriman, FastAPI menimbulkan pengecualian dalam penjana — finally itu tetap berjalan, jadi pemegang sumber tidak pernah bocor.
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"},
    )

Semakan Pantas

Client menghantar Range: bytes=2000-2999 untuk file bersaiz 10000 bait. Apakah kod status dan pengepala yang patut dikembalikan oleh titik akhir anda untuk muat turun separa yang betul?

Rumusan

Kini anda boleh menyediakan media besar dengan cekap serta menyokong muat turun yang boleh disambung semula dan dilangkau.

  • StreamingResponse membungkus lelaran bait supaya memori puncak bersamaan satu ketulan, bukan keseluruhan file.
  • FileResponse ialah cara mudah untuk file pada cakera dan sudah menyokong julat serta pengepala pencache.
  • Permintaan julat menghantar Range: bytes=start-end; balas dengan 206, Content-Range, Content-Length bersaiz bahagian, dan Accept-Ranges: bytes.
  • Huraikan pengepala (termasuk bytes=-N untuk akhiran), gunakan seek(start), dan baca sambil menjejak bait yang berbaki supaya anda tidak melepasi had.
  • Gunakan penjana tak segerak dengan blok finally untuk menutup pemegang sumber walaupun client terputus di tengah-tengah penstriman.
Percuma untuk bermula

Pelajari Kem Intensif Pembangunan Bahagian Belakang FastAPI dengan tutor kecerdasan buatan — percuma

Tulis dan jalankan kod sebenar dalam pelayar anda, dapatkan bantuan segera daripada tutor kecerdasan buatan yang tersedia 24/7, dan sambung semula dari tempat anda berhenti di web atau dalam aplikasi.

Kursus
21
Pelajaran
84

Soalan Lazim

Adakah pelajaran “Tindak Balas Penstriman dan Permintaan Julat” percuma?

Ya — sebanyak 3 pelajaran dalam laluan pembelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI, termasuk “Tindak Balas Penstriman dan Permintaan Julat”, boleh dibaca sepenuhnya secara percuma di web ini. Selepas itu, CoddyKit PRO membuka akses kepada semua pelajaran, serta latihan interaktif dengan penyunting kod terbina dalam dan tutor kecerdasan buatan yang tersedia 24/7. Kursus Kem Intensif Pembangunan Bahagian Belakang FastAPI merangkumi sejumlah 4 pelajaran.

Apakah yang akan saya pelajari dalam “Tindak Balas Penstriman dan Permintaan Julat”?

Hidangkan fail besar dengan StreamingResponse dan sokong permintaan julat HTTP untuk muat turun yang boleh disambung semula. Anda berlatih Kem Intensif Pembangunan Bahagian Belakang FastAPI menggunakan kod praktikal yang dijalankan terus dalam pelayar, manakala tutor kecerdasan buatan 24/7 menjawab soalan anda semasa anda mengikuti pelajaran.

Adakah saya memerlukan pengalaman untuk memulakan Kem Intensif Pembangunan Bahagian Belakang FastAPI?

Tiada pengalaman terdahulu diperlukan. Pembelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI di CoddyKit disusun untuk pelajar daripada peringkat pemula hingga lanjutan, jadi anda boleh bermula di sini atau dari awal dan belajar mengikut kadar anda sendiri. Ini ialah pelajaran 2 daripada 4.

Berapa lamakah pelajaran “Tindak Balas Penstriman dan Permintaan Julat” diambil?

Kebanyakan pelajaran CoddyKit mengambil masa kira-kira 5–10 minit. Setiap pelajaran ringkas dan interaktif, jadi anda boleh membuat kemajuan secara berterusan dan menyambung tepat dari tempat anda berhenti di web atau aplikasi.

Bolehkah saya menulis dan menjalankan kod dalam pelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI ini?

Ya. Setiap pelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI menyertakan penyunting kod terbina dalam, jadi anda boleh menulis dan menjalankan kod sebenar terus dalam pelayar serta menerima maklum balas kecerdasan buatan serta-merta — tanpa memerlukan persediaan setempat.

Semua pelajaran dalam kursus ini

  1. Muat Naik Berbilang Bahagian dan Pengesahan Kandungan
  2. Tindak Balas Penstriman dan Permintaan Julat
  3. Mengalihkan Storan ke Baldi Serasi S3
  4. Transformasi Imej dan Dokumen Tak Segerak
← Kembali ke Kem Intensif Pembangunan Bahagian Belakang FastAPI