Avlasta lagring till S3-kompatibla buckets
Strömma uppladdningar direkt till S3/MinIO med försignerade URL:er för att hålla API:t tillståndslöst och skalbart.
Avlasta lagring till S3-kompatibla buckets är en gratis lektion i Bootcamp i backendutveckling med FastAPI på CoddyKit. Detta är lektion 3 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för Bootcamp i backendutveckling med FastAPI, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Bootcamp i backendutveckling med FastAPI innehåller totalt 4 lektioner.
Varför flytta lagringen till S3?
Att lagra uppladdade filer på API-serverns lokala disk skalar inte. Varje replik skulle behöva sin egen kopia, diskarna blir fulla och containrar är efemära — starta om podden så försvinner filerna.
Lösningen är att skicka medier till en extern objektlagring och hålla API:t tillståndslöst. Alla S3-kompatibla tjänster fungerar:
- Amazon S3 — originalet, fullständigt hanterat.
- MinIO — självhostat, kompatibelt med S3 API, utmärkt för utveckling och lokala installationer.
- Cloudflare R2, Backblaze B2, DigitalOcean Spaces — billigare utgående trafik, samma API.
Eftersom alla använder S3-protokollet kan samma boto3-klientkod användas mot vilken som helst av dem genom att bara ändra endpoint-URL:en.
Konfigurera en boto3 S3-klient
Biblioteket boto3 är det vanliga AWS SDK:t för Python. Om du vill använda en annan leverantör än AWS, till exempel MinIO eller R2, anger du uttryckligen endpoint_url.
Förvara autentiseringsuppgifter och endpointen i inställningar, aldrig hårdkodade. Använd config=Config(signature_version="s3v4") så att presigned URLs genereras med den moderna SigV4-algoritmen som alla leverantörer accepterar.
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__)Det naiva tillvägagångssättet (och varför det skadar)
Det uppenbara första försöket är att läsa in hela uppladdningen i minnet och sedan skicka den till S3:
data = await file.read()läser in hela filen i RAM.- En videouppladdning på 2 GB blir 2 GB processminne — multiplicera det med samtidiga förfrågningar så kraschar dina workers på grund av slut på minne.
Det fungerar för små avatarer men är riskabelt för medier. Vi vill strömma byte genom API:t (eller hoppa över API:t helt med presigned URLs). I nästa avsnitt bygger vi upp båda teknikerna.
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ömmande uppladdningar med upload_fileobj
FastAPI:s UploadFile omsluter en SpooledTemporaryFile: små uppladdningar stannar i minnet, medan stora automatiskt flyttas till disken. Dess .file-attribut är ett vanligt filliknande objekt.
b oto3:s upload_fileobj läser den strömmen i delar och utför en multipart-uppladdning i bakgrunden — därför förblir minnesanvändningen begränsad oavsett filstorlek.
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}"}Blockera inte händelseloopen
boto3 är synkront. Om du anropar upload_fileobj direkt inuti en endpoint med async def blockeras händelseloopen medan byte skickas till S3, vilket stoppar alla andra förfrågningar på den workern.
Flytta det blockerande anropet till en thread pool med run_in_threadpool (Starlette) eller asyncio.to_thread. Då förblir händelseloopen fri att hantera andra anslutningar.
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}Presigned URLs: låt klienterna prata direkt med S3
Att strömma genom API:t använder fortfarande din bandbredd och CPU två gånger (klient→API, API→S3). Det mest skalbara mönstret tar bort API:t helt från dataströmmen genom att använda en presigned URL.
En presigned URL är en tillfällig, signerad länk som ger behörighet till en specifik åtgärd (PUT eller GET) på ett specifikt objekt och upphör att gälla efter N sekunder. Klienten laddar upp direkt till S3; ditt API signerar bara begäran.
- API:t förblir tillståndslöst och litet — det hanterar aldrig bytena.
- Autentiseringsuppgifter lämnar aldrig servern; signaturen kodar behörigheten.
Generera en presigned PUT-URL
Använd generate_presigned_url med klientmetoden put_object för att skapa en uppladdningslänk. Endpointen returnerar URL:en samt den slutliga objektnyckeln; webbläsaren skickar sedan en vanlig HTTP-PUT till URL:en med filens innehåll.
Ange ett kort ExpiresIn (t.ex. 300–900 sekunder) — precis tillräckligt länge för att starta uppladdningen.
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}Uppladdningsflödet på klientsidan
Med en presigned PUT-URL laddar webbläsaren upp med en enda begäran — inget multipart-formulär, bara det råa innehållet. Flödet är:
- 1. Klienten ber ditt API om en presigned URL (skickar filnamn + innehållstyp).
- 2. API:t returnerar
upload_urloch den slutligakey. - 3. Klienten gör
PUT upload_urlmed filens byte och den matchandeContent-Type-headern. - 4. Klienten meddelar ditt API om
keyså att du kan spara den i databasen.
Content-Type vid PUT-begäran måste matcha den du signerade, annars returnerar 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);Leverera privata filer med presigned GET-URL:er
Gör buckets privata som standard. Om du vill låta en användare ladda ner eller visa en fil genererar du vid behov en kortlivad presigned get_object-URL i stället för att göra objektet offentligt.
Då styrs åtkomsten av autentiseringen i ditt API: kontrollera att användaren äger filen och signera sedan en URL som gäller i några minuter. Bädda in den i ett <img>-src-attribut eller returnera 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)Begränsa uppladdningar med presigned POST
En presigned PUT-URL kan inte begränsa filstorleken — en illvillig klient skulle kunna ladda upp en fil på 50 GB. När du behöver begränsningar som servern upprätthåller använder du i stället generate_presigned_post.
Den returnerar en URL samt formulärets fields och låter dig ange villkor som content-length-range och en exakt innehållstyp. S3 avvisar uppladdningen på serversidan om bytena bryter mot policyn.
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 återanvändbar nyckelbyggare
Objektnycklar bör vara kollisionssäkra, strukturerade och aldrig lita på klientens råa filnamn (som kan innehålla ../ eller ovanliga tecken). En liten hjälpfunktion samlar denna logik på ett ställe och är ren Python — enkel att enhetstesta.
En bra nyckel innehåller ett logiskt prefix (ägare, kategori), ett UUID för unikhet och en sanerad filändelse.
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"))Snabbkontroll: välj det skalbara mönstret
Du bygger en endpoint som låter användare ladda upp stora videor (upp till 2 GB). Du vill att FastAPI-tjänsten ska förbli tillståndslös och undvika att dirigera filens byte genom API-servern över huvud taget. Vilket tillvägagångssätt passar bäst?
Sammanfattning: tillståndslösa medier i stor skala
Nu har du en komplett verktygslåda för att flytta lagring till S3-kompatibla buckets:
- En klient, många leverantörer —
boto3med enendpoint_urlriktar sig mot S3, MinIO, R2 och Spaces. - Buffra aldrig hela filer — om byte måste passera genom API:t använder du
upload_fileobjoch flyttar det till en thread pool medrun_in_threadpoolså att händelseloopen förblir fri. - Föredra presigned URLs — klienterna gör PUT/GET direkt mot S3; API:t signerar bara och förblir tillståndslöst.
- Upprätthåll begränsningar med
generate_presigned_postoch ettcontent-length-range-villkor. - Håll buckets privata och leverera filer via kortlivade presigned GET-länkar som skyddas av din autentisering.
- Sanera nycklar — UUID-baserade, försedda med prefix och aldrig baserade på råa filnamn.
Resultatet är ett API som hanterar uppladdningar på 2 GB eller 2 KB med samma begränsade resursförbrukning.
Lär dig Bootcamp i backendutveckling med FastAPI med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 21
- Lektioner
- 84
Vanliga frågor
Är lektionen ”Avlasta lagring till S3-kompatibla buckets” gratis?
Ja – du kan läsa vilka 3 lektioner som helst i lärvägen Bootcamp i backendutveckling med FastAPI, inklusive ”Avlasta lagring till S3-kompatibla buckets”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i Bootcamp i backendutveckling med FastAPI innehåller totalt 4 lektioner.
Vad lär jag mig i ”Avlasta lagring till S3-kompatibla buckets”?
Strömma uppladdningar direkt till S3/MinIO med försignerade URL:er för att hålla API:t tillståndslöst och skalbart. Ni övar på Bootcamp i backendutveckling med FastAPI med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig Bootcamp i backendutveckling med FastAPI?
Du behöver inga förkunskaper. Utbildningen i Bootcamp i backendutveckling med FastAPI på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.
Hur lång tid tar lektionen ”Avlasta lagring till S3-kompatibla buckets”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här Bootcamp i backendutveckling med FastAPI-lektionen?
Ja. Varje Bootcamp i backendutveckling med FastAPI-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- Multipart-uppladdningar och innehållsvalidering
- Strömmande svar och range requests
- Avlasta lagring till S3-kompatibla buckets
- Asynkron bild- och dokumenttransformering