Статические файлы
Middleware статических файлов отдаёт файлы из каталога до того, как запрос дойдёт до PHP. Он отвечает на запрос, если путь указывает на файл в его корневом каталоге. Все остальные запросы он передаёт в PHP без изменений.
Включение middleware
Middleware включают две части rapira.toml. Добавьте static в список middleware таблицы [http]. Затем добавьте таблицу [http.static], которая задаёт каталог файлов.
[http]
middleware = ["static"]
[http.static]
root = "public" # Обязательно. Относительный путь использует каталог этого файла.
forbid = [".php"] # Необязательно. Этот список заменяет значение по умолчанию.middleware задаёт цепочку middleware в порядке списка. Сейчас static - единственное имя, которое он принимает.
root задаёт каталог с файлами для отдачи. Значения по умолчанию нет, поэтому таблица должна задать этот ключ. Относительный путь использует каталог файла конфигурации, как и http.pool.entrypoint.
forbid содержит суффиксы имён файлов, которые middleware не отдаёт. Значение по умолчанию: [".php"]. Явный список заменяет значение по умолчанию. Например, forbid = [".php", ".env"] блокирует оба суффикса.
Значение forbid = [] разрешает все файлы в корневом каталоге, включая исходный код PHP. Не используйте это значение для общедоступного корневого каталога. Оно может раскрыть код приложения и встроенные секреты.
Каждая запись начинается с точки и содержит минимум два символа. Она не может содержать / или пробельные символы. Недопустимая запись останавливает инициализацию сервера.
Остальные ключи файла конфигурации описаны на странице Конфигурация.
Проверки при инициализации
Сервер проверяет корневой каталог до приёма запросов. Корневой каталог должен существовать и быть каталогом. Учётная запись сервера должна иметь для него право поиска. Ошибка проверки останавливает инициализацию и указывает путь.
Обе части конфигурации должны присутствовать вместе. Запись "static" в списке middleware требует таблицу [http.static], а таблица требует эту запись. Rapira также отклоняет повторяющиеся и неизвестные имена middleware.
Почему сервер проверяет корневой каталог дважды?
Первая проверка читает метаданные корневого каталога. Она подтверждает, что путь существует и является каталогом. Вторая проверка разрешает . внутри корневого каталога. Она проверяет право поиска, которое требуется для доступа к файлам.
Права поиска и чтения каталога используют разные биты. Поэтому первая проверка может пройти, а вторая завершиться ошибкой. Требуемые права описаны в документации stat.
Правила отдачи
Middleware обрабатывает запрос, только если метод GET или HEAD. Любой другой метод уходит в PHP.
Middleware применяет эти правила к пути:
- Путь, в котором сегмент начинается с
., уходит в PHP. Поэтому/.env,/.git/configи/../outside.txtне получают доступ к файлам. - Проверка
forbidработает с путём после раскрытия процентного кодирования и не учитывает регистр. Если.phpзапрещён,/index.php,/index%2Ephpи/Upper.PHPуходят в PHP. - Путь с процентным кодированием, которое не раскрывается в UTF-8, уходит в PHP. Например,
/%FF.cssуходит в PHP. - URL каталога уходит в PHP. Middleware не отдаёт индексный файл.
- Отсутствующий файл, ошибка прав доступа или недопустимое имя файла уходят в PHP. Недопустимое имя файла слишком длинное или содержит нулевой байт.
- Любая другая ошибка чтения возвращает
500. PHP не получает такой запрос, а Rapira пишет ошибку в лог с цельюhttp.
Запрос, который уходит в PHP, приходит без изменений. Что PHP читает из запроса, описано в разделе Запросы и ответы HTTP.
Почему на URL каталога не приходит index.html?
PHP управляет пространством URL, поэтому URL каталога является маршрутом приложения. Автоматический индексный файл создал бы два возможных ответа. Файловая система могла бы вернуть один ответ, а маршрутизатор приложения другой. Входной скрипт не получал бы запросы к /.
Поля ответа
Следующие поля есть в ответе, который отдаёт файл. В ответе 500 от middleware их нет.
Middleware задаёт Content-Type по расширению файла. Имя без известного расширения получает application/octet-stream.
Ответ содержит поля ETag и Last-Modified. Middleware создаёт Last-Modified из времени изменения файла. Он создаёт ETag из времени изменения и длины файла. Файл без времени изменения не получает ни одно из этих полей.
Middleware возвращает 304 Not Modified, когда If-None-Match совпадает с ETag. Запрос без If-None-Match получает 304 Not Modified, если время изменения файла не позже времени в If-Modified-Since. Этот ответ содержит только ETag и Last-Modified. У него нет тела.
Ответ также содержит Accept-Ranges: bytes. Запрос с Range может вернуть 206 Partial Content и поле Content-Range. Rapira возвращает 416 Range Not Satisfiable для недопустимого диапазона или для нескольких диапазонов. PHP не получает такой запрос.
Неуспешное условие If-Match или If-Unmodified-Since возвращает 412 Precondition Failed.
Middleware не задаёт Cache-Control. Задайте это поле в обратном прокси, если оно нужно клиентам.
Кеш файлов
Каждый воркер хранит в памяти файлы, которые он отдаёт. Настроить кеш нельзя. Кеш использует эти фиксированные значения:
- Запись кеша действительна одну секунду.
- Кеш не хранит файл больше 256 КиБ. Такой файл на каждом запросе читается с диска потоком.
- Каждый воркер хранит не больше 16 МиБ. Поэтому кеш может использовать 16 МиБ памяти на каждый процесс из
http.pool.processes.
Через одну секунду следующий запрос к файлу выполняет для него stat. Воркер сохраняет запись, если время изменения и длина не изменились. Иначе он читает файл повторно. Rapira перестаёт отдавать удалённый файл не позже чем через одну секунду.
Полный кеш продолжает отдавать свои записи. Сначала он удаляет устаревшие записи. Если кеш остаётся полным, он не сохраняет новый файл.
Новый воркер начинает с пустым кешем. Поэтому перезагрузка, замена воркера или перезапуск очищают кеш.
Корневой каталог должен находиться на локальном хранилище. Middleware выполняет stat и open в потоке, который обрабатывает запросы. Медленная файловая система задерживает другие соединения этого воркера.
Почему кеш не замечает мой изменённый файл?
Кеш сравнивает только время изменения и длину файла. ETag содержит те же значения. Кеш не замечает замену, которая сохраняет оба значения. Изменение прав тоже сохраняет запись. Чтобы удалить запись, удалите файл, измените время его изменения или выполните перезагрузку сервера.