Manipulando o cabeçalho de resposta HTTP pelo PHP

Artigo que mostra a utilização da função header do PHP, que permite modificar o cabeçalho de resposta HTTP dos documentos gerados em PHP.

Introdução

Neste artigo, veremos como utilizar a função header do PHP e algumas aplicações práticas. Embora header seja uma função importantíssima da linguagem e também seja muito usada, nem sempre os programadores sabem seu funcionamento exato. Além disso, veremos alguns erros comuns cometidos ao usar a função.

Se você é um programador PHP iniciante e acredita que a função header sirva apenas para redirecionar o usuário de uma página para outra nos seus sites em php, então leia este artigo e entenda em detalhes o que você está fazendo.

Arquitetura Transacional (Cliente/Servidor)

Antes de apresentar o funcionamento da função header, é preciso saber como a Web funciona e, para isso, precisamos entender a arquitetura transacional na qual ela está inserida.

A arquitetura transacional é composta por clientes e servidores, que trafegam informações de forma transacional, ou seja, por troca de mensagens. Considerando o ambiente web, o cliente normalmente é o navegador web e o servidor é um computador (servidor) que hospeda um site ou sistema web. Na web os clientes e servidores utilizam um padrão para esta comunicação de mensagens, que é o HTTP (HyperText Transfer Protocol, ou Protocolo de Transferência de Hipertextos).

O HTTP especifica como devem ser as mensagens trafegadas. Então, quando você acessa um endereço pelo seu navegador (digitando na barra de endereço e solicitando "ir" ao site), ele precisa converter este endereço em uma mensagem HTTP que irá "pedir" ao site servidor uma informação de lá. Por exemplo, para acessar este artigo do blog, o navegador envia uma mensagem HTTP de requisição semelhante a isso:

GET /2012/11/manipular-cabecalho-http-via-php.html HTTP/1.1
Host: rubsphp.blogspot.com.br
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:16.0) Gecko/20100101 Firefox/16.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive

A primeira linha é importante pois especifica o tipo de mensagem (GET), o path do recurso solicitado (/2012/11/manipular-cabecalho-http-via-php.html) e a versão do protocolo (1.1). Em seguida, são informadas várias diretivas HTTP, tais como o Host do servidor (Host), a string de identificação do software que está realizando a requisição (User-Agent), os tipos de arquivos aceitos pelo user-agent (Accept), os idiomas preferenciais do user-agent (Accept-Language), os métodos de decodificação aceitos pelo user-agent (Accept-Encoding) e o status da conexão (Connection).

Ou seja, além de pedir pelo HTML do artigo, o navegador também informa algumas características dele, para que o servidor possa oferecer o recurso (arquivo HTML) da forma mais viável ou desejável pelo user-agent.

Quando o servidor recebe esta requisição, ele a processa e devolve uma mensagem de resposta HTTP, que é parecida com isso:

HTTP/1.0 200 OK
Content-Type: text/html; charset=UTF-8
Expires: Wed, 07 Nov 2012 19:11:27 GMT
Date: Wed, 07 Nov 2012 19:11:27 GMT
Cache-Control: private, max-age=0
Last-Modified: Wed, 07 Nov 2012 19:11:24 GMT
Etag: "bad9525a-f931-40ea-9d9f-41d28f3f1a8f"
Content-Encoding: gzip
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Content-Length: 18585
Server: GSE
X-Cache: MISS from gw250
X-Cache-Lookup: MISS from gw250:3128
Via: 1.0 gw250 (squid/3.1.19)
Connection: keep-alive

<!DOCTYPE ...
<html>
  <head>
    <title>PHP, Web e coisas assim: Manipulando o cabeçalho de ...</title>
...
</html>

Observe que a resposta é composta por duas partes. A primeira parte é o cabeçalho HTTP (que vai até a primeira linha em branco), e a segunda parte é o conteúdo da resposta, que representa o recurso desejado (todas linhas abaixo da primeira linha em branco). Ao receber esta resposta, o navegador avalia o cabeçalho, faz as devidas conversões/decodificações sobre o conteúdo e renderiza o HTML visualmente. Durante esta renderização, ele pode precizar realizar outras requisições para obter novos recursos como, por exemplo, imagens, arquivos CSS e arquivos JavaScript usados no HTML.

