3 de maio de 2012

Trabalhando com HTTP em Delphi

O protocolo HTTP - HyperText Transfer Protocol ou Protocolo de Transferência de Hipertexto) - é certamente o mais utilizado na internet. É com ele que as páginas da internete são transferidas para seu computador antes de serem exibidas no navegador. O termo "hipertexto" diz respeito ao fato de que os documentos obtidos através do protocolo podem conter links remetendo a outros documentos.

Numa primeira olhada, a utilidade do HTTP não parece muito diferente do FTP usado para fazer downloads. O código abaixo, por exemplo, obtém o documento padrão do site do Google Brasil, isto é, a página HTML que é exibida quando se digita o endereço do Google Brasil num navegador. No exemplo, estou usando o componente TIdHTTP da paleta do projeto Indy:
var lResponse : TStringStream;
begin
lResponse := TStringStream.Create('');

try
{ lResponse conterá a página HTML requisitada :}
idHttp1.Get('http://www.google.com.br', lResponse);
finally
lResponse.Free();
end;
Apesar de não ser necessário configurar explicitamente um servidor/porta e usuário/senha como no FTP, o resultado final é o mesmo: o download de um arquivo remoto. Na verdade, a infraestrutura da internete mapeia automaticamente o endereço do Google para um servidor/porta; nesse caso, o usuário e senha é dispensável pois se trata de um site público que permite acesso anônimo.

Se olharmos mais a fundo, no entanto, veremos que o HTTP é mais flexível porque permite adicionar parâmetros à requisição enviada ao servidor. Na prática, isso dá ao servidor a chance de montar dinamicamente a resposta mais adequada à solicitação feita. As APIs de serviços publicados na internete normalmente são baseados no HTTP e se valem dos parâmetros pra flexibilizar as requisições. É o caso, por exemplo, do Google Docs e de outros serviços do Google, que usam a API de dados da empresa. Em outro post, eu mostro como se comunicar com o Google Docs usando a API de dados para ler os documentos salvos nesse serviço.

Se você já trabalhou com HTML, deve ter notado que a sintaxe dos Forms inclui uma "action", um método de envio e os diversos campos que serão preenchidos pelo usuário da página, sendo admitido até mesmo a existência de campos invisíveis contendo informações pré alimentadas.

Para relembrar o significado dessas propriedades do Form HTML : A "action" é o endereço na internete para onde a requisição será submetida. O "método" diz repespeito a como e quais informações serão enviadas; os valores mais comuns são GET (as informações são formatadas e acrescentadas ao endereço da "action", que então é enviado ao servidor) ou POST (as informações são transportadas à parte e não é possível enxergá-las na barra de endereço do navegador).

O form de pesquisa do Google, por exemplo, admite que você selecione a língua na qual deseja obter as repostas e, claro, os termos a serem pesquisados. O exemplo abaixo submete ao Google uma pesquisa em português com o nome do blog:
var lURL : String;
lResponse : TStringStream;
begin
lResponse := TStringStream.Create('');

try
lURL := 'http://www.google.com.br/search?' +
'hl=pt-BR&' +
'q=balaio%20tecnologico';
idHttp1.Get(lURL, lResponse);
lResponse.Position := 0;
{ Exemplo de uso do response : carregar o conteúdo num RichEdit : }
reResp.Lines.LoadFromStream(lResponse);
finally
lResponse.Free();
end;
Veja que cada parâmetro é composto de um nome (o mesmo configurado nas tags input do form) seguido por um sinal de igual (=) e o valor que o parâmetro deve assumir (normalmente, os próprios valores fornecidos pelo usuário). Cada parâmetro é concatenado ao anterior através de um "E Comercial" (&).

Ambos os parâmetros considerados pelo exemplo estão incluídos no form da pesquisa do Google. Ou seja, se você souber quais são os parâmetros de um form, poderá simular via Delphi a requisição que ele submeteria numa página da Web e, então, obter exatamente a mesma reposta, podendo tratá-la do modo que for mais apropriado para sua aplicação.

Por causa dessa facilidade de simular requisições via programação, muitos sites se protegem implementando um CAPTCHA. Trata-se de uma imagem com um texto embutido que é gerada dinamicamente pelo servidor de modo que apenas operadores humanos consigam ler. O texto deve, então, ser digitado num campo e enviado junto com as demais informações. A requisição só é atendida se o texto estiver correto.

