Come restituire HTTP Status Code specifici con Apache e .htaccess

Sia di Apache e .htaccess, sia di http status code ho già avuto modo di parlare a più riprese su questo blog, in lungo ed in largo. Vorrei però tornare nuovamente sull’argomento proponendo un riepilogo di come restituire i principali status code via .htaccess cercando di uscire dalla banalità dei soliti e ben noti Redirect 301 e Redirect 302.

Prendete questo articolo come una sorta di cheatsheet, una raccolta di esempi per restituire http status code con Apache e .htaccess. L’articolo non è cortissimo, ho deciso di dividerlo in 4 pagine. Il link per la navigazione delle pagine è disponibile al fondo di ogni pagina, prima dei commenti.

Requisiti e Premesse

Poiché il focus di questo articolo è squisitamente tecnico, partirò dal presupposto che conosciate almeno le basi del funzionamento di Apache e le caratteristiche essenziali del file .htaccess.
Inoltre, non mi soffermerò a dettagliare il significato di ogni status code, delegando invece alla documentazione ufficiale eventuali approfondimenti.

Classificazione degli status code HTTP

Gli HTTP Status code sono normalmente classificati per tipo di risposta e si suddividono in:

  1. Informational
  2. Successful
  3. Redirection
  4. Client Error
  5. Server Error

Il quarto e quinto insieme possono essere identificati in un colpo solo come Error Responses. Ecco lo schema riassuntivo, presente nella definizione del file sorgente di Apache che indica anche, per ogni classe di status code, il suo range di valori.

/\*\* is the status code informational */
#define ap\_is\_HTTP_INFO(x)     (((x) >= 100)&&((x) < 200))
/\*\* is the status code OK ?*/
#define ap\_is\_HTTP_SUCCESS(x)    (((x) >= 200)&&((x) < 300))
/\*\* is the status code a redirect */
#define ap\_is\_HTTP_REDIRECT(x)   (((x) >= 300)&&((x) < 400))
/\*\* is the status code a error (client or server) */
#define ap\_is\_HTTP_ERROR(x)    (((x) >= 400)&&((x) < 600))
/\*\* is the status code a client error  */
#define ap\_is\_HTTP\_CLIENT\_ERROR(x) (((x) >= 400)&&((x) < 500))
/\*\* is the status code a server error  */
#define ap\_is\_HTTP\_SERVER\_ERROR(x) (((x) >= 500)&&((x) < 600))
/\*\* is the status code a (potentially) valid response code?  */
#define ap\_is\_HTTP\_VALID\_RESPONSE(x) (((x) >= 100)&&((x) < 600))

Al fine del nostro articolo gli insiemi di status code più interessanti sono i redirect e gli errori. Gli altri casi denotano richieste processate secondo il normale flusso previsto dal web server e difficilmente ci troveremo ad impostare a mano uno di questi valori.

HTTP Redirect

Come anticipato in apertura, non mi dilungherò troppo per quanto riguarda gli status code di reindirizzamento. I due più importanti sono l’HTTP 301 e l’HTTP 302. Entrambi possono essere impostati utilizzando sia il mod_alias, sia il cugino mod_rewrite.

Redirect con mod_alias

Nel primo caso è sufficiente indicare il tipo di redirect dopo al comando di reindirizzamento, come segue.

Redirect temp /old http://site.com/new
Redirect permanent /old http://site.com/new

Entrambe le direttive accettano anche il rispettivo status code.

Redirect 302 /old http://site.com/new
Redirect 301 /old http://site.com/new

Nel caso in cui né lo status code né la sua descrizione sia presente, Apache imposta il tipo di redirect a 302.

Altrettanto interessante, mod_alias supporta anche lo status di reindirizzamento seeother, rappresentato dal valore 303.

Redirect 303 /old http://site.com/new
Redirect seeother /old http://site.com/new

Tutte le direttive sopra descritte sono ugualmente supportate dal comando RedirectMatch.

Redirect 301        /old/(.*) http://site.com/new/$1
Redirect permanent  /old/(.*) http://site.com/new/$1
Redirect 302        /old/(.*) http://site.com/new/$1
Redirect temp       /old/(.*) http://site.com/new/$1
Redirect 303        /old/(.*) http://site.com/new/$1
Redirect seeother   /old/(.*) http://site.com/new/$1

mod_alias mette anche a disposizione alcune scorciatoie per i due tipi di redirect più diffusi.

Redirect 301      /old http://site.com/new
RedirectPermanent /old http://site.com/new

Redirect 302      /old http://site.com/new
RedirectTemp      /old http://site.com/new

Redirect con mod_rewrite

La sintassi per eseguire un redirect con mod_rewrite è altrettanto semplice. E’ sufficiente scrivere una regola di rewrite ed appendere al fondo il flag [R=XXX] sostituendo XXX con lo status code desiderato.

