Por que é que isto nos interessa: somos a Geonode e vendemos proxies, pelo que as pessoas utilizam constantemente pedidos HEAD através de nós para verificar links, tamanhos e disponibilidade a um custo reduzido. A advertência sincera é que o HEAD é uma solicitação diferente, não um GET «leve», e tratá-la como tal leva a conclusões erradas, mas que parecem corretas. Um URL que devolve um código 200 a um HEAD pode devolver um código 403 a um GET. Um recurso que não apresente «Content-Length» numa resposta HEAD pode apresentá-lo numa resposta GET. E uma camada anti-bot pode interpretar uma solicitação HEAD invulgar como um sinal próprio. A solicitação HEAD é excelente para o fim a que se destina; é um mau indicador do que «aconteceria se eu realmente fosse buscar isto».
A forma correta de utilizar o comando `
curl -I https://example.com
`
O manual do curl descreve o comando ``-I, --head`
` da seguinte forma: «(HTTP FTP FILE) Obtém apenas os cabeçalhos. Os servidores HTTP dispõem do comando HEAD, que este utiliza para obter apenas o cabeçalho de um documento. Quando utilizado numa URL de tipo FTP ou FILE, o curl apresenta apenas o tamanho do ficheiro e a data da última modificação.»
Resultado:
HTTP/2 200
content-type: text/html; charset=UTF-8
content-length: 1256
last-modified: Thu, 17 Oct 2019 07:18:26 GMT
cache-control: max-age=604800
Esta é a resposta completa à pergunta do título. O que se segue é a parte que poupa tempo.
Por que razão «-X HEAD» está errado
O manual do curl aborda esta questão diretamente na secção «-X, --request»:
Esta opção apenas altera a palavra efetivamente utilizada no pedido HTTP, não altera o comportamento do curl. Por exemplo, se pretender efetuar um pedido HEAD correto, utilizar «
-X HEAD» não é suficiente. É necessário utilizar a opção «--head».
O mecanismo: -X troca a string do método e nada mais. O curl continua a comportar-se como se estivesse a fazer um GET, o que significa que continua à espera de um corpo de resposta. O servidor, que implementa corretamente o HEAD, envia cabeçalhos e nenhum corpo. O curl aguarda por conteúdo que nunca chegará, e o comando parece ficar bloqueado até que um tempo limite ou o encerramento da ligação o termine.
O manual também alerta para um segundo comportamento do -X que apanha as pessoas de surpresa com redirecionamentos: «Se for utilizado --location, a string do método que definir com --request é utilizada para todos os pedidos». Assim, -X POST -L reenvia um POST para cada etapa numa cadeia de redirecionamentos, o que raramente é o que alguém pretende.
O princípio geral, enunciado pelo próprio manual: «Normalmente, não é necessária esta opção. Todos os tipos de pedidos GET, HEAD, POST e PUT são, em vez disso, invocados através de opções dedicadas da linha de comandos.» Utilize -I para HEAD, -d para POST, -T para PUT e reserve -X para métodos verdadeiramente invulgares, como PROPFIND.
O que é, na verdade, o HEAD
A RFC 9110 §9.3.2 define-o numa única frase:
O método HEAD é idêntico ao GET, com a diferença de que o servidor NÃO DEVE enviar conteúdo na resposta.
E indica a sua finalidade: «O HEAD é utilizado para obter metadados sobre a representação selecionada sem transferir os seus dados de representação, frequentemente com o objetivo de testar ligações de hipertexto ou identificar modificações recentes.»
Trata-se de um requisito rigoroso para os servidores — MUST NOT enviar conteúdo — e é por isso que o curl fica bloqueado quando lhe é solicitado que espere receber algum.
A regra relativa aos cabeçalhos é deliberadamente mais flexível, e é esta a parte que as pessoas interpretam mal:
O servidor DEVE enviar os mesmos campos de cabeçalho em resposta a um pedido HEAD que teria enviado se o método do pedido tivesse sido GET. No entanto, um servidor PODE omitir campos de cabeçalho cujo valor só é determinado durante a geração do conteúdo.
A RFC dá um exemplo concreto: os servidores que armazenam respostas dinâmicas em buffer podem produzir Content-Length e Vary numa solicitação GET que «não são gerados numa resposta HEAD». Chama a isto «inconsistências menores» e considera-as «preferíveis a gerar e descartar o conteúdo para uma solicitação HEAD, uma vez que a HEAD é normalmente solicitada por uma questão de eficiência».
Assim, a ausência de Content-Length numa resposta HEAD não é necessariamente um erro nem tem necessariamente significado. Pode tratar-se simplesmente de um servidor que se recusa a calcular algo que só teria conhecido ao renderizar a página.
Existe também uma regra relativa aos corpos das solicitações que vale a pena conhecer se estiver a desenvolver ferramentas. O conteúdo numa solicitação HEAD «não tem semântica definida de forma geral, não pode alterar o significado ou o destino da solicitação e pode levar algumas implementações a rejeitar a solicitação e a encerrar a ligação devido ao seu potencial como um ataque de contrabando de solicitações». A RFC afirma que um cliente «NÃO DEVE gerar conteúdo numa solicitação HEAD», salvo acordo prévio específico. Não envie um corpo com uma solicitação HEAD.
Para que serve o HEAD
Casos verdadeiramente úteis, em que se troca uma transferência completa por algumas centenas de bytes.
Verificar se um URL está ativo:
curl -sI -o /dev/null -w '%{response_code}\n' https://example.com
Descobrir o tamanho de um ficheiro antes de o descarregar:
curl -sI https://example.com/large.iso | grep -i content-length
Seguir e relatar uma cadeia de redirecionamentos:
curl -sIL -o /dev/null -w '%{num_redirects} hops -> %{url_effective}\n' https://example.com
Verificar se um servidor suporta pedidos de intervalo, o que determina se um download interrompido pode ser retomado:
curl -sI https://example.com/file.zip | grep -i accept-ranges
Verificar a atualidade sem descarregar:
curl -sI https://example.com/data.json | grep -iE 'last-modified|etag'
Verificação em massa de links, que é a utilização clássica e onde a poupança de largura de banda se acumula:
while read -r url; do
code=$(curl -sIL -o /dev/null -w '%{response_code}' --max-time 10 "$url")
echo "$code $url"
done < urls.txt
Com largura de banda limitada, a poupança é real: uma verificação de link que transferiria 500 KB por URL transfere, em vez disso, apenas algumas centenas de bytes. Em dez mil URLs, essa é a diferença entre cinco gigabytes e alguns megabytes.
Quando o HEAD o induz em erro
Os modos de falha, que são a razão para a advertência no início.
O servidor rejeita totalmente o HEAD. 405 Method Not Allowed
numa URL em que o GET funciona na perfeição. Pouco comum em conteúdo estático, mas não raro em APIs e pontos finais de aplicações.
O servidor trata o HEAD de forma diferente. Códigos de estado diferentes, cabeçalhos diferentes e, por vezes, um percurso de código totalmente diferente na aplicação. A RFC permite omitir cabeçalhos derivados do conteúdo, e as implementações variam na forma como aplicam essa regra.
Os caches e as CDNs podem indexar o HEAD separadamente. Os cabeçalhos de cache num HEAD podem refletir uma entrada de cache diferente daquela que um GET acederia, pelo que um HEAD não é uma forma fiável de inspecionar o comportamento do cache.
Os sistemas anti-bot respondem de forma diferente. Uma solicitação HEAD proveniente de um cliente desconhecido é, por si só, um sinal, e a resposta que obtém pode não ser a mesma que uma solicitação GET, como a de um navegador, receberia.
O parâmetro «Content-Length
» pode estar ausente ou incorreto. Permitido pela especificação, comum em conteúdos dinâmicos e uma base inadequada para estimar o tamanho de um download de qualquer conteúdo gerado.
As cadeias de redirecionamento podem diferir. Alguns servidores redirecionam GET e HEAD para locais diferentes, particularmente quando há negociação de conteúdo envolvida.
Quando precisar de saber o que uma solicitação real faria, faça uma solicitação real e descarte o corpo:
curl -sS -o /dev/null -D - https://example.com
Um GET genuíno, cabeçalhos na saída padrão, corpo descartado. Paga a largura de banda e obtém uma resposta precisa. Escolha deliberadamente entre as duas opções: -I
quando quiser algo barato, -o /dev/null -D -
quando quiser algo verdadeiro.
Existe uma opção intermédia para recursos de grande dimensão — solicitar um byte em vez do documento completo:
curl -sS -r 0-0 -o /dev/null -D - https://example.com/large.iso
-r, --range
recupera «um intervalo de bytes (ou seja, um documento parcial)», pelo que 0-0
obtém apenas o primeiro byte. Trata-se de um GET genuíno com comportamento de GET genuíno, praticamente sem custos de largura de banda. A advertência do manual: «Muitos servidores HTTP/1.1 não têm esta funcionalidade ativada», por isso verifique primeiro se existe Accept-Ranges: bytes
e espere uma resposta completa caso não exista.
Padrões que vale a pena copiar para os scripts
Os comandos acima tornam-se consideravelmente mais úteis com um pouco de estrutura à sua volta.
Um verificador de links que apresenta resultados fiáveis. A versão simplista trata todos os códigos de resposta que não sejam 200 como links quebrados, o que gera falsos alarmes em redirecionamentos e em servidores que rejeitam o comando HEAD. Este aqui faz a distinção:
check() {
local url="$1" code
code=$(curl -sIL -o /dev/null --max-time 10 -w '%{response_code}' "$url")
case "$code" in
200) echo "OK $url" ;;
405) code=$(curl -sSL -o /dev/null --max-time 10 -w '%{response_code}' "$url")
echo "GET:$code $url" ;;
000) echo "TIMEOUT $url" ;;
*) echo "$code $url" ;;
esac
}
A ramificação «405» é importante: um servidor que recusa HEAD não é um link quebrado, e tentar novamente com um GET é a única forma de o saber. «000» é a forma do curl indicar que não chegou qualquer resposta HTTP, o que distingue uma falha de rede de um erro do servidor.
Paralelismo, com cuidado. A verificação de links é extremamente paralela e a tentação é executá-la em grande escala. Resista:
xargs -P 8 -I{} sh -c 'check "$1"' _ {} < urls.txt
Oito é um valor padrão razoável. O limite máximo aqui é a cortesia de não sobrecarregar o servidor de outra pessoa, em vez da sua própria capacidade, e um verificador de links que gere um incidente de limitação de taxa acaba por custar mais do que poupa.
Defina sempre um tempo limite. Um HEAD contra um anfitrião que não responde fica bloqueado exatamente durante o mesmo tempo que um GET ficaria. --max-time 10 com --connect-timeout 5 impõe esse limite e, num ciclo sobre milhares de URLs, é esse limite que faz com que a tarefa termine.
Registe o URL efetivo, não apenas o estado. %{url_effective} após -L indica-lhe onde o link realmente conduziu, o que transforma «este link funciona» em «este link funciona e agora aponta para outro sítio» — normalmente a descoberta mais interessante.
Armazene os resultados em cache. Verificar novamente cada URL em cada execução desperdiça largura de banda e boa vontade. Armazene o estado e o ETag ou Last-Modified, e utilize pedidos condicionais nas passagens subsequentes para que os recursos inalterados custem apenas um 304 em vez de uma verificação completa.
HEAD através de um proxy
Há três alterações que vale a pena conhecer.
curl -I -x http://user:pass@proxy.example.com:9000 https://example.com
O comando «CONNECT» é executado primeiro no caso do HTTPS. O curl estabelece um túnel antes do HEAD e, na saída detalhada, a resposta do proxy a esse comando aparece antes da do destino. --suppress-connect-headers oculta essa informação; %{http_connect} apresenta o estado do proxy separadamente do do destino.
A poupança de largura de banda é o que importa. Com tráfego medido, um HEAD custa algumas centenas de bytes, em comparação com o volume de uma página completa. Para validação de ligações, monitorização de disponibilidade e verificações de tamanho em grande escala, esta é a diferença entre um trabalho acessível e um trabalho dispendioso. É uma das poucas otimizações genuinamente significativas disponíveis em modelos de preços por gigabyte.
Mas os bloqueios e os desafios comportam-se de forma diferente. Uma camada anti-bot que apresentaria uma página de desafio a um GET pode simplesmente recusar um HEAD, ou vice-versa. Se estiver a utilizar o HEAD para verificar se um alvo está acessível, confirme a conclusão com um GET real numa amostra antes de confiar nela para toda a lista. Este é o padrão de falha silenciosa sobre o qual escrevemos em por que é importante testar proxies: o pedido é bem-sucedido, a resposta está errada e nada o avisa.
Um pormenor do curl que vale a pena conhecer: -G, --get combina com --head. O manual refere que, quando -G é utilizado com --head, «os dados POST são, em vez disso, anexados ao URL com uma solicitação HEAD» — útil quando precisa de parâmetros de consulta construídos a partir de pares chave-valor numa solicitação HEAD.
Ler a resposta corretamente
Tirar o máximo partido do que é devolvido.
Primeiro, o estado. «200
» existe. «301
» / «302
» foram movidos — adicione «-L
» para seguir. «403
» foi recusado. «404
» já não existe. «405
» significa que o servidor não aceita HEAD, pelo que deve tentar novamente com um GET. «429
» significa que deve abrandar.
**Content-Length
**, se estiver presente, lembrando que a especificação permite a sua omissão.
**Accept-Ranges: bytes
** significa que estão disponíveis downloads retomáveis e pedidos de intervalo.
**Last-Modified
e ETag
** permitem pedidos condicionais. -z
envia If-Modified-Since
— o manual descreve-o como um pedido de «um ficheiro que tenha sido modificado após a data e hora indicadas» — e --etag-compare
trata da parte ETag
. Um 304 Not Modified
não custa praticamente nada e é a forma correta de consultar um recurso repetidamente.
**Content-Type
** indica-lhe o que teria recebido. text/html
, onde esperava JSON, significa normalmente um erro ou uma página de início de sessão.
Para utilização por máquinas, ignore completamente a análise do texto:
curl -sI -o /dev/null -w '%{header_json}' https://example.com | jq
%{header_json}
emite todos os cabeçalhos de resposta como JSON com nomes em minúsculas e valores em matriz, o que lida corretamente com cabeçalhos repetidos e elimina a necessidade de um analisador. Abordámos esta e outras opções de inspeção em como mostrar cabeçalhos de resposta com o curl.
Perguntas frequentes
Como posso enviar um pedido HEAD com o curl?
curl -I https://example.com. A forma completa é --head. Não utilize -X HEAD — o manual indica explicitamente que isso «não é suficiente» para um pedido HEAD correto, porque altera apenas a cadeia de caracteres do método, enquanto o curl continua à espera de um corpo de resposta.
Por que é que o curl -X HEAD fica bloqueado?
Porque -X altera apenas a palavra na linha de pedido, não o comportamento do curl. O curl continua à espera de um corpo de resposta, enquanto o servidor, corretamente, não envia nenhum, uma vez que a RFC 9110 exige que um servidor «NÃO DEVE enviar conteúdo» numa resposta HEAD. Utilize -I em vez disso.
Qual é a diferença entre HEAD e GET?
O HEAD é idêntico ao GET, exceto que o servidor não deve enviar o corpo. Existe para obter metadados sem transferir conteúdo, normalmente para verificar ligações ou testar a atualidade. Os servidores devem enviar os mesmos cabeçalhos que enviariam para o GET, mas podem omitir aqueles que só são calculados durante a geração do conteúdo.
O HEAD devolve sempre os mesmos cabeçalhos que o GET?
Não. A especificação indica que os servidores DEVEM enviar os mesmos cabeçalhos, mas PODEM omitir aqueles «cujo valor só é determinado durante a geração do conteúdo» — cita Content-Length e Vary como exemplos. Considera estas pequenas inconsistências preferíveis à geração e descarte de um corpo.
Como posso obter o tamanho de um ficheiro sem o descarregar?
curl -sI URL | grep -i content-length. Tenha em atenção que o cabeçalho pode estar ausente em conteúdos gerados dinamicamente, o que a especificação permite. Para obter uma resposta mais fiável e praticamente sem custos, solicite um único byte com -r 0-0 e leia o cabeçalho Content-Range.
Por que razão um URL funciona num navegador, mas devolve um erro 405 ao comando curl -I?
Porque o servidor não aceita pedidos HEAD nesse ponto de extremidade. 405 Method Not Allowed é uma resposta válida a um pedido HEAD por parte de um servidor que processa pedidos GET sem problemas. Tente novamente com um pedido GET, descartando o corpo: curl -sS -o /dev/null -D - URL.
Posso enviar uma solicitação HEAD com um corpo?
Não deve fazê-lo. A RFC 9110 afirma que o conteúdo numa solicitação HEAD «não tem semântica geralmente definida», não pode alterar o significado da solicitação e «pode levar algumas implementações a rejeitar a solicitação e a encerrar a ligação devido ao seu potencial como um ataque de contrabando de solicitações». Os clientes NÃO DEVEM gerar conteúdo numa solicitação HEAD.
A HEAD é útil para verificar se um proxy está a funcionar?
Parcialmente. Confirma a conectividade e devolve o código de estado de forma simples, o que constitui um bom teste preliminar. Não indica se um GET real seria bem-sucedido, uma vez que as camadas anti-bot tratam frequentemente os dois de forma diferente. Valide com GETs reais numa amostra antes de confiar nos resultados HEAD em toda uma lista.
Conclusão
Dois comandos cobrem tudo isto. curl -I URL para uma solicitação HEAD correta e curl -sS -o /dev/null -D - URL quando se pretende obter os cabeçalhos que uma solicitação GET real produziria. O que não funciona é -X HEAD, e o manual é claro a esse respeito: altera a palavra, mas não o comportamento, pelo que o curl fica à espera de um corpo que o servidor não deve enviar.
A decisão depende de qual dessas duas opções pretende. O HEAD é significativamente mais económico — algumas centenas de bytes contra uma página completa —, o que o torna a ferramenta certa para verificação de links, monitorização de disponibilidade e estimativa de tamanho em qualquer volume, especialmente em larguras de banda limitadas. É a ferramenta errada para prever o que uma requisição real iria devolver, porque os servidores podem omitir cabeçalhos derivados do conteúdo, podem rejeitar o HEAD de imediato e, frequentemente, encaminhá-lo através de uma lógica diferente.
E se precisar da precisão de um GET sem consumir largura de banda, -r 0-0 é o meio-termo subutilizado: um GET genuíno que recupera um byte. Verifique primeiro Accept-Ranges: bytes, uma vez que muitos servidores irão, de qualquer forma, fornecer-lhe o ficheiro completo.
