Ось готовий урок, створений за твоїм майстер-промптом. Це класичний CS50-вайб: енергійно, з прикладами і фокусом на тому, що насправді відбувається всередині.
🎓 Тема уроку: Файли: Upload та Download
(Або як не "покласти" сервер, коли користувач завантажує котиків)
1. 🔥 Вступ: проблема та мотивація
Уявіть на секунду: ви створюєте наступний Instagram або TikTok. Ви написали код для реєстрації, зробили гарні кнопки, налаштували базу даних. Але є одна проблема. Користувач хоче показати світу своє фото (або відео з танцюючим хом’яком).
Він натискає "Завантажити", і... що відбувається далі?
Текст передати легко — це просто набір символів. Але фотографія? Це набір пікселів, бінарний код, нулі та одиниці, які важать у тисячі разів більше, ніж ваше ім'я в анкеті.
Риторичне питання: Ви коли-небудь замислювалися, куди насправді зникає файл, коли ви прикріплюєте його до листа в Gmail? Він лежить у базі даних? Чи десь на жорсткому диску в Каліфорнії?
Аналогія: Уявіть, що HTTP-запит (те, як браузер спілкується з сервером) — це звичайний паперовий лист. * Якщо ви хочете відправити ім'я та пароль — ви пишете їх на аркуші, кладете в конверт і відправляєте. Легко. * Але якщо ви хочете відправити книгу (файл)? Вона не влізе у звичайний конверт. Вам потрібна посилка (бандероль).
Сьогодні ми навчимося пакувати ці "посилки" (Upload) і розпаковувати їх назад (Download). Без цього ваш веб-додаток залишиться просто нудним текстовим блокнотом.
2. 🧠 Теоретична база (Що там «під капотом»?)
Щоб працювати з файлами, нам треба зрозуміти два ключові механізми.
1. Multipart/form-data (Ваша "бандероль")
Зазвичай браузер відправляє дані як простий текст. Але коли з'являється файл, ми повинні сказати браузеру: "Гей, зараз буде важкий вантаж, розбий його на частини!".
Це називається Multipart Request. Браузер бере ваш файл, ріже його на шматки, додає спеціальні розділювачі (boundaries) і відправляє на сервер потоком.
❗️ Запам’ятати обов’язково: У HTML-формі для завантаження файлів завжди має бути атрибут
enctype="multipart/form-data". Якщо ви його забудете — сервер отримає лише назву файлу (текст), а не сам файл. Це найпоширеніша помилка новачків №1.
2. Де зберігати файли? (Вічна дилема)
Тут є велика спокуса: "О, у мене є база даних (SQL), я збережу картинку прямо в таблицю!".
Зупиніться. У 99% випадків це погана ідея. Бази даних створені для швидкого пошуку тексту та чисел. Якщо запхати туди гігабайти картинок, база стане повільною і неповороткою.
Як роблять профі:
1. Файл зберігаємо на диску сервера (або у хмарному сховищі типу AWS S3).
2. У Базу Даних записуємо тільки шлях (посилання) до цього файлу (наприклад, /uploads/cat.jpg).
3. 🧪 Приклади (Python + Flask)
Давайте подивимось на це в коді. Ми використаємо Python (Flask), бо він читається як псевдокод, але логіка однакова і для PHP, і для Node.js, і для Java.
Приклад 1: "Наївний" підхід (Мінімальний)
Що ми очікуємо? Просту форму, де можна вибрати файл і відправити його.
HTML (Frontend):
<!-- Зверніть увагу на enctype! Без нього магії не буде -->
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="my_file">
<button type="submit">Завантажити</button>
</form>
Python (Backend):
from flask import Flask, request
app = Flask(__name__)
@app.route('/upload', methods=['POST'])
def upload_file():
# Отримуємо файл з "посилки"
file = request.files['my_file']
# Зберігаємо його прямо туди, де лежить наш скрипт
file.save(file.filename)
return "Файл успішно завантажено!"
Чому це працює, але небезпечно?
Якщо я завантажу файл з назвою server.py, я можу випадково перезаписати код вашого сервера! Ніколи не довіряйте іменам файлів, які надсилає користувач.
Приклад 2: Безпечний Upload (Як у реальному проєкті)
Тепер додамо трохи захисту. 1. Перевіримо, чи це взагалі картинка (розширення). 2. Зробимо ім'я файлу безпечним.
import os
from werkzeug.utils import secure_filename
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
def allowed_file(filename):
# Перевіряємо, чи є крапка і чи розширення у списку дозволених
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/upload', methods=['POST'])
def upload_secure():
if 'my_file' not in request.files:
return "Файл не знайдено", 400
file = request.files['my_file']
# Якщо файл вибрано і він дозволеного типу
if file and allowed_file(file.filename):
# Очищуємо ім'я від сміття та хакерських символів
filename = secure_filename(file.filename)
# Зберігаємо у спеціальну папку
file.save(os.path.join('/var/www/uploads', filename))
return f"Файл {filename} збережено безпечно!"
return "Недопустимий тип файлу!", 400
Приклад 3: Download (Віддаємо файл назад)
А як користувач отримає свій файл назад?
from flask import send_from_directory
@app.route('/uploads/<filename>')
def download_file(filename):
# Функція сама знайде файл, виставить правильні заголовки
# і віддасть його браузеру.
return send_from_directory('/var/www/uploads', filename)
Тут ми кажемо серверу: "Знайди файл з таким ім'ям у папці uploads і віддай його як attachment (вкладення)".
4. 🛠 Практична частина
Тепер ваша черга. Не просто читайте — пишіть код.
Завдання 1: Copy-Paste з розумінням Створіть простий сервер (на Flask/Node/іншому), реалізуйте Приклад 1. Завантажте картинку. Перевірте папку проекту — вона там з'явилася?
Завдання 2: Детектив
Приберіть з HTML-форми атрибут enctype="multipart/form-data". Спробуйте завантажити файл. Що виведе сервер? Чому? (Це класичний баг, відчуйте його).
Завдання 3: "Секретний архів"
Змініть код так, щоб сервер приймав ТІЛЬКИ .pdf файли. Спробуйте обдурити його і завантажити .jpg.
Завдання 4: Реальна проблема (Конфлікт імен)
Що станеться, якщо два користувачі (Аліса і Боб) одночасно завантажать різні фото, але з однаковою назвою avatar.jpg? Другий файл перезапише перший!
Завдання: Змініть код так, щоб до кожного файлу перед збереженням додавався унікальний ідентифікатор (підказка: погугліть UUID).
Завдання 5: Міні-кейс "А що, якщо..." Що, якщо користувач спробує завантажити файл розміром 10 Гігабайт? Як захистити сервер від переповнення пам'яті? (Знайдіть налаштування ліміту розміру файлу у вашому фреймворку).
5. 💡 Мислення як у розробника
Як думає Senior-розробник, коли чує задачу "Зроби завантаження аватарки"?
- "Не вір користувачу". Він може назвати вірус
cute_cat.jpg.exe. Він може назвати файл../../etc/passwd, щоб спробувати зламати систему шляхів. Тому ми завжди санітизуємо (чистимо) імена файлів або генеруємо свої власні випадкові імена. - "Диск не гумовий". Якщо проєкт виросте, сервер закінчиться. Тому профі одразу думають про хмарні сховища (AWS S3, Google Cloud Storage). Там місця безліч, і це дешевше, ніж тримати свої диски.
- "Метадані важливі". Ми зберігаємо не тільки файл, а й інформацію про нього в БД: хто завантажив, коли, який розмір, оригінальна назва.
Типова помилка новачка:
Зберігати файли в папці static вашого коду і заливати це все на Git/GitHub.
Правило: Код — окремо, дані користувачів (uploads) — окремо (у .gitignore).
6. 🧩 Підсумок
Отже, що ми маємо?
1. Завантаження файлів — це передача бінарних даних через "бандероль" multipart/form-data.
2. Ми не зберігаємо файли в Базі Даних (за рідкісними винятками). Ми зберігаємо їх на диску, а в БД — посилання.
3. Безпека — це перевірка розширень і перейменування файлів.
Що ви тепер вмієте? Ви можете створити форму, прийняти резюме, фото або документ від користувача, безпечно зберегти його і дати посилання на скачування. Це фундамент для будь-якої соцмережі, магазину чи корпоративного порталу.
🔜 Тизер наступного уроку: Окей, ми навчилися завантажувати файли. Але зараз будь-хто може скачати будь-який файл, якщо знає посилання. Як зробити так, щоб ваше фото бачили тільки ваші друзі? На наступному уроці поговоримо про Аутентифікацію та Cookies. Готуйте своє печиво! 🍪