Como podemos notar pelo exemplo, usar o GET para recuperar um documento pode ser problemático se certas informações sensíveis - como uma senha - tiverem que ser transmitidas direto na URL. Nestes casos, é recomendado usar o POST pois com ele as informações trafegam por outros meios.

O uso do POST também é simples. Basta passar os parâmetros num TStringList separado. A URL do "action" é informada sem quaisquer decorações extras:
var lParams :TStringList;
lResponse : TStringStream;
begin
lParams := TStringList.Create;
lResponse := TStringStream.Create('');

try
lParams.Add('filt=all');
lParams.Add('p=balaio%20tecnologico');
idHttp1.Post('http://br.search.yahoo.com/search', lParams, lResponse);

{ Exemplo de uso do response : carregar o conteúdo num RichEdit : }
lResponse.Position := 0;
reResp.Lines.LoadFromStream(lResponse);
finally
lParams.Free();
lResponse.Free();
end;
Neste exemplo, usei o Yahoo! porque o Google não implementa POST para pesquisas.

Ajustes ainda mais finos para submeter a requisição podem ser configurados na propriedade Request do TIdHTTP. Com ele, é possível ajustar o tipo de documento que se espera receber de volta (HTML, XML, etc.), o tipo de encoding, se há necessidade de passar por um proxy, etc.

36 comentários :

Anônimo disse...

Se eu coloco o site para abrir a partir de um edit da Unknown Protocol, como contorno?

Luís Gustavo Fabbro disse...

Você colocou uma caixa de texto (edit) na tela para o usuário poder entrar com o endereço do site? Neste caso, certifique-se de que o endereço digitado está completo, incluindo o "http" no início. Os navegadores hoje acrescentam isso automaticamente mas chamadas via API (como as do post) não. Então, se o usuário não passar isso no seu edit, o seu programa pode completá-lo antes de chamar get ou post.

[]s

Besteira do Dia disse...

No caso seria correto eu criar o idHttp dentro da thread ou quando chamo na unit principal? Caso eu queria fazer vários downloads.

No caso algo como:

Crio o idhttp
crio a thread indicando o idhttp criado.
E quando terminar o downlaod eu dou um free na thread?

Como vou identificar a thread correta se eu uso a mesma variável para todas as threads?

Luís Gustavo Fabbro disse...

O TidHTTP não precisa estar embutido num TForm. Por isso, você pode criar o TidHTTP como um membro da sua thread e instanciá-lo manualmente, evitando a necessidade de sincronizar o uso do recurso compartilhado.

Se você tem alguma restrição para criar mais que um TidHTTP, dê veja o post Trabalhando com Threads em Delphi - Seções Críticas. Nele há exemplos de sincronização no uso de recursos compartilhados em ambiente multi-thread.

[]s

Anônimo disse...

Luis, utilizei a tua sugestão de rotina para buscar o retorno de uma url que no caso o retorno é um xml, ao executar:
IdHTTPxml.get('urldoclientecomparametros',lresponse);

XMLresposta.Active := True;
XMLresposta.Encoding := 'iso-8859-1';
XMLresposta.LoadFromStream(lResponse);

Não está vendo como um xml e sim apenas dividindo em duas linhas, sendo a segunda todo o conteudo do xml como um texto. O que pode estar errado ?

Anônimo disse...

Sobre o comentario anterior, queria adicionar o seguinte. qdo visualizo o codigo fonte do retorno desta URL, realmente volta todo o xml em uma unica linha.

Luís Gustavo Fabbro disse...

Você consegue enxergar o XML de retorno no navegador(IE, Firefox ou Chrome) ? Isto é, ao invés de carregar no TDomXml, salve o retorno para um arquivo e veja se o navegador consegue abrí-lo corretamente.

PS:Não tenho certeza mas talvez vc precise voltar a posição do lResponse para o começo do stream antes de carrega-lo no XMLResposta. Algo como lResponse.Position := 0;

[]s

§ÜP®ËM3 (Daniel/Nerd) disse...

Olá amigo!

Muito bom seu tutorial, ótimos exemplo!

Tenho uma dúvida em relação ao exemplo do método POST, que é o seguinte. Como posso pegar o endereço do site, exibir o código fonte no RichEdit só que eu queria utilizar um Edit para digitar o endereço do site e pegar o código fonte. Poderia me responder essa dúvida por gentileza? Email de contato: nerd.supreme@gmail

