Я написал небольшой веб-сайт (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
файл, если вам нужно добавить больше правил позже, то, вероятно, так и есть (и необходимость не забывать изменять существующие правила таким образом может привести к ошибкам).