Como usar mod_rewrite de verdade

Rá! Hoje quero falar sobre mod_rewrite. Um módulo para o apache que permite reescrever URL’s. Ele é muito útil em vários sentidos: aumenta a o page rank em buscadores, torna URL’s limpas e inteligíveis, fazendo com que o usuário consiga lembrá-las muito mais rápida e facilmente.

Porém, como todos os outros artefatos que estão por aí, pode complicar ainda mais a vida de um programador, caso não seja usado adequadamente.

Introdução

Como visto no post anterior, onde está descrito algumas vantagens e desvantages de se usar o mod_rewrite, neste post vamos concluir o assunto. Assumindo que você esteja usando Apache com o mod_rewrite habilitado. Também é necessário o uso de arquivos .htaccess: Arquivos ocultos que contém diretivas interpretadas pelo Apache. Para que seu .htaccess funcione, deve se certificar de que a diretiva AllowOverride All esteja nos arquivos de configuração do apache. Ela deve ficar dentro de <Directory />, mais ou menos assim:

	AllowOverride All

Basta adicionar ao seus arquivos de configuração do apache ou requisitar seu servidor que o faça.

Conhecendo as diretivas

Antes de mais nada, vamos entender o que o mod_rewrite faz e é capaz de fazer, conhecendo seus parâmetros de configurações.

RewriteEngine on|off

Habilita(on) ou desabilita(off) o uso do módulo mod_rewrite.

RewriteOptions [inherit] [MaxRedirects=X]

Adiciona uma ou as duas opções.

inherit

Herda os parâmetros dos diretórios pai.

MaxRedirects=X

Ao invés de permitir o ‘loop infinito’ de redirecionamento, limita para X vezes. Caso, X seja alcançado, é disparado erro 500 (Internal Error).

RewriteLog /caminho/para/arquivo.log

Escreve o log dos eventos ocorridos no arquivo especificado.

RewriteLogLevel Level

Level inicia em 0 (desabilitado) e vai até 9 (números superiores podem ser utilizados, mas terão o mesmo efeito: irão logar quase tudo! Use somente para debug, pois vai diminuir a performance do apache. Bom para testes/debug, não para produção.)

RewriteBase /dir

Especifica /dir como a base para a procura do arquivo pelo Apache. (Mais explicações adiante)

RewriteCond

Responsável pela condições (a.k.a. IF’s). Pode-se utilizar inúmeros testes antes de especificar uma regra, sendo que a mesma só será executada ao passar por todos os testes com sucesso. Exemplos:

# Se o host for 123.45.67.89
RewriteCond %{REMOTE_ADDR} ^123.45.67.89$ [OR]
# ou for 98.76.54.32
RewriteCond %{REMOTE_ADDR} ^98.76.54.32
# A regra será a seguinte...

Podem ser usadas uma série de variáveis, entre elas:

RewriteRule

É aqui que realmente acontece a reestruturação da URL. Aqui é onde é dito o que deve ser entrepretado como o quê. Se o Padrão casar, o apache interpreta como se fosse o Caminho.

Por exemplo:

RewriteRule ^/(about|aboutme|sobre|sobremim|eu|minhavida)$ eu.html [L,NC]

Referências interessantes:

Exemplos de uso

Agora, com alguma noção sobre o mod_rewrite, vamos utilizá-los. Com alguns exemplos do que é possível. A idéia é que você descubra um jeito próprio e que funcione PARA VOCÊ. Esta é o tipo da coisa que não adianta fazer porque alguém disse que funciona, deve seguir os seus padrões e cobrir suas necessidades.

Exemplo 1: Organizando seu site no .htaccess

Convenhamos: uma notícia sobre um acidente não irá mudar. Depois de submetida para um site de notícias, dificilmente ela irá ser modificada. Ao menos que tenha passado desapercebido um erro de português ou uma estatística imprecisa, uma notícia não será modificada. Portando, é possível manter um cache da notícia em um arquivo .html, evitando consultas desnecessárias a bancos de dados ou outras fontes. Mas só porque ela está em cache, não significa que ela deva aparecer com o .html no fim ou .htm ou seja lá o que for.