Paz e espero respostas suas, sucesso com blog! ;)

Luís Gustavo Fabbro disse...

Daniel

Uma vez que você tenha colocado o edit no form, basta substituir o endereço fixo do post pelo valor da propriedade Text do seu edit:

idHttp1.Post(Edit1.Text, lParams, lResponse);

Como agora o usuário precisa informar o endereço, vc tb pode usar o código pra carregar o conteúdo dentro do evento OnClick de um botão na tela.

[]s

Dempsey Silva disse...

Utilizando o idHTTP.Post retornou o seguinte erro 'HTTP/1.1 500 INTERNAL SERVER ERRO' ao dar o Post. Que erro seria este, abaixo esta o meu código:

lParams := TStringList.Create;
lResponse := TStringStream.Create('');

lParams.Add('razao_social=' + QuotedStr(cdsClientePortsDSC_RAZAO_SOCIAL.AsString));

IdHttp.Request.Username := 'xxx@yahoo.com.br'; //Aqui eu entro com o Usuário que a Api exige
IdHttp.Request.Password := '112233'; //Aqui eu entro com a Senha que a Api exige
IdHttp.Request.BasicAuthentication := True;

IdHttp.Post('http://69.164.203.63:8080/api/v1/clientes', lParams, lResponse);

Luís Gustavo Fabbro disse...

Dempsey

Essa mensagem indica que o servidor não foi capaz de atender sua requisição porque deu "pau" na lógica interna dele.

Pode ser um valor errado que você passou ou alguma informação esperada mas não fornecida pelo seu código.

Reveja a documentação sobre o modo de se comunicar com o site pra determinar o que está faltando (ou sobrando).

[]s

Bruno disse...

Boa tarde Luis não estou conseguindo montar o post, quando o vem com javascript exemplo abaixo:

javascript:__doPostBack('ctl00$frameCorpo$gdvAgrupamentoDetalhe$ctl02$gdvListaNfe$ctl02$lnkNnf','')">122308

IdHTTP1.Post('www.set.rn.gov.br/uvt/nfeentrada.aspx', Params, Stream);

Luís Gustavo Fabbro disse...

Bruno

Como vc mesmo diz, o __doPostBack é um código javascript e não representa URL para onde se possa submeter uma requisição. Vc terá que entender como funciona a função pra determinar qual é a URL base e quais são os parâmetros necessários. A URL base deve ser a mesma da página contendo a chamada ao script. Como parâmetros, são esperados no mínimo os valores para __EVENTTARGET e __EVENTARGUMENT.

O artigo Understanding the JavaScript __doPostBack Function dá uma boa noção sobre esse assunto.

[]s

Egon Klipstein disse...

Estou tentando enviar um XML para um webservice do INMETRO. Estou com dúvidas em como enviar. O WS usa REST, com método POST e retorna um XML de resposta. Meu código está assim:


var
dado : TStringList; // Variável que contém o XML a ser enviado
retorno : TStringStream;
host : string; // Endereço do INMETRO
begin

retorno := TStringStream.Create('');
host := 'https://webservicedesenv.cronotacografo.inmetro.rs.gov.br/informarEnsaio';
dado := TStringList.Create;
dado.text := textoxml; // textoxml é uma Widestring recebida com o XML

IdHTTP.Request.ContentType := 'text/xml';
IdHTTP.Post(host, dado, retorno);


Acontece o seguinte erro : IOHandler value is not valid.

Alguém tem uma ideia de como fazer isso ?
Para enviar um XML deve ser usado outra forma, ou pode ser como fiz ?

Luís Gustavo Fabbro disse...

Egon

Como você está manipulando um endereço seguro (HTTPS), terá quer fornecer um iohandler apropriado para lidar com a conexão. Instancie um TIdSSLIOHandlerSocket e use-o como iohandler do seu idHTTP.

Obs: Pode ser necessário encontrar na web as DLLs do OpenSSL para usar com sucesso o handler seguro.

[]s

Egon Klipstein disse...

Ok. Deu certo, funcionou, tive que buscar como vc me alertou a versão adequada das DLLs. No meu caso uso DELPHI 7 com INDY 10, a versão da DLL é a 0.9.6a...
Agora estou enfrentando algumas dificuldades relativas ao site do INMETRO, mas não é relacionado ao componente...

