Назад | Перейти на главную страницу

Использование правил перезаписи Apache в .htaccess для удаления .html вызывает ошибку 500

Я написал небольшой веб-сайт (4 страницы, только HTML), и я хочу удалить расширение .html из URL-адреса, поместив некоторые правила перезаписи в свой файл .htaccess, я поискал в Google и нашел несколько фрагментов, похожих на этот:

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME}\.html -f
  RewriteRule ^(.*)$ $1.html
</IfModule>

Оба следующих URL-адреса обслуживают один и тот же контент (чего я ожидал)

https://example.io/contact
https://example.io/contact.html

Однако следующее дает ошибку 500:

https://example.io/contact/

Этот каталог не существует, и если я удалю упомянутый выше код перезаписи, вместо этого он будет 404, чего я ожидал. Почему приведенный выше код вызывает ошибку 500?

Еще более интересно то, что это будет 500:

https://example.io/contact/blah

Но это будет 404:

https://example.io/contact123/blah

Ни contact /, ни contact123 / не существуют как каталог, но contact.html существует, а contact123.html - нет.

Любая помощь или объяснение будут оценены.


Редактировать:

MrWhite уже дал правильный ответ, но для тех, кто смотрит в будущее, журналы ошибок Apache выглядят так:

[Thu Oct 24 20:49:47.722210 2019] [core:error] [pid 13001:tid 139915446667008] [client 1.2.3.4:39006] AH00124: Request exceeded the limit of 10 internal redirects due to probable configuration error. Use 'LimitInternalRecursion' to increase the limit if necessary. Use 'LogLevel debug' to get a backtrace.

Я проверил журналы и не понял, почему это происходит, но забыл включить это в вопрос.

tl; dr Запрос на /contact/ (или /contact/blah) приводит к циклу перезаписи (ответ 500 Internal Server Error), потому что REQUEST_FILENAME содержит путь к отображаемой файловой системе; не тот URL-путь, который вы ожидаете.


RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html

"Проблема" в использовании REQUEST_FILENAME во 2-м состоянии. В REQUEST_FILENAME переменная сервера содержит абсолютный путь к файловой системе после URL-адрес сопоставлен с файловой системой. Это не обязательно то же самое, что и URL-путь, но это условие предполагает что это. Когда URL-путь содержит целые сегменты пути, которые не отображаются в файловой системе (как в /contact/blah или /contact123/blah) тогда REQUEST_FILENAME по существу «сводится» к последнему сегменту пути, который соответствует каталогу, плюс «имя файла» (т.е. .../contact и .../contact123 соответственно - корень документа, т.е. /, является последним совпавшим каталогом в этом примере).

Запрос /contact

Когда вы просите /contact тогда URL-путь /contact и REQUEST_FILENAME является /path/to/document-root/contact - так что REQUEST_FILENAME отображается прямо в URL-путь. Условие испытания /path/to/document-root/contact.html успешно, и запрос переписан на contact.html. Все хорошо.

Запрос /contact/ или /contact/blah

Однако, когда вы запрашиваете /contact/ тогда URL-путь /contact/, но REQUEST_FILENAME снова /path/to/document-root/contact (без суффикса косой черты). Условие проверки снова успешно (как указано выше), но запрос переписывается на contact/.html (поскольку .html добавляется к захвачен URL-путь, т.е. $1.html). Циклы обработки, REQUEST_FILENAME оценивается так же, как и раньше (условие снова выполнено успешно), и запрос перезаписывается второй раз на contact/.html.html. И т. Д. И т. Д., Что приводит к циклу перезаписи, который в конечном итоге достигает внутреннего предела (по умолчанию 10), когда он «ломается» и сервер отвечает 500 внутренней ошибкой сервера.

Запрос /contact123/blah

/contact123/blah, с другой стороны, приводит к 404, потому что REQUEST_FILENAME переменная сервера становится /path/to/document-root/contact123 и /path/to/document-root/contact123.html не существует, поэтому перезапись не происходит.

"Решение"

Чтобы "исправить" это поведение, вы должны использовать REQUEST_URI вместо этого серверная переменная. Он содержит относительный к корню URL-путь. Добавьте это в DOCUMENT_ROOT переменная сервера, чтобы создать имя файла для проверки.

Например:

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.html -f
RewriteRule (.*) $1.html [L]

Теперь условием тестирования является проверка того же пути файловой системы, на который будет перезаписан запрос (в случае успеха).

Запрос на /contact/, /contact/blah или /contact123/blah все теперь приводят к 404, как и ожидалось.

Обратите внимание, что нет необходимости использовать обратную косую черту после буквальной точки в RewriteCond TestString поскольку это не регулярное выражение.

Незначительные моменты ... ^ и $ якоря на ^(.*)$ не нужны, поскольку регулярное выражение по умолчанию является жадным (хотя некоторым пользователям они все еще нравятся для читаемость?). Вы также должны включить L (last) флаг на RewriteRule. Хотя в этом нет необходимости, если это единственное (или последнее) правило в .htaccess файл, если вам нужно добавить больше правил позже, то, вероятно, так и есть (и необходимость не забывать изменять существующие правила таким образом может привести к ошибкам).