Bootcamp i backendutvikling med FastAPI · leksjon

Avlasting av lagring til S3-kompatible bøtter

Strøm opplastinger direkte til S3/MinIO med forhåndssignerte URL-er, slik at API-et forblir tilstandsløst og skalerbart.

Leksjon 3 av 413 trinn

Avlasting av lagring til S3-kompatible bøtter er en gratis leksjon i Bootcamp i backendutvikling med FastAPI på CoddyKit. Dette er leksjon 3 av 4. Du kan lese valgfritt 3 leksjoner fra denne læringsstien gratis i sin helhet – deretter låser CoddyKit PRO opp alle leksjoner, samt praktisk øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Bootcamp i backendutvikling med FastAPI, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Bootcamp i backendutvikling med FastAPI inneholder totalt 4 leksjoner.

Hvorfor flytte lagringen til S3?

Det skalerer ikke å lagre opplastede filer på API-serverens lokale disk. Hver replika trenger sin egen kopi, diskene blir fulle, og containere er flyktige — start podden på nytt, så forsvinner filene.

Løsningen er å flytte medier til et eksternt objektlager og holde API-et tilstandsløst. Alle S3-kompatible tjenester fungerer:

  • Amazon S3 — originalen, fullt administrert.
  • MinIO — selvvertet, kompatibelt med S3-API-et og godt egnet for utvikling og lokal drift.
  • Cloudflare R2, Backblaze B2, DigitalOcean Spaces — billigere datautgang, samme API.

Fordi de alle bruker S3-protokollen, kan den samme boto3-klientkoden brukes mot alle ved å endre URL-en til endepunktet.

Konfigurere en boto3-S3-klient

Biblioteket boto3 er standard-SDK-en for AWS i Python. Hvis De vil bruke en annen leverandør enn AWS, for eksempel MinIO eller R2, sender De inn en eksplisitt endpoint_url.

Oppbevar legitimasjon og endepunkt i innstillinger, aldri hardkodet. Bruk config=Config(signature_version="s3v4"), slik at forhåndssignerte URL-er genereres med den moderne SigV4-algoritmen som alle leverandører godtar.

import boto3
from botocore.config import Config

def build_s3_client():
    return boto3.client(
        "s3",
        endpoint_url="https://s3.eu-central-1.amazonaws.com",
        aws_access_key_id="AKIA...",
        aws_secret_access_key="secret...",
        region_name="eu-central-1",
        config=Config(signature_version="s3v4"),
    )

client = build_s3_client()
print(type(client).__name__)

Den naive metoden (og hvorfor den er problematisk)

Det åpenbare første forsøket er å lese hele opplastingen inn i minnet og deretter sende den til S3:

  • data = await file.read() laster hele filen inn i RAM.
  • En videoopplasting på 2 GB blir til 2 GB prosessminne — multipliser dette med samtidige forespørsler, så krasjer arbeiderne på grunn av tomt minne.

Dette fungerer for små avatarer, men er risikabelt for medier. Vi vil strømme byte gjennom API-et (eller hoppe over API-et helt ved å bruke forhåndssignerte URL-er). De neste delene bygger opp begge teknikkene.

from fastapi import FastAPI, UploadFile

app = FastAPI()

@app.post("/upload-naive")
async def upload_naive(file: UploadFile):
    data = await file.read()  # whole file in RAM - avoid for large media!
    return {"size": len(data)}

Strømme opplastinger med upload_fileobj

FastAPI sin UploadFile pakker inn en SpooledTemporaryFile: små opplastinger blir i minnet, mens store opplastinger automatisk flyttes til disken. Attributtet .file er et standard fillignende objekt.

upload_fileobj i boto3 leser denne strømmen i blokker og utfører en multipart-opplasting i bakgrunnen — dermed holder minnebruken seg begrenset uavhengig av filstørrelsen.

from fastapi import FastAPI, UploadFile

app = FastAPI()
BUCKET = "user-media"

@app.post("/upload")
async def upload(file: UploadFile):
    client.upload_fileobj(
        Fileobj=file.file,          # streams in chunks, no full read
        Bucket=BUCKET,
        Key=f"uploads/{file.filename}",
        ExtraArgs={"ContentType": file.content_type},
    )
    return {"key": f"uploads/{file.filename}"}

Ikke blokker hendelsesløkken

boto3 er synkront. Hvis De kaller upload_fileobj direkte inne i et async def-endepunkt, blokkeres hendelsesløkken mens bytene sendes til S3, og alle andre forespørsler på arbeideren stopper opp.

