Статичні файли
OxPHP віддає статичні файли безпосередньо з кореневого каталогу документів, не залучаючи PHP. Для кожного файлу автоматично визначається MIME-тип, повторний доступ пришвидшує кеш у пам’яті, а HTTP-кешування підтримується повністю: ETag, умовні запити та запити Range для часткового завантаження.
Як це працює
Коли запит відповідає статичному файлу:
- Файл знайдено — шар маршрутизації зіставляє шлях URL із файлом на диску
- Визначення MIME — тип вмісту визначається за розширенням файлу
- Перевірка кешу — перш ніж звертатися до файлової системи, сервер перевіряє файловий кеш
- Умовна перевірка — якщо запит містить
If-None-MatchабоIf-Modified-Since, OxPHP перевіряє умову й може повернути304 Not Modifiedбез тіла - Перевірка Range — якщо запит GET або HEAD містить заголовок
Range, OxPHP відповідає206 Partial Content: GET отримує лише запитаний діапазон байтів, HEAD — ті самі заголовки діапазону без тіла - Відповідь — файли до 1 MiB віддаються з кешу в пам’яті; більші файли передаються потоком просто з диска, якщо тільки їх не збираються стиснути
Налаштування
| Змінна | За замовчуванням | Опис |
|---|---|---|
STATIC_MAX_AGE |
30d |
Cache-Control: max-age для статичних файлів. Приймає 30s, 5m, 2h, 30d, 1w, 1y, просте число секунд (наприклад, 3600) або off, щоб повністю вимкнути заголовки кешування. Замінює застарілу STATIC_CACHE_TTL. |
STATIC_REVALIDATE |
off |
Значення on вмикає ревалідацію кешу вмісту в пам’яті за mtime (кожен файл перевіряється щонайбільше раз на 3 секунди; зміни стають видимими в межах цього вікна). Замінює застарілу STATIC_CACHE (де off мало протилежне значення). |
Визначення MIME
MIME-типи визначаються автоматично за розширенням файлу. Якщо визначити тип не вдається, сервер використовує application/octet-stream. Поширені відповідності:
| Розширення | Content-Type |
|---|---|
.html |
text/html |
.css |
text/css |
.js |
text/javascript |
.json |
application/json |
.png |
image/png |
.svg |
image/svg+xml |
.woff2 |
font/woff2 |
Кешування файлів
OxPHP використовує кеш у пам’яті, щоб зменшити дисковий ввід-вивід для файлів, які запитують часто:
- Файли до 1 MiB (1 048 576 байтів) зчитуються в пам’ять і кешуються. Загальний бюджет кешу становить 64 MiB (67 108 864 байти). Коли бюджет перевищено, найдавніше використані записи витісняються, щоб звільнити місце.
- Файли понад 1 MiB не кешуються, а передаються потоком просто з диска. Заголовок
Content-Lengthбереться з метаданих файлу, тож клієнт заздалегідь знає загальний розмір. - Єдиний виняток із потокової передачі — файл, що піддається стисненню і ще вміщується у вікно стиснення (3 MiB). Для клієнтів, які приймають кодування вмісту, він зчитується повністю, бо сервер не може закодувати тіло, якого не має цілком. Оскільки такий файл перевищує ліміт кешу, він стискається заново на кожен такий запит. Поставте перед ресурсами цього розміру CDN або тримайте їх меншими за 1 MiB — і робота виконуватиметься один раз. Одночасно утримується лише обмежена кількість таких файлів; запит, що надходить понад цей ліміт, отримує файл потоком і без кодування замість того, щоб чекати на вільне місце.
Файловий кеш заповнюється під час першого запиту до кожного файлу й зберігається для наступних запитів. За замовчуванням записи кешу залишаються, доки їх не витіснить політика LRU.
Ревалідація вмісту
Задайте STATIC_REVALIDATE=on, щоб увімкнути ревалідацію за mtime. У цьому режимі сервер перевіряє час зміни закешованого файлу системним викликом stat() щонайбільше раз на 3 секунди для кожного файлу, а не на кожен запит. Якщо файл на диску змінився, застарілий запис витісняється, а файл автоматично зчитується заново. У межах 3-секундного вікна закешований запис віддається просто з пам’яті без жодного системного виклику, тож витрати розподіляються, а не сплачуються за кожен запит. Зміни на диску стають видимими впродовж 3 секунд.
Увімкніть STATIC_REVALIDATE=on під час розробки, щоб бачити зміни у файлах без перезапуску сервера. У продакшні залиште змінну незаданою (за замовчуванням off) — так пропускна здатність буде максимальною, без жодних системних викликів на запит.
HTTP-кешування
Cache-Control
Коли задано STATIC_MAX_AGE (за замовчуванням 30d), кожна відповідь зі статичним файлом містить заголовок Cache-Control:
Cache-Control: public, max-age=2592000Значення max-age — це TTL, переведений у секунди. Задайте STATIC_MAX_AGE=off, щоб зовсім не надсилати цей заголовок.
ETag і Last-Modified
Кожна відповідь зі статичним файлом містить:
- ETag — сильний ETag у форматі
"<size>-<mtime_hex>", обчислений із розміру файлу та часу останньої зміни. Сильний валідатор задовольняє йIf-Range, тож перервані завантаження можна безпечно відновлювати. Коли відповідь віддається стисненою, з будь-яким кодуванням, тег послаблюється доW/"…": стиснені байти — це інше подання, а слабкий тег так само дає ревалідацію (304), але не дозволяє змішувати стиснені й нестиснені фрагменти під час відновлення. - Last-Modified — HTTP-дата за RFC 9110, що відповідає часу зміни файлу
Ці заголовки дозволяють браузерам і CDN перевіряти закешовані копії, не завантажуючи файл повторно.
Умовні запити (304)
OxPHP обробляє заголовки умовних запитів, щоб не надсилати вміст файлу, який не змінився:
- If-None-Match — клієнт надсилає закешований ETag. Якщо він збігається з поточним файлом, OxPHP повертає
304 Not Modifiedбез тіла. - If-Modified-Since — клієнт надсилає позначку часу. Якщо відтоді файл не змінювався, OxPHP повертає 304.
Згідно з RFC 9110, If-None-Match має пріоритет над If-Modified-Since. Для файлів, які вже є в кеші в пам’яті, умовна перевірка виконується без жодного звернення до диска.
Запити Range (206)
Відповіді зі статичними файлами оголошують Accept-Ranges: bytes, а запити GET з однодіапазонним заголовком Range отримують лише запитані байти:
GET /videos/intro.mp4 HTTP/1.1
Range: bytes=1048576-
HTTP/1.1 206 Partial Content
Content-Range: bytes 1048576-52428799/52428800
Content-Length: 51380224Завдяки цьому в браузерах працює перемотування <video>/<audio>, відновлюване завантаження (wget -c, менеджери завантажень) і часткове завантаження PDF. Підтримуються всі три форми діапазонів з RFC 9110: bytes=N-M, bytes=N- (від зміщення до кінця) та bytes=-N (останні N байтів).
- Діапазон, який неможливо задовольнити (початок за межами кінця файлу), повертає
416 Range Not SatisfiableзContent-Range: bytes */<size>. - If-Range враховується: коли клієнт надсилає ETag (або дату
Last-Modified) своєї часткової копії, а файл відтоді змінився, OxPHP повертає повну відповідь200замість невідповідного фрагмента. Форма з датою приймається лише тоді, коли секунда зміни файлу повністю минула: щойно записаний файл може змінитися ще раз у межах тієї самої секунди, не зсунувши дати, тож він ще не є сильним валідатором (RFC 9110). - Запити з кількома діапазонами (
bytes=0-1,4-5) отримують увесь файл як200 OK— відповідіmultipart/byterangesне формуються. - Запити HEAD із заголовком
Rangeотримують ті самі заголовки206/Content-Range, що й GET, але без тіла — так само, як у nginx і Apache. - Діапазони й стиснення взаємовиключні. Для клієнтів, які приймають будь-яке кодування вмісту, обробку діапазонів вимкнено для подань, що віддавалися б стисненими, а стиснені відповіді не оголошують
Accept-Ranges: інакше відновлене завантаження могло б приєднати нестиснені байти до стисненого початку. Файли, більші за вікно стиснення (3 MiB), ніколи не стискаються, тож діапазони завжди працюють для вмісту, якому вони справді потрібні: відео, архівів і зображень. Відповіді для файлів, придатних до стиснення, завжди містятьVary: Accept-Encoding, навіть якщо віддаються нестисненими, щоб спільні кеші не плутали варіанти. - Відповіді
206ніколи не стискаються, а обробка діапазонів не застосовується до відповідей PHP — лише до статичних файлів.
Приклад: відновлення перерваного завантаження за допомогою curl:
curl -C - -O https://example.com/dist/app-installer.dmgВимкнення кешування
Є два незалежні рівні кешу, і для кожного — своя змінна:
| Змінна | Що керує | Дія off |
|---|---|---|
STATIC_MAX_AGE=off |
Кеш браузера (HTTP-заголовки) | Заголовки Cache-Control, ETag і Last-Modified не надсилаються |
STATIC_REVALIDATE=on |
Кеш сервера в пам’яті | mtime файлу перевіряється щонайбільше раз на 3 с для кожного файлу; застарілі записи витісняються автоматично |
Під час розробки задайте STATIC_REVALIDATE=on, щоб сервер завжди віддавав свіжий вміст. За потреби також задайте STATIC_MAX_AGE=off, щоб повністю запобігти кешуванню в браузері.
Усунення несправностей
Сервер і далі віддає застарілі файли
За замовчуванням кеш вмісту в пам’яті не перевіряє, чи змінилися файли на диску. Під час розробки задайте STATIC_REVALIDATE=on, щоб увімкнути ревалідацію за mtime, — сервер автоматично помічатиме зміни у файлах (упродовж 3 секунд).
Браузер і далі показує застарілі файли
Якщо сервер віддає свіжий вміст, а браузер досі показує стару версію, винен власний кеш браузера. Задайте STATIC_MAX_AGE=off, щоб не надсилати заголовки кешування, або скористайтеся примусовим перезавантаженням у браузері (Shift+F5 або Cmd+Shift+R).
Файли віддаються з `application/octet-stream`
OxPHP визначає MIME-тип за розширенням файлу. Якщо розширення немає або воно не розпізнане, використовується application/octet-stream. Додайте файлу правильне розширення або переконайтеся, що ваш фреймворк явно задає заголовок Content-Type у відповідях PHP.
Великі файли віддаються повільно
Файли понад 1 MiB не кешуються в пам’яті. Вони передаються потоком із диска на кожен запит або — якщо піддаються стисненню й менші за 3 MiB — зчитуються та стискаються заново на кожен запит від клієнта, що приймає стиснення. Для дуже великих файлів поставте перед OxPHP CDN, щоб кешувати їх на периферії. Інший варіант — організувати ресурси так, щоб файли, які запитують найчастіше, не перевищували 1 MiB.
Повертається 304, хоча ви очікуєте 200
Відповідь 304 означає, що клієнт уже має актуальну версію. Це коректна поведінка. Якщо під час розробки потрібно примусово отримати свіжу відповідь, задайте STATIC_MAX_AGE=off, щоб не надсилати заголовки ETag і Last-Modified.
Приклад для Docker
services:
app:
image: ghcr.io/oxphp/oxphp:0.13.0
ports:
- "8080:80"
volumes:
- ./src:/var/www/html
environment:
- DOCUMENT_ROOT=/var/www/html/public
- ENTRY_FILE=index.php
- STATIC_MAX_AGE=1yРекомендації
- Використовуйте довгі TTL разом з іменами файлів для скидання кешу у продакшні (наприклад,
app.a1b2c3.js). ЗадайтеSTATIC_MAX_AGE=1yдля максимального кешування в браузерах і CDN. - Під час розробки задавайте
STATIC_REVALIDATE=on, щоб сервер автоматично помічав зміни у файлах. За потреби також задайтеSTATIC_MAX_AGE=off, щоб оминути кешування в браузері. - Ставте CDN перед OxPHP для сайтів з високим трафіком. Заголовки
ETag,Last-ModifiedіCache-Controlпрацюють з усіма основними CDN-провайдерами. - Довірте хешування ресурсів своєму інструменту збирання. Такі інструменти, як Vite і Laravel Mix, автоматично генерують імена файлів із хешем, тож довгі TTL кешу стають безпечними.
Промпти для вашого агента
Ці промпти передбачають агента з підключеним MCP-сервером документації OxPHP — потрібні сторінки він прочитає сам.
Налаштуй кешування статичних файлів OxPHP для мого конвеєра ресурсів. Прочитай сторінку `features/static-files` через MCP-сервер документації OxPHP.
Перевір, чи збирання в цьому репозиторії генерує імена файлів із хешем, а потім дай мені значення `STATIC_MAX_AGE` і `STATIC_REVALIDATE` для продакшну та для розробки й поясни, що кожне з них змінює для браузера, який повертається на сайт.Див. також
- Стиснення — як стискаються статичні файли, придатні до стиснення, і як вони кешуються для кожного кодування
- Маршрутизація — як шляхи URL зіставляються з файлами на диску
- Довідник із налаштувань — повний перелік змінних середовища