Cabeceras: la función header()

La función header()

Como se explica en la lección sobre el protocolo HTPP, cuando un servidor envía una página web al navegador, no sólo envía la página web, sino también información adicional (el estado y los campos de cabecera). Tanto el estado como los campos de cabecera se envían antes de la página web.

Normalmente, un programa PHP sólo genera la página web y es el servidor el que genera automáticamente la información de estado y los campos de cabecera y los envía antes de enviar el contenido generado por el programa. Pero un programa PHP también puede generar la información de estado y los campos de cabecera, mediante la función header().


El ejemplo siguiente muestra un ejemplo de cabecera HTTP, concretamente una redirección. En el ejemplo siguiente, la página 1 contiene un enlace a la página 2. Pero como la página genera una cabecera HTTP de redirección a la página 3, al hacer clic en el enlace se acaba en la página 3.

<p>Esta es la página 1.</p>

<p><a href="cabeceras-header-1-2.php">Enlace a página 2 (que redirige a la página 3)</a></p>
<?php
header("Location:cabeceras-header-1-3.php");
?>
<p>Esta es la página 3.</p>

<p><a href="cabeceras-header-1-1.php">Volver a la página 1</a></p>
ERROR (no puede mostrarse el objeto)
Enlace a ejemplo

Es muy importante tener en cuenta que la función header() no debe utilizarse una vez se ha generado contenido HTML (por ejemplo, con un print). El motivo es que en cuanto un programa genera contenido HTML, el servidor genera automáticamente la información de estado y los campos de cabecera y a continuación envía el contenido generado. Si después el programa contiene una instrucción header(), se produce un error porque las cabeceras ya se han enviado, como muestran los dos ejemplos siguientes.

En el primer ejemplo, se llama a la función header() después de un print, lo que provoca un aviso de error.

<?php
print "<p>Intento fallido de redirección</p>\n";
header("Location:http://www.example.com");
?>

Intento fallido de redirección

Warning: Cannot modify header information - headers already sent by (output started at ejemplo.php:2) in ejemplo.php on line 3

En el segundo ejemplo, se llama a la función header()en un programa que ya ha generado contenido antes del fragmento PHP, lo que provoca un aviso de error:

<p>Intento fallido de redirección</p>
<?php
header("Location:http://www.example.com");
?>

Intento fallido de redirección

Warning: Cannot modify header information - headers already sent by (output started at ejemplo.php:2) in ejemplo.php on line 3

Por ello, hay que tener mucho cuidado en no generar ningún tipo de salida antes de utilizar la la función header() ni en el programa, ni en las bibliotecas a las que se llame en el programa.

Hay que tener en cuenta que una simple línea en blanco antes del bloque PHP sería suficiente para impedir el envío de la cabecera:


<?php
header("Location:http://www.example.com");
?>

Warning: Cannot modify header information - headers already sent by (output started at ejemplo.php:2) in ejemplo.php on line 3

Nota: Otra situación que puede provocar el fallo de la función header() es cuando el programa contiene un error antes, ya que PHP generará un aviso de error que es también parte de la salida del programa y eso provocaría el fallo de la función header() posterior. Esta situación suele darse al desarrollar el programa, ya que normalmente los servidores de desarrollo están configurados para mostrar el mayor número posible de avisos de error, pero desaparece en los servidores de producción, que se suelen configurar para no enviar al usuario ningún mensaje de error.

Buffer de salida

El buffer de salida del intérprete PHP se crea mediante la directiva output_buffering. El buffer de salida almacena temporalmente la salida del programa, que no se envía al navegador hasta que se complete la página o se llene el buffer.

Si el servidor está utilizando un buffer de salida (lo que es muy habitual), el error de utilizar la función header() después de crear contenido HTML que se ha comentado en el apartado anterior puede no llegar a producirse.

Si cuando el programa llega a la instrucción header(), el contenido HTML generado anteriormente ha cabido en el buffer de salida y todavía no se ha enviado al navegador, el servidor puede todavía modificar la información de cabecera y la función header() no daría error. Pero si el contenido HTML hubiera sido mayor que el tamaño del buffer, el error sí que se produciría porque las cabeceras ya se habrían enviado.