RewriteEngine On
RewriteBase /
# Se não foi requerido um arquivo vazio (-s)
RewriteCond %{REQUEST_FILENAME} !-s [OR]
# Se não foi requerido um link simbólico (-l)
RewriteCond %{REQUEST_FILENAME} !-l [OR]
# ou um arquivo (-f)
RewriteCond %{REQUEST_FILENAME} !-f [OR]
# ou um diretório (-d)
RewriteCond %{REQUEST_FILENAME} !-d
# Aplique a seguinte regra:
# Se for notícia cacheada, chama o cache
RewriteRule ^ler/([A-Za-z0-9_-]+)$ /noticias/cache/$1.html [L]
# Se for para buscar por noticias
RewriteRule ^buscar/([A-Za-z0-9_-]+) /noticias/busca.php?termo=$1&area=noticias [L]
# Feeds
RewriteRule ^feeds$ feeds.php?area=noticias
# Feeds para a categoria X
RewriteRule ^feeds/(.*)$ feeds.php?area=noticias&categoria=$1

e assim por diante. Podemos estabelecer regras, inclusive, para subdomínios.

RewriteCond %{HTTP_HOST} ^en\.example\.com$
RewriteRule ^(.*)$ /news/english/index.php?q=$1 [L]
 
RewriteCond %{HTTP_HOST} ^br\.example\.com$
RewriteRule ^(.*)$ /news/brazilian/index.php?q=$1 [L]

uma outra sugestão:

RewriteCond %{HTTP_HOST} ^en\.example\.com$
RewriteRule ^(.*)$ /news/index.php?lang=en&q=$1 [L]
 
RewriteCond %{HTTP_HOST} ^br\.example\.com$
RewriteRule ^(.*)$ /news/index.php?lang=pt-br&q=$1 [L]

Exemplo 2: Listas de traduções

A questão de usar uma única variável que receberá o valor da url, funciona melhor para este segundo caso:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f [OR]
RewriteCond %{REQUEST_FILENAME} !-s [OR]
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L,NC]

Imagine que seu cliente queira criar páginas novas, de ‘política de privacidade’, ’sobre a empresa’, seja lá qual for o assunto da página. Ele vai querer acessá-la de forma rápida e acessível. Para tanto, index.php?area=view_page&page=4354 não parece agradar muita gente. Não seria interessante deixar que o próprio cliente crie seu padrão de URL? Talvez /politicas ou /empresa.

Existe uma forma interessante de se fazer isso: usando uma lista de expressões regulares e seus arquivos originais, os quais serão usados caso as expressões regulares casarem. Por exemplo, nos casos acima, apenas com o código abaixo no .htaccess, já poderíamos fazer algo:

A idéia é usar a REQUEST_URI (no php $_SERVER['REQUEST_URI']). Ela retornará, por exemplo, /ler/minha_noticia_bombastica que casará com ^/ler/([a-z0-9_-])$ e por consequência, irá chamar a noticias.php. Agora, como pegar o ‘minha_noticia_bombastica’ ? Fácil! Basta apenas utilizarmos os grupos das expressões regulares de forma apropriada. Usando o exemplo do php, com preg_match podemos resolver isso!

$urlPatterns = Array(
	'/^ler\/([a-z0-9_-]+)$/i' => 'noticias.php'
);
foreach ($urlPatterns as $pattern => $fileName)
	if (preg_match($pattern, $_SERVER['REQUEST_URI'], $vars)) {
		$_GET = $vars;
		include $fileName;
		break;
	}
}

Pronto! Alguns usuários podem ter notado, mas $vars irá ser uma sequência numérica dos grupos casados:

0 => /ler/minha_noticia_bombastica, 1 => ‘minha_noticia_bombastica’

Para resolver isso, podemos dar nomes aos grupos, o que seria extremamente normal para o programador (que utilizaria a variável $_GET normalmente) e bonito aos olhos do cliente. Para tanto, modificamos a nossa $urlPatterns:

	$urlPatterns = Array(
		'/ler\/(?<news -slug>[a-z0-9_-]+)$/i' => 'noticias.php'
	);</news>

Assim, poderemos acessar $_GET['news-slug']. Como fica mais difícil do usuário modificar nomes de variáveis vindos da URL, se torna uma solução segura. Podemos ter certeza de que $_GET['news-slug'] não conterá aspas, caracateres hexadecimais ou qualquer outro tipo de código malicioso. Pois se tivesse, não passaria no teste da expressão regular. E também nos certificamos de que aquele arquivo (noticias.php) só esteja sendo acessado, caso nossa index.php o esteja incluindo. Porque o usuário não conseguirá acessar de fora.