Muchas gracias pela atenção...

Dempsey Silva disse...

Obrigado pela resposta Luís Gustavo... Realmente era dados que deixei de enviar...

Agora, tenho outra dúvida, preciso pegar uns Headers de respostas de HTTP.Post, como faço isso??? Já tentei de tudo q eh jeito aq e não consegui.

Dempsey Silva disse...

Não sei se me expressei bem...
Querendo pegar o retorno do WebService (O restorno vem pelo Header)... é um codigo de nome 'MeusPedidosID' que me informa que meu Post deu certo e me retorna o codigo do registro inserido...

Luís Gustavo Fabbro disse...

Dempsey

Por conveniência, o conteúdo da resposta do Post é lançado no stream passado como parâmetro à função.

Se quiser obter mais detalhes sobre a resposta, inspecione as propriedades do Response que é membro do idHttp responsável pelo Post.

Entre outros dados, o Response traz o tipo de conteúdo retornado (ex: HTML), o charset usado, data e hora do retorno, data e hora de expiração do conteúdo e os headers enviados pelo servidor (RawHeaders) na forma de uma lista.

[]s

Dempsey Silva disse...

Obrigado novamente... Perfeito....

IdHttp.Post(lUrl, lParametros, Response); //Enviando
lResposta := IdHttp.Response.RawHeaders[5]; //Retorno

HOME disse...

Como faço para verificar se o arquivo existe ?

Luís Gustavo Fabbro disse...

Faça o Get conforme mostra o post e verifique o valor da propriedade ResponseCode do TIdHttp. Ela estará com valor 404 quando o arquivo buscado não existir.

Outros valores possíveis são listados neste link.

[]s

HOME disse...
Este comentário foi removido pelo autor.
HOME disse...

Esqueci de mensionar... uso delphi 7 com indy 9

Luís Gustavo Fabbro disse...

Checar pelo erro 404 só vai funcionar se o servidor não redirecionar sua requisição. Parece ser esse o caso do endereço que vc usou no seu código: o servidor não encontra o arquivo mas ao invés de retornar erro 404 ele retorna uma página válida.

Uma alternativa, neste caso, é testar o Response.ContentType do IdHTTP para saber se a resposta contém uma imagem ou um HTML.

[]s

HOME disse...

Deu certinho conforme você mencionou:

vRestring := IdHTTP1.Response.ContentType;

strm1.Seek(0,soFromBeginning);
if (vRestring <> 'text/html') then
begin
foto1.LoadFromStream(strm1);
cxfoto1.Picture.Assign(foto1);
end;


Muito obrigado!

Roger Castro disse...

Boa tarde Luiz Gustavo,
vi aqui no blogger algumas ajudas suas para o pessoal.
Gostaria de saber se você pode me ajudar quanto a elaboração da interação de uma aplicação delphi2007 com o google drive. Seria upload e download de arquivos apenas.

Luís Gustavo Fabbro disse...

Roger

Que tipo de dificuldade você encontrou? Nunca trabalhei com o Drive mas imagino que funcione como o Docs, onde você fornece a URL de um serviço e passa outras informações necessárias através de entradas no header - como os dados de login ou o nome da planilha com a qual quer trabalhar. As repostas destas requisições são formatadas como XML, de modo que seu programa terá que lê-las e interpretá-las de acordo com a documentação do próprio serviço. Para lidar com planilhas, a documentação está em https://developers.google.com/google-apps/spreadsheets/?csw=1.

[]s

Roger Castro disse...

Não encontrei código em delphi para que eu possa tentar fazer a comunicação com o drive. Vou trabalhar apenas colocando arquivos e retirando arquivos (upload e download).
A unica coisa que encontrei, foi um objeto de terceiro mas que vale por 30 dias apenas, este eu consegui fazer o upload e o downloads, mas para autenticar tenho que efetuar o login no google chrome e não posso fazer isso, porque se não qualquer usuário vê os arquivos que estão la no drive.
Quanto ao objeto de terceiro, eu queria ver se tem como efetuar o upload e o download do arquivo sem usar este objeto, ou seja, queria criar algo que fizesse este processo.

Luís Gustavo Fabbro disse...

