Статичні файли
У ProfileService виріс фронтенд. HTML, CSS, скрипти, аватарки, звична справа. Раніше їх роздавав nginx, але nginx ми урочисто провели в першому розділі. Хто тепер їх обслуговує?
Перший порив зрозумілий: написати обробник, який відкриває файли за URL. Зупиніться. Прикиньте, що це означає: тисячі запитів до логотипа на день, і на кожен корутина, вхід у PHP, fopen, вихід. PHP тут не робить нічого, що вимагало б PHP.
Сервер розв'язує це радикально. Статичний маршрут обслуговується цілком у C. Запит до нього в PHP не заходить узагалі:
use TrueAsync\StaticHandler;
$server->addStaticHandler(
new StaticHandler('/assets/', '/var/www/profile/public')
);Префікс URL, каталог на диску, готово. GET /assets/css/app.css перетворюється на асинхронне читання файлу просто в сокет, через libuv, повз PHP. Обробники з попередніх розділів далі отримують усе інше. Маунтів може бути кілька, а збіги шукаються в порядку реєстрації. Увесь HTTP-етикет із sendFile тут теж є: Content-Type, ETag з 304, докачка через Range.
Налаштування маунта
StaticHandler налаштовується ланцюжком, до підключення до сервера:
$static = (new StaticHandler('/assets/', '/var/www/profile/public'))
->setCacheControl('public, max-age=86400')
->enablePrecompressed('br', 'zstd', 'gzip')
->hide('*.map', 'drafts/**')
->setOnMissing(StaticOnMissing::NEXT);
$server->addStaticHandler($static);Пройдімося по рядках.
setCacheControl — заголовок кешування на кожну відповідь. У парі з увімкненим за замовчуванням ETag браузер завантажує файл заново лише тоді, коли файл справді змінився.
enablePrecompressed — мій улюблений пункт. Якщо поруч із app.css лежить app.css.br, клієнту з відповідним Accept-Encoding піде готовий стиснений файл. Вдумайтеся в економіку: стиснули один раз на етапі збірки фронтенда, найдорожчим і найякіснішим рівнем, а роздаєте мільйон разів, не витрачаючи на компресію жодного такту.
hide — глоби, які отримують 404 незалежно від того, існує файл чи ні. Карти сорсів і чернетки назовні не підуть.
setOnMissing(NEXT) — доля запитів повз файли. За замовчуванням промах відповідає 404 просто з C. NEXT натомість передає запит далі, до звичайного PHP-обробника. Навіщо? SPA. Файл /assets/app.js віддається з диска, а неіснуючий /assets/whatever провалюється в застосунок, який відповість своїм index.html.
Після addStaticHandler об'єкт замикається: сервер уже побудував по ньому структури для гарячого шляху. Спроба смикнути сеттер після цього виняток.
Безпека за замовчуванням
Невеликий ліричний відступ. Віддача файлів за URL історично одна з найурожайніших дір вебсерверів. ../../etc/passwd в адресному рядку прийом старший за багатьох читачів цього розділу.
Тому політика з коробки параноїдальна. Запити з .. отримують 404. Шляхи через файли з крапкою на початку отримують 404: ні .env, ні .git не витечуть, навіть якщо випадково опиняться в каталозі. Символічні посилання не розіменовуються зовсім: файл має фізично лежати всередині кореня маунта, і жоден симлінк назовні його не витягне.
Усе це послаблюється свідомо (setDotfilePolicy, setSymlinkPolicy), але замовчування підібрані так, щоб варіант «підключив і забув» був безпечним.
Коли файлів дуже багато
Для гарячих маунтів є ще один важіль:
$static->setOpenFileCache(maxEntries: 1024, ttlSeconds: 60);Кеш запам'ятовує розв'язаний шлях, метадані та заголовки останніх файлів і зрізає syscall-прогулянку на повторних запитах. На великому каталозі або мережевій файловій системі відчутно. На маленькому локальному сайті ні, тому й вимкнений за замовчуванням.
Ось і весь розділ, я обіцяв короткий. Статика летить повз PHP, а PHP займається справою. І ось тепер, коли в сервісу є обличчя, час його оживити. Пам'ятаєте смужку прогресу з найпершого розділу першої серії, ту, що малювалася в терміналі? Вона зараз переїде в браузер. І малювати її буде сам сервер.