Esta é uma pergunta de acompanhamento
- O meu conjunto de regras pretendido
.htaccess
foi globalmente alcançado . - Veja esse arquivo em sua forma atual citada abaixo.
Problema restante: um conjunto de regras ainda não funciona
- Objetivo: Normalizar
request(\.htm|\.html)
para apenas o bonitorequest
:
RewriteCond %{REQUEST_URI} ".+\.html?$"
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f
RewriteRule (.*)/([^/]+)\.html?$ $1/$2 [R=301]
- RegEx testa ok em: https://regex101.com/r/4rTEVk/3
Comportamento observado
- A solicitação
/hello
serviu o subjacente/hello.html
.- Isto é desejado. Definido em regra
2c) DirectoryIndex features implemented in mod_rewrite
.
- Isto é desejado. Definido em regra
- A solicitação
/hello.html
serviu como está.- Isto é indesejado.
- Porque servir o mesmo arquivo com e sem sufixo significa conteúdo duplicado e não uniformidade. O que eu quero evitar.
- Pois
/hello.htm(l)
eu queria um redirecionamento HTTP 301 imposto apenas para/hello
.
Desafio
- A sintaxe para isso é aparentemente fácil, como mostra o trecho acima.
- Mas a interação correta com os outros conjuntos de regras não é. Especialmente com regras
2a) Serve file as requested
que ainda devem funcionar em todos os outros casos.
.htaccess completo
## My IA technical concept: All major things work, except one detail.
#
# 1a) Security settings.
# 1b) Explicit requests to index.php CMS router --> Served as-is.
# 2) Static page overlay into CMS namespace
# 2a) /hello — File without file extension exists. --> Served as-is.
# 2b) /hello.html - File with .html extension exists.
# - Serve in canonical form (=without extension) so at: /hello
# - Doesn't work yet. This is where I need help.
# 2c) /hello/index.(htm|html|php) — Folder of that name with index file exists.
# - Note: /hello/ directory listing is explicitly forbidden in Security section (1a)
# - DirectoryIndex rules cannot "fail gracefully", hence recreated as rules in mod_rewrite. Works fine.
# 3) CMS page exists as cached file --> Cached page gets served at beautiful URL.
# 4) /index.php — If nothing of the above matched hand over to CMS index.php (e.g. Wordpress)
## 1a) Security
### Responding as if those files don't exist
RedirectMatch 404 /\.gitignore
### Directory listing OFF
Options -Indexes
### Valid index files
DirectoryIndex index.html index.htm index.php
## 1b) If CMS router is directly requested, serve as-is
RewriteEngine On
RewriteRule ^index\.php$ - [L]
### 2a) Serve file as requested
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule . - [L]
### 2b) Except if it has a .htm or .html suffix
# - For those we will redirect to the canonical URL with the file extension stripped. --> Not yet working!
# - Serve also filenames without extensions.
# Those gets served without a content-type (MIME type). --> Works.
# No problem for those few rare cases as parser of most web browsers detects most important types such as HTML also without a "Content-Type:" header.
# For "/request" check if there's a corresponding "/request.html" and if serve this under "/request"
#### Code block which doesn't work
# - RegEx itself is OK as tested at: https://regex101.com/r/4rTEVk/3
# - Must be a ruleset conflict or order problem
# RewriteCond %{REQUEST_URI} ".+\.html?$"
# RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f
# RewriteRule (.*)/([^/]+)\.html?$ $1/$2 [R=301]
#### So in the meanthile I simply serve files with a .htm(l) extension as-is
RewriteCond %{DOCUMENT_ROOT}/$1.html -f
RewriteRule ^([^.]*[^/])$ $1.html [L]
RewriteCond %{DOCUMENT_ROOT}/$1.htm -f
RewriteRule ^([^.]*[^/])$ $1.htm [L]
## 2c) DirectoryIndex features implemented in mod_rewrite
# - index.(htm|html|php) are valid index files
# - /hello/index.(htm|html|php) will get served at /hello
# If a directory is requested, which is missing the trailing slash then append it
RewriteCond %{DOCUMENT_ROOT}/$1 -d
RewriteRule ^(.*[^/])$ /$1/ [R=301,L]
# Optimisation: If a directory is not requested then skip the next 3 rules
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . - [S=3]
# NB: Directories end in a trailing slash (enforced above)
RewriteCond %{DOCUMENT_ROOT}/$1/index.html -f
RewriteRule ^(.+)/$ $1/index.html [L]
RewriteCond %{DOCUMENT_ROOT}/$1/index.htm -f
RewriteRule ^(.+)/$ $1/index.htm [L]
RewriteCond %{DOCUMENT_ROOT}/$1/index.php -f
RewriteRule ^(.+)/$ $1/index.php [L]
## 3) CMS pages cached to files
# BEGIN W3TC Browser Cache
# ... Various rules which set Cache-Control headers per file type.
# ... Machine generated from Admin UI.
# END W3TC Browser Cache
# BEGIN W3TC Page Cache core
# ... Various redirections rules to cached page representations.
# ... Machine generated from Admin UI.
# END W3TC Page Cache core
## 4) Fallback to CMS
# Note that from the default Wordpress .htaccess two conditions (filesystem checks) are removed.
# - The first one that checks for a "file" is handled by multiple of our rulesets.
# - The second check for a "directory" was removed otherwise directories that do not contain a "DirectoryIndex" are not routed to the CMS.
# BEGIN WordPress
# The directives (lines) between "BEGIN WordPress" and "END WordPress" are
# dynamically generated, and should only be modified via WordPress filters.
# Any changes to the directives between these markers will be overwritten.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
Resposta preliminar:
2b)
Except if it has a .htm or .html suffix
nunca é alcançada devido ao2a)
Serve file as requested
fato de a primeira corresponder generosamente a qualquer arquivo!2a)
para ser efetivamenteServe filename.ext as requested if it's indeed a file and not ending in .htm(l)
.→ Atualização: A abordagem não foi suficiente por si só! ↓ Ver comentários. ↓
Meu esquema IA/URL: Funciona completamente agora
1a. Configurações de segurança.
1b. Solicitações explícitas ao
/index.php
roteador CMS -> Servidas como estão.Sobreposição de página estática no namespace CMS
/hello
— Existe um arquivo sem extensão de arquivo. -> Servido como está, sem Content-Type. -> Comportamento padrão. Nenhuma regra necessária./hello.html
- Existe arquivo com extensão .html. --> Servir HTTP 301 em/hello
(= sem extensão = forma canônica) com conteúdo de/hello.html
/hello
solicitado e/hello
não existe, mas/hello.html
existe --> Servir HTTP 200 em/hello
com conteúdo de/hello.html
/hello/index.(htm|html|php)
— Existe uma pasta com esse nome com arquivo de índice./hello/
a listagem de diretórios é explicitamente proibida na seção Segurança (1a)DirectoryIndex
as regras não podem "falhar normalmente", portanto, recriadas como regras nomod_rewrite
. Funciona bem.A página CMS existe como arquivo em cache -> A página em cache é veiculada em um lindo URL.
/index.php
— Se nada do que foi dito acima corresponder, passe para o CMS index.php (por exemplo, Wordpress)As regras ausentes 2b e 3c foram alcançadas assim
Explicação de todas as diretivas, seus sinalizadores e como evitar um loop infinito
Regra 2b: Redirecionar solicitações de arquivos HTML para seu URL canônico sem sufixo
RewriteEngine On
: Esta diretiva ativa o mecanismo de reescrita para este arquivo .htaccess, permitindo a reescrita de URL.RewriteCond %{THE_REQUEST} \s/([^/]+)\.html [NC]
: esta condição verifica se a solicitação atual inclui uma URL com o.html
sufixo. A%{THE_REQUEST}
variável contém a linha completa de solicitação HTTP enviada pelo navegador ao servidor e\s/([^/]+)\.html
é uma expressão regular que corresponde às solicitações de arquivos HTML. O[NC]
sinalizador torna a correspondência insensível a maiúsculas e minúsculas.RewriteRule ^ /%1 [R=301,L]
: esta regra corresponde a qualquer URL (^
) e o redireciona para o URL sem o.html
sufixo (/%1
). O%1
na substituição refere-se ao grupo capturado do anteriorRewriteCond
. As flags[R=301,L]
indicam um redirecionamento permanente (301) e que esta é a última regra a ser aplicada para esta requisição.[L]
flag garante que esta regra seja a última processada para uma determinada solicitação, evitando que regras subsequentes causem um loop.Regra 2c – Servir arquivos HTML em seu URL canônico sem sufixo
.html
sufixo.RewriteCond %{REQUEST_FILENAME} !-f
: esta condição verifica se o URL solicitado não mapeia diretamente para um arquivo existente no sistema de arquivos. Se o arquivo existir (!-f
), esta condição não será atendida e a regra será ignorada. Esta condição ajuda a evitar um loop, garantindo que a regra seja aplicada apenas quando o URL solicitado não corresponder a um arquivo real.RewriteCond %{REQUEST_FILENAME}.html -f
: esta condição verifica se anexar.html
ao nome do arquivo solicitado resulta em um arquivo existente no sistema de arquivos. Esta condição é crucial para a prevenção de loop porque garante que a regra de reescrita só seja aplicada se o arquivo HTML correspondente existir. Se o arquivo HTML não existir, a regra não corresponderá, evitando um loop.RewriteRule ^([^/]+)/?$ $1.html [L]
: esta regra captura a parte do URL após o nome de domínio usando o padrão^([^/]+)/?$
e anexa.html
a ele. Porém, devido às condições anteriores, esta regra só é aplicada se o arquivo solicitado não existir (!-f
) e o arquivo HTML correspondente existir (%{REQUEST_FILENAME}.html -f
). Isso evita um loop em que o servidor anexa continuamente.html
à URL, pois só aplica a regra quando necessário.RewriteCond
) na Regra 2c. Estas condições garantem que a regra de reescrita seja aplicada apenas quando necessário, evitando possíveis loops infinitos. Além disso, o[L]
sinalizador da Regra 2a garante que o redirecionamento ocorra apenas uma vez por solicitação, evitando um loop no processo de redirecionamento.Exemplos de solicitações e como elas são processadas pelas regras acima
/sand.html
/sand
./sand
é processado de acordo com a Regra 2c. O servidor reescreve internamente o URL para veicular o conteúdo de /sand.html./sand.html
é servido./sand
/sand.html
existe./sand.html
./sand.html
é servido.