Neste caso, nós estipulamos os valores na $urlPatterns, mas ainda se pode pegar esses valores do banco de dados, por exemplo. Uma lista de ‘tradução’ para a url. Funciona muito bem, principalmente com páginas estáticas ou fixas. Ao invés do cidadão digitar index.php?area=pages&page_id=4232, ele estará acessando /sobre-a-empresa. Para usuários provenientes de sites de busca, isto é muito importante. Lhe dá muito mais segurança e você garante o seu clique. Ele vai direto ao ponto.

Posts Relacionados

Por favor, se você deseja opinar, criticar ou até mesmo mandar uma receita de bolo, deixe um comentário! Ou fique atento aos feeds.

Comentários

Que ótimo artigo!

Acabei de ficar ‘irritado’ rsrsrs

Enquanto posts sem sentido algum, pela web, estão cheios de comentários e agradecimentos, posts como esse estão quase que na escuridão… Uma pena.

Ótimo post! Conferindo o blog já!

Abraços!

Que bom que lhe serviu! É uma satisfação saber que, de fato, alguém pôde aproveitar/tirar algo dos meus posts.

Obrigado pelo elogio!

Muito bom o post!

Encontro muita coisa sobre rewrite na internet.. mas seu artigo prova que um bom conteúdo tem que estar aliado a forma de se escrever.

só uma dúvida..
por exemplo… quero q as imagens,css,js que são chamados pelo html sejam direcionados a um certo diretório

html-> src=”/images/imagem.png”
rewrite -> /public/beck/images/imagem.png

seria possivel?

Obrigado pelo elogio!

Não sei se entendi direito. O Apache apenas consegue enxergar arquivos que estiverem dentro do teu DocumentRoot ou do DocumentRoot de outro virtualhost. O que é possível fazer, é o seguinte:

RewriteRule ^/images/(.*)\.(jpg|png|jpeg|gif)$ /public/beck/images/$1.$2

Veja se funciona!

Parabéns pelo conteúdo, como disse o @anderson lamentável este post estar na escuridão.
tanks,

tinha me esquecido de passar por aqui pra agradecer… a reginha pra imnagens funcionou blz…

valeu!

EXCELENTE!!!
Brow cada dia que passa vc c supera com a simplicidade e qualidade de seus POSTS…
F1 é nois queirois ^^

ola, Gustavo

eu por ex quero criar o subdominio: videos.meusite.com/

mas quando eu uso esse codigo abaixo:

RewriteEngine On
RewriteCond %{HTTP_HOST} ^(www\.)?videos\.meusite\.com$ [NC]
RewriteCond %{REQUEST_URI} !^/videos/
RewriteRule ^(.*) /videossexo/$1 [L,R]

aparece no navegador: videos.meusite.com/videos

como elimino esse “/videos” para somente aparecer videos.meusite.com?

email: guganet@hotmail.com

agradeço! abs!

gustavo

Muito bom post. Parabéns.
Só estou com um probleminha, fiz o Exemplo 2: Listas de traduções e quando coloco o .htaccess no servidor o site nem abre mais da erro 500 Internal server erro. O que seria isso!? Será que tem algo desabilitado no apache? Pelo phpinfo consigo ver se está tudo que preciso habilitado no servidor?

Valeu. à todos, se puder responder agradeço de verdade, pois estou precisando muito, e esse post esta espetacular.

o Erro 500, internal error, é geralmente um erro ocorrido decorrente à má configuração do .htaccess.

Antes de mais nada, eu entraria em contato com o seu servidor para verificar se ele permite mod_rewrite e se o seu domínio possui AllowOverride All.

Caso não resolva, então poste exatamente o .htaccess do seu servidor pra gente dar uma olhada ;P

muito bom o artigo parabens..

Estou com um problema grave com um subdominio em joomla!

http://www.nomedosite.com/subdominio acessa o sub e os links perfeitamente.

subdominio.nomedosite.com acessa o sub e os links ao acessar da erro 500!

Agradeço desde já!

Antonio Bruno, você pode nos mostrar o .htaccess ?, como falei antes, erro 500 é geralmente decorrente a algum problema na configuração do .htaccess

[...] mais informações ver o blog do Gustavo Dutra que foi o cara que me ajudou nesse .htaccess [...]

Deixe um comentário

(obrigatório)

(obrigatório)