Flytt det blokkerende kallet til en trådpool med run_in_threadpool (Starlette) eller asyncio.to_thread. Da forblir hendelsesløkken ledig til å betjene andre tilkoblinger.

from fastapi import FastAPI, UploadFile
from fastapi.concurrency import run_in_threadpool

app = FastAPI()
BUCKET = "user-media"

@app.post("/upload")
async def upload(file: UploadFile):
    key = f"uploads/{file.filename}"
    await run_in_threadpool(
        client.upload_fileobj, file.file, BUCKET, key,
        {"ContentType": file.content_type},
    )
    return {"key": key}

Forhåndssignerte URL-er: La klientene snakke direkte med S3

Strømming gjennom API-et bruker fortsatt båndbredden og CPU-en Deres to ganger (klient→API, API→S3). Det mest skalerbare mønsteret fjerner API-et helt fra dataflyten ved å bruke en forhåndssignert URL.

En forhåndssignert URL er en midlertidig, signert lenke som gir tillatelse til én bestemt operasjon (PUT eller GET) på ett objekt, og som utløper etter N sekunder. Klienten laster opp direkte til S3; API-et Deres signerer bare forespørselen.

  • API-et forblir tilstandsløst og lite — det håndterer aldri bytene.
  • Legitimasjonen forlater aldri serveren; signaturen inneholder tillatelsen.

Generere en forhåndssignert PUT-URL

Bruk generate_presigned_url med klientmetoden put_object for å opprette en opplastingslenke. Endepunktet returnerer URL-en samt den endelige objektnøkkelen; nettleseren sender deretter en vanlig HTTP-PUT til URL-en med filinnholdet.

Angi en kort ExpiresIn (for eksempel 300–900 sekunder) — akkurat lenge nok til å starte opplastingen.

import uuid
from fastapi import FastAPI

app = FastAPI()
BUCKET = "user-media"

@app.post("/uploads/presign")
def presign_put(filename: str, content_type: str):
    key = f"uploads/{uuid.uuid4()}-{filename}"
    url = client.generate_presigned_url(
        ClientMethod="put_object",
        Params={"Bucket": BUCKET, "Key": key, "ContentType": content_type},
        ExpiresIn=600,
    )
    return {"upload_url": url, "key": key}

Opplastingsflyten på klientsiden

Med en forhåndssignert PUT-URL laster nettleseren opp med én enkelt forespørsel — ikke et multipart-skjema, bare det rå innholdet. Flyten er:

  • 1. Klienten ber API-et Deres om en forhåndssignert URL (sender filnavn + innholdstype).
  • 2. API-et returnerer upload_url og den endelige key.
  • 3. Klienten utfører PUT upload_url med filbytene og den tilsvarende Content-Type-headeren.
  • 4. Klienten varsler API-et Deres om key, slik at De kan lagre den i databasen.

Content-Type på PUT-forespørselen må samsvare med den De signerte, ellers returnerer S3 403.

// Browser-side (illustrative)
const { upload_url, key } = await api.presign(file.name, file.type);
await fetch(upload_url, {
  method: "PUT",
  headers: { "Content-Type": file.type },
  body: file,
});
await api.confirm(key);

Levere private filer med forhåndssignerte GET-URL-er

Gjør buckets private som standard. Hvis en bruker skal laste ned eller vise en fil, genererer De en kortvarig forhåndssignert get_object-URL ved behov i stedet for å gjøre objektet offentlig.

Da styres tilgangen av autentiseringen i API-et Deres: kontroller at brukeren eier filen, og signer deretter en URL som er gyldig i noen minutter. Bygg den inn i en <img>-src-attributt eller returner den som en omdirigering.

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()
BUCKET = "user-media"

@app.get("/files/{key:path}")
def download(key: str):
    url = client.generate_presigned_url(
        ClientMethod="get_object",
        Params={"Bucket": BUCKET, "Key": key},
        ExpiresIn=300,
    )
    return RedirectResponse(url)

Begrense opplastinger med forhåndssignert POST

En forhåndssignert PUT-URL kan ikke begrense filstørrelsen — en ondsinnet klient kan laste opp en fil på 50 GB. Når De trenger serverhåndhevede begrensninger, bør De i stedet bruke generate_presigned_post.

Den returnerer en URL og skjemafeltet fields, og lar Dem legge til betingelser som content-length-range og en nøyaktig innholdstype. S3 avviser opplastingen på serversiden hvis bytene bryter med policyen.