RewriteRule       /old http://site.com/new
RewriteRule       /old http://site.com/new
RewriteRule       /old/(.*) http://site.com/new/$1 [R=301]
RewriteRule       /old/(.*) http://site.com/new/$1 [R=302]

A differenza di mod_alias, se il flag è omesso la richiesta viene riscritta e non reindirizzata.

HTTP Client Error

L’insieme dei client error, identificato dal range >= 400 && < 500, è probabilmente quello con maggiori applicazioni pratiche ed anche il più interessante.

HTTP 404

Cominciamo con il più diffuso dei codici di errore, l’HTTP 404. Il modo più efficace per restituirlo… è cancellare il contenuto dell’indirizzo. E’ difficile che vi troviate a dover restituire un errore HTTP 404 via Apache, ma se questo fosse il vostro caso allora una soluzione è creare una pagina dinamica, ad esempio in PHP, e riscrivere l’indirizzo per puntare a quella pagina.

Ecco il contenuto del file 404.php:

<?php
header("HTTP/1.0 404 Not Found");

Ed ecco come riscrivere la pagina old.php affinché ritorni un errore 404.

RewriteRule ^old\.php$ 404.php [L]

HTTP 401

Anche per impostare un errore HTTP 401 possiamo affidarci al modulo mod_rewrite ed a una pagina personalizzata, proprio come descritto per l’errore HTTP 404.

E’ anche possibile impostare una basic auth per il percorso che intendiamo proteggere. In caso l’utente non fornisca le credenziali corrette, Apache restituirà in automatico un errore 401.

HTTP 403

Come per i precedenti, anche per l’HTTP 403 utilizziamo mod_rewrite ma questa volta Apache ci mette a disposizione lo speciale flag [F].

RewriteRule ^folder/(.*)$ - [F,L]

Il primo parametro contiene il path da proteggere, il secondo è invece un semplice trattino -. Si utilizza quando non si desidera riscrivere path ma solo sfruttare RewriteRule per appendere dei flag alla request.

L’alternativa è utilizzare le direttive di raggruppamento Files, FilesMatch, Directory, DirectoryMatch e Location per modificare le impostazioni di accesso attraverso i comandi Order e Deny. L’esempio seguente dimostra come restituire un errore 403 quando l’utente tenta di accedere ad una cartella speciale di SVN.

<DirectoryMatch "^/.*/\.svn/">
  Order allow,deny
  Deny from all
  Satisfy All
</DirectoryMatch>

HTTP 410

L’ultimo status della categoria client error che vale la pena considerare è lo status HTTP 410. All’argomento ho dedicato un post speciale qualche giorno fa, intitolato Serve a qualcosa lo status HTTP 410?.

Anche in questo caso, possiamo procedere sia con mod_alias, sia con mod_rewrite.
Ad esempio, ecco come possiamo indicare che il feed atom.xml del nostro blog non è più disponibile.

# le seguenti righe sono equivalenti nel risultato
Redirect 410  /blog/atom.xml
Redirect gone /blog/atom.xml
RewriteRule   ^blog/atom.xml$ - [G,L]

Server Error

Perché mai dovremmo voler restituire uno status code che identifica un errore del server? Non bastano da soli i famosi errori 500 causati di norma dai bug senza che ci mettiamo del nostro per restituire questo genere di errori?

Un’esempio eccellente è possibile ritrovarlo in un post di Maurizio, con tanto di conferma ufficiale da parte di Google. In breve, se state eseguendo operazioni di manutenzione sul server può essere utile restituire temporaneamente un errore 503.

Proprio da questo caso vorrei partire con due esempi. Il primo, ripreso direttamente dal blog di Maurizio, dimostra come è possibile utilizzare la strategia prima descritta per 404 e 401 per ottenere uno status code 503 per qualsiasi pagina del sito.

RewriteCond %{REQUEST_URI} !^/errors/503/index\.php [NC]
RewriteRule .* /503.php
<?php
header('HTTP/1.1 503 Service Temporarily Unavailable');
header('Retry-After: 3600');

Il secondo esempio è invece un estratto da una regola installata sui nostri server di produzione necessaria al funzionamento di Capistrano per Ruby on Rails. In questo caso, la sintassi è più complessa ma dimostra come ottenere lo stesso risultato con il solo uso di Apache senza eventuali file php esterni.

# Maintenance Page
# Handle the HTTP 503 status code with a special page
# and send Retry-After header in case of maintenance.
ErrorDocument 503 /errors/503.html

<LocationMatch "/errors/503\.html">
  Header set Retry-After "3600"
</LocationMatch>

# for the maintenance time
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !503\.html
RewriteRule ^.*$ /errors/503.html [R=503,L]

In conclusione

Conoscere ed utilizzare gli HTTP status code nel modo corretto è un requisito imprescindibile per chi lavora a stretto contatto con pagine web, web server ma soprattutto client come i motori di ricerca.
Se fino ad oggi non avevato ancora approfondito l’argomento, questa è la volta buona per sgranocchiare un po’ di documentazione.