Não é objetivo deste artigo falar sobre as possíveis diretivas de requisição e resposta do protocolo HTTP. Você pode encontrar mais detalhes em: HTTP (Wikipédia) e RFC-2616 (W3C). Além disso, já postei sobre um assunto relacionado, que são os Códigos de Retorno HTTP. No exemplo mostrado, o código de retorno foi 200, que indica "OK", ou seja, o resultado foi obtido corretamente e está disponível.


A função header

Sabendo como funciona a base do protocolo HTTP, podemos dizer em uma frase breve que: "a função header do PHP serve para definir ou modificar as diretivas do cabeçalho HTTP de uma mensagem de resposta do servidor para o cliente."

A utilização da função também é bastante simples. Ela recebe três parâmetros, mas apenas o primeiro é obrigatório:

  1. A string com uma "linha" do cabeçalho (que pode ser uma Diretiva/Valor ou então a linha principal do cabaçalho).
  2. Um valor booleano indicando se a string deve substituir a diretiva, caso ela já tivesse sido definida, ou se ela deve ser apenas adicionada.
  3. Um valor inteiro indicando o código de retorno do cabeçalho HTTP (incluído na linha principal do cabeçalho HTTP).

Lembre-se que o PHP, quando usado em um sistema web, trabalha em conjunto com o servidor HTTP (por exemplo, o Apache). O servidor HTTP pode incluir o cabeçalho HTTP ou adicionar diretivas, de acordo com sua configuração. Além disso, o PHP também pode adicionar diretivas automaticamente, de acordo com algumas configurações.


Utilização prática

Vamos listar alguns exemplos de utilização da função header:

<?php
// Informar que o recurso solicitado nao existe
header('HTTP/1.0 404 Not Found');
echo '<p>Página não encontrada</p>';
exit(0);
<?php
// Informar que o usuario deve ser redirecionado
header('Location: http://rubsphp.blogspot.com.br/', true, 307);
exit(0);
<?php
// Informar que o recurso mudou de endereco permanentemente
header('Location: http://rubsphp.blogspot.com.br/', true, 301);
exit(0);

Note que no primeiro exemplo, informamos uma linha de cabeçalho principal, que contém o código de retorno 404. Nos exemplos seguintes, foram informados a diretiva "Location" com um valor, e o código de retorno HTTP através do terceiro parâmetro da função. Ao informar o código de retorno pelo terceiro parâmetro, o prórpio PHP já modifica a linha de cabeçalho principal. No primeiro caso, ele inclui a linha "HTTP/1.1 307 Temporary Redirect" e, no segundo caso, a linha "HTTP/1.1 301 Moved Permanently". Portanto, o terceiro parâmetro pode ser visto como um atalho para definir o código de retorno desejado. A forma completa, sem usar o atalho, seria assim:

<?php
// Informar que o recurso mudou de endereco permanentemente
header('HTTP/1.0 301 Moved Permanently');
header('Location: http://rubsphp.blogspot.com.br/');
exit(0);

O PHP é configurado para definir a diretiva "Content-Type" automaticamente, caso não seja definido pelo script PHP. A diretiva de configuração é "default_mimetype" em conjunto com "default_charset". Normalmente esta diretiva está configurada com valor "text/html". Então, se queremos gerar um arquivo CSS dinamicamente, precisamos redefinir o cabeçalho HTTP, conforme o exemplo:

<?php
header('Content-Type: text/css');
echo <<<CSS
.destaque {
  font-weight: bold;
}
CSS;
?>

Uma outra utilização comum da função, é para auxiliar o user-agent de que o recurso deve ser anexado, ou seja, forçar que o arquivo seja baixado por download. Para isso, é usada a diretiva HTTP Content-Disposition com o valor "attached", e pode ser informada uma sugestão de nome de arquivo, conforme o exemplo:

<?php
header('Content-Type: image/jpeg');
header('Content-Disposition: attachment; filename="imagem.jpg"');
readfile('/caminho/para/a/imagem.jpg');
exit(0);

Observação: no PHP 5.4.0 foi adicionada a função http_response_code. Com ela, é possível obter ou definir o código de retorno HTTP, informando apenas o número. Veja o exemplo:

<?php
// Informar que o recurso solicitado nao existe
http_response_code(404);
echo '<p>Página não encontrada</p>';
exit(0);

Erros comuns

Um erro comum de utilização da função header (e de outras funções que geram cabeçalho HTTTP, como é o caso da função setcookie e a função session_start) é quando ela é chamada após algum conteúdo já ter sido "impresso". O erro é: Warning: Cannot modify header information - headers already sent by ... in ... on line ...

