Uso de reglas de reescritura de Apache en .htaccess para eliminar .html causando un error 500
He escrito un sitio web pequeño (4 páginas, solo HTML) y quiero eliminar la extensión .html de la URL poniendo algunas reglas de reescritura en mi archivo .htaccess, busqué en Google y encontré varios fragmentos similares a este:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html
</IfModule>
Las dos URL siguientes ofrecen el mismo contenido (lo que yo esperaría)
https://example.io/contact
https://example.io/contact.html
Sin embargo, lo siguiente da un error de 500:
https://example.io/contact/
Este directorio no existe y si elimino el código de reescritura mencionado anteriormente, aparecerá el 404, que es lo que esperaría. ¿Por qué el código anterior causa un error 500?
Aún más interesante es que esto 500:
https://example.io/contact/blah
Pero esto 404:
https://example.io/contact123/blah
Ni contact / ni contact123 / existen como directorio, pero contact.html existe y contact123.html no.
Se agradecería cualquier ayuda o explicación.
Editar:
MrWhite ya ha dado la respuesta correcta, pero para cualquiera que esté mirando en el futuro, los registros de errores de Apache se ven así:
[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.
Había revisado los registros y no estaba seguro de por qué estaba sucediendo, pero olvidé incluir esto en la pregunta.
Respuestas
tl; dr Una solicitud de /contact/
(o /contact/blah
) da como resultado un bucle de reescritura (respuesta de error interno del servidor 500) porque REQUEST_FILENAME
contiene la ruta del sistema de archivos asignada; no la ruta URL que esperabas.
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME}\.html -f RewriteRule ^(.*)$ $1.html
El "problema" es el uso de REQUEST_FILENAME
en la segunda condición. La REQUEST_FILENAME
variable del servidor contiene la ruta absoluta del sistema de archivos después de que la URL se ha asignado al sistema de archivos. Esto no es necesariamente lo mismo que la ruta URL, pero esta condición asume que lo es. Cuando la ruta URL contiene segmentos de ruta completos que no se asignan al sistema de archivos (como en /contact/blah
o /contact123/blah
), entonces REQUEST_FILENAME
esencialmente se "reduce" al último segmento de ruta que se asigna a un directorio, más el "nombre de archivo" (es decir, .../contact
y .../contact123
respectivamente - la raíz del documento, es decir /
, es el último directorio coincidente en este ejemplo).
Solicitud /contact
Cuando lo solicita /contact
, la ruta de URL es /contact
y REQUEST_FILENAME
es /path/to/document-root/contact
, por lo que se REQUEST_FILENAME
asigna directamente a la ruta de URL. La condición de prueba /path/to/document-root/contact.html
es exitosa y la solicitud se reescribe a contact.html
. Todo es bueno.
Solicitar /contact/
o/contact/blah
Sin embargo, cuando lo solicita /contact/
, la ruta de la URL es /contact/
, pero REQUEST_FILENAME
es nuevamente /path/to/document-root/contact
(sin sufijo de barra). La condición de prueba es nuevamente exitosa (como arriba), pero la solicitud se reescribe contact/.html
(ya que .html
se agrega a la ruta URL capturada , es decir $1.html
). El proceso se repite, se REQUEST_FILENAME
evalúa igual que antes (la condición es nuevamente exitosa) y la solicitud se reescribe por segunda vez en contact/.html.html
. Etc, etc, resultando en un bucle de reescritura que eventualmente alcanza un límite interno (predeterminado 10) cuando se "rompe" y el servidor responde con un 500 Internal Server Error.
Solicitud /contact123/blah
/contact123/blah
, por otro lado, da como resultado un 404 porque la REQUEST_FILENAME
variable del servidor se convierte /path/to/document-root/contact123
y /path/to/document-root/contact123.html
no existe, por lo que no se produce ninguna reescritura en primer lugar.
"Solución"
Para "corregir" este comportamiento, debería utilizar la REQUEST_URI
variable de servidor en su lugar. Contiene la ruta URL relativa a la raíz. Agregue esto a la DOCUMENT_ROOT
variable del servidor para construir un nombre de archivo para probar.
Por ejemplo:
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.html -f
RewriteRule (.*) $1.html [L]
Ahora, la condición de prueba es probar la misma ruta del sistema de archivos en la que se reescribirá la solicitud (si tiene éxito).
Una solicitud /contact/
, /contact/blah
o /contact123/blah
todas ahora, dan como resultado un 404 como se esperaba.
Tenga en cuenta que no es necesario que la barra invertida escape del punto literal en RewriteCond
TestString ya que no es una expresión regular.
Puntos menores ... los anclajes ^
y son innecesarios ya que la expresión regular es codiciosa por defecto (¿aunque a algunos usuarios todavía parece gustarles por su legibilidad ?). También debe incluir la marca ( ) en el . Si bien esto no es necesario si esta es la única (o última) regla en el archivo, si debe agregar más reglas más adelante, probablemente lo sea (y tener que recordar modificar las reglas existentes de esta manera es propenso a errores).$
^(.*)$
L
last
RewriteRule
.htaccess