Uso de reglas de reescritura de Apache en .htaccess para eliminar .html causando un error 500

Oct 25 2019

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

3 MrWhite Oct 25 2019 at 07:40

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_FILENAMEcontiene 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_FILENAMEen la segunda condición. La REQUEST_FILENAMEvariable 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/blaho /contact123/blah), entonces REQUEST_FILENAMEesencialmente se "reduce" al último segmento de ruta que se asigna a un directorio, más el "nombre de archivo" (es decir, .../contacty .../contact123respectivamente - 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 /contacty REQUEST_FILENAMEes /path/to/document-root/contact, por lo que se REQUEST_FILENAMEasigna directamente a la ruta de URL. La condición de prueba /path/to/document-root/contact.htmles 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_FILENAMEes 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 .htmlse agrega a la ruta URL capturada , es decir $1.html). El proceso se repite, se REQUEST_FILENAMEevalú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_FILENAMEvariable del servidor se convierte /path/to/document-root/contact123y /path/to/document-root/contact123.htmlno existe, por lo que no se produce ninguna reescritura en primer lugar.

"Solución"

Para "corregir" este comportamiento, debería utilizar la REQUEST_URIvariable de servidor en su lugar. Contiene la ruta URL relativa a la raíz. Agregue esto a la DOCUMENT_ROOTvariable 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/blaho /contact123/blahtodas 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).$^(.*)$LlastRewriteRule.htaccess