El uso de un buffer de salida puede así enmascarar en ciertos casos errores de programación como los de los ejemplos anteriores. Por ese motivos, en la lección de configuración de XAMPP se recomienda desactivar el buffer de salida al desarrollar el programa, de manera que al probar el programa detectemos este tipo de errores.


Los ejemplos siguientes muestran la influencia del tamaño del buffer. En estos ejemplos se utilizan las funciones ob_start() y ob_end_flush(), que permiten gestionar directamente el buffer de salida independientemente de la configuración del servidor. Estos buffers son temporales y desaparecen cuando se termina de ejecutar la página.

Marca de orden de bytes (BOM) de UTF-8

Otra situación que puede causar problemas en los programas que incluyen la función header() es la marca de orden de bytes (BOM) de los archivos UTF-8. Como en el caso anterior, la utilización de un buffer de salida puede enmascarar este problema.

La marca de orden de bytes son los primeros caracteres de un fichero UTF. Estos caracteres indican:

En el caso de los archivos UTF-8 realmente no se necesita marca de orden de bytes porque el orden de los bytes debe ser siempre el orden natural, pero el caso es que existe una marca de orden de bytes, los tres caracteres EF BB BF (que en ASCII corresponden a los caracteres ). En el caso de UTF-8 la marca de orden de bytes es opcional y puede servir para identificar un archivo como UTF-8 y distinguirlo de otros formatos.

Los editores (como Eclipse for PHP developers) no muestran la marca de orden de bytes y suelen respetarlos al guardar de nuevo al archivo. Para ver esos caracteres, se puede utilizar un editor hexadecimal, que muestra todos los caracteres sin excepción.

Las imágenes siguientes muestran los valores hexadecimales de dos archivos UTF-8 (con y sin BOM ), utilizando el editor Notepad++ con el plug-in HEX-Editor:

archivo UTF-8 sin BOM

archivo UTF-8 con BOM

Esta marca de orden de bytes puede causar problemas si PHP no reconoce la marca de orden de bytes como un identificador de juego de caracteres, sino como caracteres situados antes de un framento PHP que se deben enviar al navegador. El comportamiento de PHP depende de las opciones elegidas al compilar el intérprete de PHP, por lo que la única solución viable es asegurarse de que los archivos no tienen BOM (como recomiendan las guías de estilo más populares).


Los ejemplos siguientes muestran la influencia del BOM cuando no hay buffer de salida.


Notepad++ permite quitar la marca de orden de bytes de un archivo php mediante el menú Codificación > Convertir a UTF-8 sin BOM:

Quitar BOM con Notepad++

Para eliminar las marcas de orden de bytes de un conjunto grande de archivos php, se puede utilizar este script de Linux:

#! /bin/bash

find ./ -name "*.php" -type f | while read file
do
  if [[ -f $file && `head -c 3 $file` == $'\xef\xbb\xbf' ]]; then
      # si el fichero existe y tiene BOM UTF-8
      mv $file $file.bak
      tail -c +4 $file.bak > $file
      echo "BOM eliminado en $file"
  fi
done

Redirecciones: header("Location:...")

La función header() se puede utilizar para redirigir automáticamente a otra página, enviando como argumento la cadena Location: seguida de la dirección absoluta o relativa de la página a la que queremos redirigir.

<?php
header("Location:http://www.example.com/");
?>

Si además de redirigir a una página, se quieren enviar controles a dicha página, se pueden añadir a la cadena separando los controles con el carácter &.

Nota: En este caso no se debe utilizar la entidad de carácter &amp;

<?php
header("Location:http://www.example.com?nombre=Pepito&edad=25");
?>

Si el valor a enviar contiene espacios, se pueden escribir caracteres + o espacios para separar las palabras:

<?php
header("Location:http://www.example.com?nombre=Pepito+Conejo&edad=25");
?>
<?php
header("Location:http://www.example.com?nombre=Pepito Conejo&edad=25");
?>

Se puede escribir "location" o "Location" y entre los dos puntos y la dirección puede haber espacios en blanco.

<?php
header("location: http://www.example.com/");
?>

Resto del programa tras la redirección

La ejecución de un programa no se detiene al encontrar una redirección, sino que PHP ejecuta el programa hasta el final. Dependiendo de las instrucciones que haya tras la dirección, el resultado puede ser relevante o no.


Si tras una redirección no queremos que se ejecute el resto del programa, tenemos dos opciones:


Pero hay que tener en cuenta que exit detiene no sólo la ejecución del programa, sino también la de los programas que hubieran llamado a esos programas, como se muestra en los ejemplos siguientes:

Crear con PHP otros tipos de archivos

PHP puede generar archivos de cualquier tipo, pero para que el navegador reconozca el tipo de archivo, en la cebecera del archivo se debe enviar el tipo MIME correspondiente. Existen muchos tipos MIME (véase lista de tipos MIME en MDN), por ejemplo:

extensión del archivo tipo de archivo tipo MIME
.html página web HTML text/html
.css hoja de estilo CSS text/css
.js JavaScript application/js
.svg SVG image/svg+xml

El tipo MIME se indica en la cabecera, por lo que un programa PHP puede declarar su tipo MIME mediante la función header(), enviando como argumento la cadena Content-type: seguida del tipo MIME correspondiente.

Crear imágenes SVG

El programa siguiente crea las etiquetas correspondientes a una imagen SVG, concretamente a un cuadrado de color aleatorio. Si escribimos directmente la url de ese programa en el navegador, el navegador muestra el contenido esperado.

Pero aunque el navegador consiga mostrar el cuadrado, realmente no estamos haciendo bien las cosas. Una imagen SVG debería entregarse al navegador con el tipo MIME image/svg+xml. En este caso, el programa no declara ningún tipo MIME, así que el servidor lo entrega con el tipo MIME predeterminado de los programas PHP, el tipo MIME text/html (es decir, como página web). Aunque estrictamente hablando el documento no es una página web válida porque le faltan las etiquetas básicas como <html>, <head>, <body>, etc. los navegadores aceptan las páginas invalidas e intentan interpretar el contenido de la mejor manera posible. En este caso, se interpreta la etiqueta <svg> como si estuviera en una página web y el resultado es el esperado.

<?php
$color = rand(0, 360);
print "<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" \n"
    . "    width=\"100\" height=\"100\" viewBox=\"-5 -5 100 100\">\n";
print "  <rect fill=\"hsl($color, 100%, 70%)\" stroke=\"hsl($color, 100%, 30%)\" stroke-width=\"4\" "
    . "x=\"0\" y=\"0\" width=\"90\" height=\"90\" />\n";
print "</svg>";
?>
ERROR (no puede mostrarse el objeto)
Enlace a ejemplo

Pero en otras circunstancias, este mismo programa no daría el resultado esperado. Como muestra el siguiente ejemplo, si el programa se llama en una etiqueta <img>, el navegador no puede mostrar la imagen. El problema es que en una etiqueta el navegador tiene que recibir obligatoriamente algo con un tipo MIME de imagen. Como en el ejemplo anterior, el servidor serviría el documento con el tipo MIME text/html, y en este caso los navegadores no intentan siquiera mostrar el contenido.

<p><img src="svg-sin-mime.php" alt="ERROR No puedo mostrar la imagen" /></p>
<?php
$color = rand(0, 360);
print "<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" \n"
    . "    width=\"100\" height=\"100\" viewBox=\"-5 -5 100 100\">\n";
print "  <rect fill=\"hsl($color, 100%, 70%)\" stroke=\"hsl($color, 100%, 30%)\" stroke-width=\"4\" "
    . "x=\"0\" y=\"0\" width=\"90\" height=\"90\" />\n";
print "</svg>";
?>
Incorrecto
ERROR (no puede mostrarse el objeto)
Enlace a ejemplo

Para que el navegador muestre siempre correctamente la imagen, el programa debe generar una cabecera que declare el tipo MIME de imagen image/svg+xml, como muestra el siguiente ejemplo.

<p><img src="svg-con-mime.php" alt="ERROR No puedo mostrar la imagen" /></p>
<?php
header("Content-type: image/svg+xml");
$color = rand(0, 360);
print "<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" \n"
    . "    width=\"100\" height=\"100\" viewBox=\"-5 -5 100 100\">\n";
print "  <rect fill=\"hsl($color, 100%, 70%)\" stroke=\"hsl($color, 100%, 30%)\" stroke-width=\"4\" "
    . "x=\"0\" y=\"0\" width=\"90\" height=\"90\" />\n";
print "</svg>";
?>
Correcto
ERROR (no puede mostrarse el objeto)
Enlace a ejemplo