Roger

Comece procurando a documentação que descreve como integrar uma aplicação com o Drive. Ela provavelmente trará dicas dos passos para estabelecer a comunicação com os servidores do Google.

[]s

Rafael Mota Facundo disse...

Olá, Luis! Gostaria de saber se você poderia me ajudar. Estou tentando enviar um SMS usando método Post para seguinte

URL= https://sms.comtele.com.br/api/{sua_chave}/sendmessage?sender={remetente}&receivers={destinatarios}&content={conteudo}

como devo passar esses parâmetros? estou tentando assim:

lParams := TStringList.Create;
lResponse := TStringStream.Create('');
try
lParams.Add('G2');
lParams.Add('8597403015');
lParams.Add('Teste de envio');
lHTTP.Post('https://sms.comtele.com.br/api/f7cd25bf-0f1c-4bbf-9fd0-9a51c8ba4c99/sendmessage?', lParams, lResponse);
lResponse.Position := 0;
reResp.Lines.LoadFromStream(lResponse);
finally
lParams.Free();
lResponse.Free();

Mas sempre recebo o erro de Bad Request.
Obrigado pela atenção

Luís Gustavo Fabbro disse...

Rafael

Pelo formato da URL me parece que deve ser usado o método GET. Monte a URL final, adicionando ao endereço base os pares nomes/valores de parâmetros. Algo como :

https://sms.comtele.com.br/api/{sua_chave}/sendmessage?sender=G2&receivers=8597403015&content=teste

Se preferir, veja a documentação do seu provedor para saber se ele aceita requisições com post e com quais nomes de parâmetros isso deve ser feito. Obs: ao adicionar o parâmetro, o nome deve ser incluído:

lParams.Add('sender=G2');
lParams.Add('receivers=8597403015');
lParams.Add('contente=Teste de envio');

[]s

Rômulo Cordeiro disse...

Ola Luiz,
Estou tentando passar o coidio abaixo atravez de post mas não tá dando certo. O que estou fazendo de errado?

lParams := TStringList.Create;
lResponse := TStringStream.Create('');
reResp.Lines.Clear;

try

lParams.Add('codigo_transacao=5');
lParams.Add('loja_primaria=teste');
lParams.Add('nome_primario=teste');
lParams.Add('Senha_primaria=teste');
// lParams.Add('loja_primaria=3477535000146');
// lParams.Add('nome_primario=gatesteacs');
// lParams.Add('Senha_primaria=34740216');
lParams.Add('versao=3.95');
lParams.Add('compra=1');
lParams.Add('produto=1417');
lParams.Add('valor=5.00');
lParams.Add('ddd=85');
lParams.Add('fone=88276717');

idHttp1.Post('https://www.cellcard.com.br/teste/integracao_xml.php', lParams, lResponse);

lResponse.Position := 0;
reResp.Lines.LoadFromStream(lResponse);

finally
lParams.Free();
lResponse.Free();
end;

Rômulo Cordeiro disse...

Olá Luiz,
O codigo abaixo não está dando certo. O que tem de errado com ele

lParams := TStringList.Create;
lResponse := TStringStream.Create('');
reResp.Lines.Clear;

try

lParams.Add('codigo_transacao=5');
lParams.Add('loja_primaria=teste');
lParams.Add('nome_primario=teste');
lParams.Add('Senha_primaria=teste');
// lParams.Add('loja_primaria=3477535000146');
// lParams.Add('nome_primario=gatesteacs');
// lParams.Add('Senha_primaria=34740216');
lParams.Add('versao=3.95');
lParams.Add('compra=1');
lParams.Add('produto=1417');
lParams.Add('valor=5.00');
lParams.Add('ddd=85');
lParams.Add('fone=88276717');

idHttp1.Post('https://www.cellcard.com.br/teste/integracao_xml.php', lParams, lResponse);

lResponse.Position := 0;
reResp.Lines.LoadFromStream(lResponse);

finally
lParams.Free();
lResponse.Free();
end;

Luís Gustavo Fabbro disse...

Rômulo

Que tipo de problema você encontrou com esse código? Há alguma mensagem de erro? Tecnicamente, ele me pareceu correto...

[]s

FMLIMACAMPOS disse...

Dempsey Silva voce conseguiu integrar com meus pedidos..
adiciona skype olivercds

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.