Pliki statyczne vs pliki użytkowników
Najpierw warto rozróżnić dwa pojęcia, które są często mylone:
| Typ pliku | Przykłady | Przechowywanie |
|---|---|---|
| Pliki statyczne | CSS, JS, obrazy UI, fonty | serwowane z /static/ lub CDN |
| Pliki użytkowników (media) | zdjęcia, dokumenty, PDF-y | przechowywane w /media/ lub zewnętrznej chmurze |
Zasada: Statyczne pliki — dostarczasz Ty jako developer. Uploady — dostarczają użytkownicy (i tu zaczynają się problemy).
Pliki statyczne są zwykle wersjonowane w repozytorium, podczas gdy pliki użytkowników są dynamicznie generowane i wymagają innego podejścia do przechowywania i serwowania.
Serwowanie plików statycznych w Django
Django oferuje wbudowany mechanizm obsługi plików statycznych. W pliku settings.py definiujesz ścieżki:
Python1 2 3 4 5STATIC_URL = '/static/' STATIC_ROOT = BASE_DIR / 'staticfiles' MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media'
STATIC_URL to URL, pod którym pliki będą dostępne, a STATIC_ROOT to katalog, do którego Django zbiera wszystkie pliki statyczne przed wdrożeniem. MEDIA_URL i MEDIA_ROOT służą do plików przesłanych przez użytkowników.
W produkcji
W środowisku produkcyjnym Django nie powinno serwować statycznych plików samodzielnie — to zadanie dla Nginx lub CDN. Django jest zoptymalizowane do obsługi logiki aplikacji, a serwowanie plików statycznych powinno być delegowane do specjalizowanych serwerów.
Przykład konfiguracji Nginx:
NGINX1 2 3 4 5 6 7 8 9 10location /static/ { alias /app/staticfiles/; expires 30d; add_header Cache-Control "public, immutable"; } location /media/ { alias /app/media/; expires 7d; }
Statyczne pliki można zbudować przed wdrożeniem komendą:
Bash1python manage.py collectstatic
Ta komenda zbiera wszystkie pliki statyczne z aplikacji Django i umieszcza je w STATIC_ROOT, gotowe do serwowania przez Nginx lub uploadu do CDN. Flagi expires i Cache-Control w Nginx zapewniają, że przeglądarki cache'ują pliki statyczne, co znacznie zmniejsza obciążenie serwera.
Upload plików w Django
Django pozwala bardzo łatwo tworzyć formularze do przesyłania plików. Proces jest zintegrowany z systemem formularzy Django.
Przykład modelu
Python1 2 3 4 5 6from django.db import models class Document(models.Model): title = models.CharField(max_length=100) file = models.FileField(upload_to='uploads/') uploaded_at = models.DateTimeField(auto_now_add=True)
FileField automatycznie obsługuje zapisywanie plików w katalogu określonym przez upload_to. Możesz też użyć funkcji do generowania dynamicznych ścieżek:
Python1 2 3 4 5def upload_path(instance, filename): return f'documents/{instance.id}/{filename}' class Document(models.Model): file = models.FileField(upload_to=upload_path)
Formularz i widok
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14from django.shortcuts import render from .models import Document from .forms import DocumentForm def upload_file(request): if request.method == 'POST': form = DocumentForm(request.POST, request.FILES) if form.is_valid(): form.save() return redirect('success') else: form = DocumentForm() return render(request, 'upload.html', {'form': form})
Ważne: musisz upewnić się, że formularz ma atrybut enctype="multipart/form-data" w szablonie HTML, inaczej Django nie otrzyma plików.
Walidacja bezpieczeństwa
Podstawowe zasady bezpieczeństwa przy obsłudze uploadów:
- Sprawdzaj rozmiar pliku — ogranicz maksymalny rozmiar, aby zapobiec atakom DoS:
Python1 2 3 4 5 6from django.core.exceptions import ValidationError def validate_file_size(file): max_size = 10 * 1024 * 1024# 10MB if file.size > max_size: raise ValidationError(f'Plik jest za duży. Maksymalny rozmiar: {max_size} bytes')
- Waliduj typ pliku — sprawdzaj rozszerzenia i MIME type (używaj
python-magic):
Python1 2 3 4 5 6 7 8 9 10 11import magic ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'application/pdf'] def validate_file_type(file): mime = magic.Magic(mime=True) file_type = mime.from_buffer(file.read(1024)) if file_type not in ALLOWED_MIME_TYPES: raise ValidationError('Niedozwolony typ pliku') file.seek(0)# Reset wskaźnika pliku
- Zmieniaj nazwy plików — unikaj oryginalnych nazw, używaj UUID:
Python1 2 3 4 5 6 7import uuid import os def upload_path(instance, filename): ext = filename.split('.')[-1] new_filename = f'{uuid.uuid4()}.{ext}' return f'uploads/{new_filename}'
- Nie pozwalaj na wykonywalne formaty — blokuj
.exe,.js,.php,.shitp.
Upload plików w Flask
Flask wymaga nieco więcej konfiguracji, ale zasada działania jest podobna. Flask nie ma wbudowanego systemu formularzy jak Django, więc musisz sam obsłużyć walidację.
Podstawowy przykład uploadu w Flask
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37from flask import Flask, request, redirect, url_for from werkzeug.utils import secure_filename import os app = Flask(__name__) app.config['UPLOAD_FOLDER'] = 'uploads/' app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024# 16MB max ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'} def allowed_file(filename): return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route('/upload', methods=['GET', 'POST']) def upload_file(): if request.method == 'POST': if 'file' not in request.files: return redirect(request.url) file = request.files['file'] if file.filename == '': return redirect(request.url) if file and allowed_file(file.filename): filename = secure_filename(file.filename) file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) return redirect(url_for('upload_file', filename=filename)) return ''' <!doctype html> <title>Upload new File</title> <h1>Upload new File</h1> <form method=post enctype=multipart/form-data> <input type=file name=file> <input type=submit value=Upload> </form> '''
Najważniejsze zabezpieczenia
- Waliduj nazwę pliku — używaj
werkzeug.utils.secure_filename, która usuwa niebezpieczne znaki:
Python1 2 3from werkzeug.utils import secure_filename filename = secure_filename(file.filename)# Usuwa ../, znaki specjalne, itp.
- Ogranicz rozmiar pliku — ustaw
MAX_CONTENT_LENGTHw konfiguracji Flask. - Zabezpiecz katalog uploadów — upewnij się, że serwer HTTP nie pozwala na listowanie zawartości katalogu (dodaj
Options -Indexesw Apache lub odpowiednią konfigurację w Nginx).
Upload w FastAPI
FastAPI integruje uploady bardzo elegancko — z automatyczną walidacją i obsługą async. FastAPI wykorzystuje python-multipart do parsowania formularzy multipart.
Podstawowy przykład uploadu w FastAPI
Python1 2 3 4 5 6 7 8 9 10 11 12 13from fastapi import FastAPI, File, UploadFile from fastapi.responses import JSONResponse app = FastAPI() @app.post("/upload") async def upload_file(file: UploadFile = File(...)): contents = await file.read() with open(f"uploads/{file.filename}", "wb") as f: f.write(contents) return {"filename": file.filename, "size": len(contents)}
Zaawansowana walidacja
Możesz używać asynchronicznego odczytu i walidacji rozmiaru pliku:
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28from fastapi import FastAPI, File, UploadFile, HTTPException import aiofiles ALLOWED_CONTENT_TYPES = ["image/jpeg", "image/png", "application/pdf"] MAX_FILE_SIZE = 10 * 1024 * 1024# 10MB @app.post("/upload") async def upload_file(file: UploadFile = File(...)): # Walidacja typu pliku if file.content_type not in ALLOWED_CONTENT_TYPES: raise HTTPException( status_code=400, detail=f"Niedozwolony typ pliku: {file.content_type}" ) # Walidacja rozmiaru contents = await file.read() if len(contents) > MAX_FILE_SIZE: raise HTTPException( status_code=400, detail=f"Plik jest za duży. Maksymalny rozmiar: {MAX_FILE_SIZE} bytes" ) # Asynchroniczne zapisywanie async with aiofiles.open(f"uploads/{file.filename}", 'wb') as f: await f.write(contents) return {"filename": file.filename, "size": len(contents)}
FastAPI automatycznie parsuje multipart/form-data i udostępnia plik jako obiekt UploadFile z metodami async. To sprawia, że obsługa dużych plików jest bardziej wydajna.
CDN – przyspieszenie dostarczania plików
W środowisku produkcyjnym pliki statyczne i uploady najlepiej dostarczać przez CDN (Content Delivery Network), np.:
- Cloudflare
- AWS CloudFront
- Google Cloud CDN
- Bunny.net
Zalety CDN
- Szybsze ładowanie — dzięki serwerom bliskim użytkownikowi (edge locations), pliki są dostarczane z najbliższego serwera CDN.
- Automatyczny caching — CDN automatycznie cache'uje pliki, zmniejszając obciążenie głównego serwera.
- Odciążenie serwera aplikacyjnego — serwer aplikacji może skupić się na logice biznesowej zamiast serwować pliki.
Integracja z Django
Przykład konfiguracji Django z AWS S3 i CloudFront:
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16# settings.py INSTALLED_APPS = [ # ... 'storages', ] DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' STATICFILES_STORAGE = 'storages.backends.s3boto3.S3StaticStorage' AWS_ACCESS_KEY_ID = config('AWS_ACCESS_KEY_ID') AWS_SECRET_ACCESS_KEY = config('AWS_SECRET_ACCESS_KEY') AWS_STORAGE_BUCKET_NAME = config('AWS_STORAGE_BUCKET_NAME') AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com' AWS_S3_OBJECT_PARAMETERS = { 'CacheControl': 'max-age=86400', }
Po skonfigurowaniu, pliki będą automatycznie uploadowane do S3 i serwowane przez CloudFront.
Bezpieczeństwo plików
Uploady to częsty wektor ataku. Kilka kluczowych zasad bezpieczeństwa:
-
Nigdy nie ufaj nazwie pliku użytkownika — zawsze waliduj i sanitizuj nazwy plików. Atakujący mogą próbować użyć nazw takich jak
../../../etc/passwd(path traversal). -
Nie pozwalaj na upload plików wykonywalnych — blokuj
.exe,.sh,.php,.js(gdy nie są to zamierzone pliki statyczne) i inne formaty, które mogą być wykonane przez serwer. -
Skanuj pliki antywirusem — użyj ClamAV lub podobnego rozwiązania do skanowania uploadów:
Python1 2 3 4 5 6 7import pyclamd def scan_file(file_path): cd = pyclamd.ClamdUnixSocket() result = cd.scan_file(file_path) if result: raise ValidationError('Plik zawiera malware')
- Używaj UUID dla nazw plików — unikniesz kolizji i problemów z niebezpiecznymi nazwami:
Python1 2 3 4 5import uuid def generate_filename(original_filename): ext = original_filename.rsplit('.', 1)[1] if '.' in original_filename else '' return f'{uuid.uuid4()}.{ext}' if ext else str(uuid.uuid4())
-
Przechowuj pliki poza katalogiem aplikacji — zapobiega to wykonaniu przypadkowo przesłanych skryptów przez serwer.
-
Szyfruj wrażliwe dane w chmurze — jeśli przechowujesz wrażliwe dokumenty w chmurze (S3, Azure Blob), włącz szyfrowanie po stronie serwera.
Dobre praktyki
Przy projektowaniu systemu obsługi plików warto pamiętać o następujących zasadach:
-
W środowisku produkcyjnym serwuj statyczne pliki przez Nginx lub CDN — Django nie jest optymalizowane do serwowania plików statycznych.
-
Używaj S3 lub Cloud Storage zamiast lokalnego dysku — przy wielu instancjach aplikacji (skalowanie poziome) lokalny dysk nie zadziała. Użyj zewnętrznego storage'u (AWS S3, Google Cloud Storage, Azure Blob Storage).
-
Nie zapisuj dużych plików w bazie danych — przechowuj tylko ścieżki/metadane w bazie, a same pliki w storage'u. Baza danych nie jest optymalna do przechowywania binariów.
-
Kompresuj obrazy i minimalizuj JS/CSS przed publikacją — użyj narzędzi takich jak Pillow (kompresja obrazów), minify (JS/CSS) przed wdrożeniem.
-
Stosuj async uploady — dla dużych plików użyj asynchronicznej obsługi (np. Celery + FastAPI Background Tasks), aby nie blokować głównego wątku aplikacji:
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14from fastapi import BackgroundTasks from celery import Celery celery = Celery('tasks') @app.post("/upload") async def upload_file( file: UploadFile = File(...), background_tasks: BackgroundTasks ): contents = await file.read() background_tasks.add_task(process_large_file, contents) return {"status": "uploaded", "processing": "in_progress"}
Podsumowanie
Obsługa plików to jeden z najczęstszych i najbardziej podatnych na błędy elementów aplikacji webowych. Niezależnie od tego, czy używasz Django, Flask czy FastAPI, musisz zadbać o bezpieczeństwo uploadu, wydajne serwowanie plików statycznych, optymalizację przez CDN oraz skalowalność przy dużych obciążeniach. Dobrze zaprojektowany system obsługi plików to gwarancja bezpieczeństwa, wydajności i stabilności Twojej aplikacji. Zacznij od podstawowych zabezpieczeń, a następnie rozbuduj system o CDN i zaawansowane funkcje w miarę potrzeb projektu.