Isso ocorre pois, como vimos anteriormente, todo o cabeçalho HTTP precisa ser definido antes do conteúdo da mensagem de resposta. Por isso, se já incluímos algum conteúdo da mensagem de resposta, então não será mais possível definir nenhum cabeçalho pois ele já foi enviado para o cliente.

A melhor solução para este erro é localizar o ponto do código onde foi "impresso" alguma coisa e tratá-lo para que só seja impresso após a chamada da função header. Uma outra solução, é utilizar o output buffer. Com ele, o conteúdo da mensagem HTTP é jogado primeiro num buffer, antes de ser enviado par ao cliente. Por isso, se estamos utilizando ele, e algo já foi "impresso", significa que este conteúdo pode ter ido apenas para o buffer e, portanto, o cabeçalho HTTP ainda pode ser modificado, já que nenhuma informação foi enviada para o cliente.

Nas versões atuais do PHP, a própria mensagem de warning informa o ponto em que foi "impresso" alguma coisa, facilitando a localização. Mas se você usa uma versão mais antiga, você pode descobrir isso chamando a função headers_sent com os dois parâmetros ($file e $line), que são passados por referência e, portanto, são modificados pela função. Veja um exemplo de como usar:

...

$file = '';
$line = '';
if (headers_sent($file, $line)) {
    echo 'Algo já foi impresso pelo arquivo ' . $file . ' na linha ' . $line;
    exit(1);
}

header(...);

Para saber mais detalhes de como funciona o output buffer, leia também os artigos Output Buffer e os mistérios do "echo" (parte 1) e Output Buffer e os mistérios do "echo" (parte 2).

Bom, mas tem um outro detalhe importante a ser citado. Nem sempre algo que foi "impresso" pelo PHP foi gerado com a chamada do comando echo. Algumas funções do PHP também imprimem conteúdo, como exemplo: print, printf, var_dump, print_r, readfile, passthru, etc. Além disso, não podemos nos esquecer que o código PHP é sempre delimitado pelos delimitadores de código PHP e que tudo que fica fora destes delimitadores é considerado conteúdo a ser impresso, até mesmo se este conteúdo for um espaço ou uma quebra de linha. Portanto, um erro muito comum é a criação de um arquivo (classe ou biblioteca de funções) que possui espaços ou quebras de linha antes do delimitador "<?php" ou após o delimitador "?>" e que, ao serem incluídos por outro arquivo PHP, acabam imprimindo algo de forma indesejada. Para evitar isso, recomendo que arquivos 100% PHP (ou seja, que não tem o PHP embutido em HTML) não utilizem o delimitador de fechamento do PHP, já que ele é opcional.

8 comentários

Unknown disse...

Boa Japa, só me restou uma duvida: Como usar o header() pra fazer o cache de css, js e imagens?
Todo mundo fala do .htacess, mas eu acho mais fácil aprender a usar o header() do que ir fuçar no servidor...
Se você puder me ajudar agradeço.

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Thiago

Escrevi 3 artigos sobre esse assunto.

1 - Explicando sobre a configuração do expires e deflate no Apache:
http://rubsphp.blogspot.com.br/2013/02/otimizando-apache-com-mod-expires-e-mod-deflate.html

2 - Explicando sobre o Expires programado via PHP:
http://rubsphp.blogspot.com.br/2012/08/cache-de-arquivos-com-http-expires-via-php.html

3 - Explicando sobre o ETag programado via PHP:
http://rubsphp.blogspot.com.br/2013/03/cache-no-navegador-usando-etag.html

Dê uma lida lá e qualquer dúvida é só comentar.

Anônimo disse...

Muito bom seu artigo Rubens, gostaria de saber se existe alguma maneira de modificar o cabeçalho de envio ( requisição ) sem ser por curl, que php redirecione como se fosse um
header('location:...')


alguma coisa parecida com isso aqui :

header("username: rcdrig");
header("login: false");
header("location: $url");


que estes dois cabeçalhos sejam incluidos no request, e não no response.

Rubens Takiguti Ribeiro (autor do blog) disse...

Anônimo,

Não entendi sua pergunta. Quem faz o request é o cliente (navegador, etc). Quando ela chega, já chegou. Não tem como mudar algo que você recebe.

O papel do servidor é responder, então ele cuida só do response. Se você faz o servidor requisitar algo externo, então esse servidor tá no papel de cliente do outro serviço, então pode modificar o request, mas aí é com o curl ou socket mesmo.