from fastapi import FastAPI

app = FastAPI()
BUCKET = "user-media"

@app.post("/uploads/presign-post")
def presign_post(key: str, content_type: str):
    return client.generate_presigned_post(
        Bucket=BUCKET,
        Key=key,
        Fields={"Content-Type": content_type},
        Conditions=[
            {"Content-Type": content_type},
            ["content-length-range", 1, 10 * 1024 * 1024],  # max 10 MB
        ],
        ExpiresIn=600,
    )

En gjenbrukbar nøkkelbygger

Objektnøkler bør være kollisjonssikre, organiserte, og De bør aldri stole på klientens rå filnavn (som kan inneholde ../ eller uvanlige tegn). En liten hjelpefunksjon samler denne logikken på ett sted og er ren Python — enkelt å enhetsteste.

En god nøkkel inneholder et logisk prefiks (eier, kategori), en UUID for unikhet og en renset filendelse.

import re
import uuid

def build_key(user_id: int, filename: str) -> str:
    ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else "bin"
    ext = re.sub(r"[^a-z0-9]", "", ext)[:8] or "bin"
    return f"users/{user_id}/{uuid.uuid4().hex}.{ext}"

print(build_key(42, "My Vacation.JPG"))
print(build_key(7, "../../etc/passwd"))
print(build_key(1, "noext"))

Hurtigsjekk: Velge det skalerbare mønsteret

De bygger et endepunkt som lar brukere laste opp store videoer (opptil 2 GB). De vil at FastAPI-tjenesten skal forbli tilstandsløs og unngå å sende filbytene gjennom API-serveren i det hele tatt. Hvilken metode passer best?

Oppsummering: Tilstandsløse medier i stor skala

De har nå et komplett verktøysett for å flytte lagringen til S3-kompatible bucketer:

  • Én klient, mange leverandører — boto3 med en endpoint_url kan brukes mot S3, MinIO, R2 og Spaces.
  • Bufre aldri hele filer — hvis byte må gå gjennom API-et, bruk upload_fileobj og flytt det til en trådpool med run_in_threadpool, slik at hendelsesløkken forblir ledig.
  • Foretrekk forhåndssignerte URL-er — klientene utfører PUT/GET direkte mot S3; API-et signerer bare og forblir tilstandsløst.
  • Håndhev begrensninger med generate_presigned_post og en content-length-range-betingelse.
  • Hold bucketer private og lever filer via kortvarige forhåndssignerte GET-lenker som beskyttes av autentiseringen Deres.
  • Rens nøkler — bruk UUID-baserte nøkler med prefiks, og stol aldri på rå filnavn.

Resultatet er et API som håndterer opplastinger på 2 GB eller 2 KB med samme begrensede ressursbruk.

Gratis å komme i gang

Lær deg Bootcamp i backendutvikling med FastAPI med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
21
Leksjoner
84

Ofte stilte spørsmål

Er leksjonen «Avlasting av lagring til S3-kompatible bøtter» gratis?

Ja – du kan lese valgfritt 3 av leksjonene i læringsstien Bootcamp i backendutvikling med FastAPI, inkludert «Avlasting av lagring til S3-kompatible bøtter», gratis i sin helhet her på nettet. Deretter låser CoddyKit PRO opp alle leksjoner, samt interaktiv øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Kurset i Bootcamp i backendutvikling med FastAPI inneholder totalt 4 leksjoner.

Hva lærer jeg i «Avlasting av lagring til S3-kompatible bøtter»?

Strøm opplastinger direkte til S3/MinIO med forhåndssignerte URL-er, slik at API-et forblir tilstandsløst og skalerbart. Du øver på Bootcamp i backendutvikling med FastAPI med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med Bootcamp i backendutvikling med FastAPI?

Ingen tidligere erfaring er nødvendig. Bootcamp i backendutvikling med FastAPI på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 3 av 4.

Hvor lang tid tar leksjonen «Avlasting av lagring til S3-kompatible bøtter»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne Bootcamp i backendutvikling med FastAPI-leksjonen?

Ja. Alle Bootcamp i backendutvikling med FastAPI-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Multipart-opplastinger og innholdsvalidering
  2. Strømmende svar og range-forespørsler
  3. Avlasting av lagring til S3-kompatible bøtter
  4. Asynkron bilde- og dokumenttransformasjon
← Tilbake til Bootcamp i backendutvikling med FastAPI