O Protocolo HTTP – Parte 05

Introdução

No post anterior (O Protocolo HTTP – Parte 04), vimos como funciona por dentro uma requisição GET de HTTP, como passar parâmetros pela URL, e como fazer um fonte em AdvPL com os tratamentos de códigos de retorno necessários. Agora, vamos ver mais alguns detalhes do GET , e ver por dentro o método POST do HTTP, primeiro pelo visão do protocolo e pelo Web Browse.

HTTP GET – Mais informações

Como já vimos anteriormente, uma requisição do tipo GET do HTTP possui restrições de tamanho e não deve ser usada para trafegar dados sensíveis. Um outro ponto muito importante é : Uma requisição do tipo GET somente é usada para CONSULTAR uma informação, e não atualizar ou mudar o estado de uma informação. Como o “dite de uma página” que começou a ser criado no post anterior ainda está muito simples, ainda tem muita coisa a ser vista sobre o HTML e como as páginas são processadas pelo Web Browser.

Quando precisamos mudar o estado de uma informação em um Web Site, usamos uma requisição POST, não apenas por ser mais flexível quando ao tamanho dos dados, mas também pela forma de passagem dos dados — dentro do “Corpo do HTTP” — não visível na URL — mas também pelos comportamentos das aplicações intermediárias envolvidas, a começar pelo Web Browser.

Fazendo um POST pelo Web Browser

Lembrando que o Web Browser carrega páginas HTML e arquivos do Web Server, para fazer uma requisição POST, precisamos fazer no mínimo uma página HTML com um formulário (form), indicando para onde (qual URL) os dados desse formulário será enviado, e como será enviado — GET ou POST. E, antes de mais nada, para conhecimento: Ao abrir o seu Web Browser e digitar uma URL qualquer, o Web Browse vai sempre fazer um GET daquela URL.

Agora, vamos incrementar a página de entrada do Web Site de testes, editando o arquivo “default.htm” da pasta “http-root” com o NOTEPAD ou outro editor de textos ASCII simples, e acrescentar a linha em negrito no arquivo … a nova página deve ficar assim:

<html>
<body">
<h3>Meu primeiro Web Site</h3>
<a href="testepost.htm">Teste de Post</a>
</body>
</html>

Ao executar o Totvs Application Server configurado para servidor de HTTP, e acessar o endereço http://localhost/, deve ser mostrado o seguinte:

O elemento <a > no HTML é uma “âncora”, onde podemos referenciar um conteúdo qualquer, e o Web Browser vai criar um LINK na tela para este conteúdo. No caso, criamos um link para uma nova página no Web Site, chamada “testepost.htm” (que ainda não existe no servidor). Se clicarmos no link “Teste de Post”, deve ser apresentada a tela de erro 404 – Not Found pelo Web Browser.

Agora, vamos criar — usando o editor de textos simples — o arquivo “testepost.htm”, dentro da pasta http-root, com o seguinte conteúdo:

<html>
<body>
<h3>Teste de Post</h3>
<hr>
<form name="mydataform" method="post" action="testepost.htm">
<input type="text" name="CAMPO1"></input>
<input type="text" name="CAMPO2"></input>
<input type="submit"/>
</form>
<hr>
<a href="/">Voltar ao Início</a>
</body>
</html>

Neste exemplo simples, criamos um formulário HTML, nomeado “mydataform”, definimos que o método de submeter esse formulário ao Web Server será “POST”, e na propriedade “action” definimos que ele deve abrir novamente o arquivo “testepost.htm”. Agora sim, ao clicar no link “Teste de Post” da página inicial do nosso site de testes, devemos ver o seguinte no Web Browser:

Testando o formulário

Preencha o primeiro campo na tela com “Ola”, o segundo com “Mundo”, e clique no botão “Enviar”. E, para nossa surpresa …

O que aconteceu ?

Bem, primeiramente … o Web Browse está “reclamando” do retorno que ele recebeu do servidor … ele fez a requisição POST do HTTP, para o arquivo “/testepost.htm”, os dados foram enviados — já vamos ver como confirmar isso –, mas o HTTP Server não retornou “nada”… sim, “nada”. Agora, vamos ver POR QUE.

  • Uma requisição de POST recebida pelo HTTP Server — no nosso caso estamos usando o Totvs Application Server — somente será “atendida” corretamente pelo Totvs Application Server, se ele estiver configurado para execução de páginas DINÂMICAS — conhecidas por “AdvPL ASP”. Calma, vamos ver isso em muitos detalhes mais para a frente. Lembre-se dos posts anteriores, que por hora o Totvs Application Server está configurado como servidor HTTP de páginas e arquivos ESTÁTICOS. Isto é, você pode montar um site “fixo”, com páginas e fotos … mas ele vai aceitar apenas GET, para mostrar as páginas e fazer download de arquivos. — Também vou montar exemplo disso …

De qualquer forma, o Web Browse não sabe disso… e ele vai fazer o que o formulário mandou .. POST para “testepost.htm”. Agora, vamos entrar nos detalhes de como o Web Browse montou o POST, usando neste caso o F12 do Google Chrome — que abre as “ferramentas do desenvolvedor” na página que você está acessando.

Refazendo o teste no Chrome

Estamos com o Google Chrome aberto em “http://localhost/testepost.htm&#8221;, vendo aquela tela de erro … clique no botão “Voltar” na parte superior esquerda da janela do Web Browser, e você deve ver novamente a tela “testepost.htm”, inclusive com os campos preenchidos.

Agora, pressione a tecla F12 do teclado, OU, pressione a combinação de teclas CONTROL + SHIFT + I — Isso abre a janela de ferramentas do desenvolvedor. Se a janela de desenvolvedor aparecer “dentro” da janela do Browse, aumente o tamanho da tela ( maximize a janela ). E você deve ver algo assim:

Agora,

Na janela do desenvolvedor, existem varias “abas”. Clique na aba “Network”, ela é que nos interessa nesse momento. Agora, vamos fazer a mágica acontecer … clique novamente no botão “Enviar”… e vamos ver o que aparece na janela do desenvolvedor, do lado direito:

Repare que a primeira linha das atividades de rede (Network) do Web Browser aparece em vermelho, indicando uma falha. O retorno recebido do HTTP Server foi um pacote de 0 bytes — ele não retornou “nada”… Agora, vamos ver o que foi enviado, clicando com o mouse em cima no nome “testepost.htm” que aparece em vermelho … Vou redimensionar a tela para a janela do desenvolvedor ficar maior ..

Ao clicar sobre uma requisição na lista de requisições feitas pelo Web Browser, ele mostra os detalhes dessa requisição, mostrando primeiro definições gerais, e logo depois em “Request Headers” tudo o que ele enviou no HTTP Header da requisição realizada — são muitas informações. As duas mais importantes agora são “Content-Type: application/x-www-form-urlencoded” , e “Content-Length: 23“.

Elas indicam que essa requisição de POST possui um Http Body, onde o conteúdo possui 23 bytes, e está CODIFICADO no formato “x-www-form-urlencoded“. Agora, se “rolarmos” essa tela para baixo, veremos algo interessante no final da página:

O Web Browse mostra que os valores preenchidos nos campos de input dentro do formulário foram enviados … Se clicarmos em “view source”, veremos COMO ele realmente enviou o corpo (Http Body) dessa requisição:

CAMPO1=Ola&CAMPO2=Mundo

Agora, conte quantas letras e símbolos aparecem nessa linha … Exatamente 23, que é o Content-length indicado no HTTP Header 😀 Após o HTTP Header, foi enviado um HTTP Body.

E, você também reparou ? Os dados estão “codificados” como se fossem os dados de uma requisição do tipo GET — URL Encoded … por isso o Web Browser informou que o Content-Type é “x-www-form-urlencoded” ( dados codificados igual a uma URL).

E AGORA ?

Faça um teste… volte para a página anterior, e no lugar da palavra “Mundo”, digite “Você S&A”.. e veja novamente nos detalhes da requisição, como os dados foram enviados pelo Web Browser:

CAMPO1=Ola&CAMPO2=Voc%EA+S%26A

Repare QUATRO coisas importantes:

  • Ao usar um caractere acentuado (ê – letra e minúscula com acento circunflexo), ela foi enviada utilizando o PERCENT ENCODING contendo o valor hexadecimal “EA” (recomendo fortemente a leitura de O que é CODEPAGE e ENCODING – Parte 01 e demais posts). Se consultarmos a tabela de caracteres do Windows (em português ou inglês, CP1252 ou CodePage 1252), veremos que o código Hexadecimal EA representa a letra “ê”
  • O espaço em branco foi codificado com o sinal de “+” (mais)
  • Como usamos no conteúdo do texto um caractere separador de parâmetros do protocolo “&”, ele foi codificado como “%26” — que na prática é o código hexadecimal do caractere “&”
  • E , por fim, o Content-length aumentou para 33 … veja, o tamanho do HTTP Body é o numero de bytes usados para representar os dados CODIFICADOS.

Mais informações, mais …

Se você ligar o log de requisições HTTP do Totvs Application Server, você vai reparar que o conteúdo do POST não e mostrado no log … ele não é mostrado pois pode ser muito grande, e codificado de formas que ele não pode ser visualmente visto no log, por isso o LOG das requisições recebidas mostra apenas o HTTP Header no Application Server.

Já existem outros posts sobre AdvPL ASP, mas que não entram “tão fundo” no protocolo … se voce quiser complementar seus conhecimentos, recomendo a leitura a partir do Protheus e AdvPL ASP – Parte 01 e demais posts da seqüência. Mais para frente vamos abordar boa parte do que foi mostrado sobre AdvPL ASP aqui no protocolo HTTP, porém com uma visão mais “interna” de como as coisas funcionam 😀

F.A.Q.

E, como cada post pode levantar muitas dúvidas, vamos ver algumas… respondidas de forma simples.

  • Esses exemplos de HTML, estão “corretos” ? Que versão de HTML esses arquivos estão usando ?

Então, os exemplos funcionam, o Web Browse “engole”, mas eles não estão usando as melhores práticas de codificação. Por convenção, a primeira linha de um arquivo HTML “elegante” informa a versão do HTML utilizada. Para indicar o uso de HTML5, a primeira linha do arquivo HTML deve ser

<!DOCTYPE html>

A partir de agora, nossos exemplos vão ser “elegantes”, e declarar o uso de HTML5.

  • Por quê foi usado o CodePage CP-1252 ? Em nenhum lugar isso aparece ?

Essa é a pergunta de 1 milhão ! No meio do HTTP Header da requisição feita pelo Web Browser, ele informa em alguns pontos o que ele “aceita” de retorno …

Accept-Encoding: gzip, deflate, br
Accept-Language: pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,sv;q=0.5

Como o Browser está configurado para Português do Brasil, rodando em uma máquina Windows, e em nenhum momento o HTTP Server informa um detalhamento sobre quais caracteres estão sendo utilizados — ao pedir o arquivo “default.htm” ou “testepost.htm”, é informado no HTTP Header apenas que o content-type é “text/html”, sem mais detalhes inclusive no corpo do HTML — o Web Browser assume que ele deve tratar o retorno baseado no que ele aceita .. e trata os caracteres acentuados enviados na página como “CP1252”.

  • CP-1252, WINDOWS-1252 e ANSI são a mesma coisa ?

R: Na prática, SIM. Em versões anteriores do Windows, os CodePages utilizados pelo DOS ( 437 e 850, para inglês e português, respectivamente) foram nomeados como code-pages OEM, e a nova tabela ASCII do Windows como ANSI, e posteriormente WINDOWS-1252 e CP-1252. Existe um codepage muito parecido com o CP1252, usado ainda em algumas aplicações, chamado IS8-8859-15 — mas que possui diferenças na interpretação gráficas dos caracteres em faixas específicas. ** Curiosidade: Desde a primvera versão do Totvs Application Server, em meados de 1999, para recompilar e executar os códigos do ERP da Microsiga – ainda escritos para MS-DOS — havia a necessidade de conversões de OEM para ANSI e vice-versa, desde arquivos de texto e dados. Para isso, foram disponibilizadas as funções OemToAnsi() e AnsiToOem() na linguagem AdvPL, que estão presentes desde então.

  • Posso usar UTF8 dentro do meu arquivo HTML ?

SIM, mas isso exige que você informe dentro do HTML que os caracteres do corpo do HTML ( HTML Body – não confunda com HTTP Body, o corpo do HTML começa após a declaração “<body>” e termina na declaração “</body>” ). Vamos entrar em mais detalhes disso mais para a frente, mas a título de curiosidade, uma das formas de você especificar que o texto dentro do seu HTML está codificado como UTF-8, é acrescentar antes do <body> a sequencia de linhas abaixo:

 <head>
 <meta charset="UTF-8">
 </head>

Quer fazer um teste ? Abra o arquivo “testepost.htm” com um editor um pouco mais avançado ( recomendo o NOTEPAD++), troque a codificação do arquivo para UTF-8, e coloque acento na palavra início … faça isso, salve e reabra a página no Web Browse — sem colocar o meta-charset .. Você vai ver que no lugar do “í” vão aparecer duas letras esquisitas … ( UTF8 interpretado erroneamente como CP1252), e depois de acrescentar as linhas head e meta-charset, salvar e recarregar o arquivo pelo Web Browser … o acento aparece certinho ! Depois vamos ver como fazer isso usando o HTTP Header 😀

*** Usar UTF-8 traz outras implicações, que veremos mais adiante. A mais imediata é que, ao abrir um campo de digitação de informação para o usuário preencher, os dados enviados para o servidor HTTP estarão codificados em UTF-8. E isso exige que você trate isso adequadamente ao receber os dados para a execução de páginas dinâmicas. Não se preocupe com isso agora, veremos esses detalhes mais para a frente.

  • Existem outros Content-Type usados para POST ?

R: SIM, para muitas outras finalidades. Normalmente o Web Browser usa o Content-Type “multipart/form-data” para submeter arquivos (POST de formulários HTML com upload de arquivos para o HTTP Server), e diversos outros Content-Types são usados na comunicação entre aplicações por serviços SOAP ou REST sobre HTTP, como text/xml, text/json … calma que veremos isso em detalhes mais pra frente.

Conclusão

Quanto mais informações são apresentadas, mais informações são necessárias. Aos poucos vamos abrindo o que tem por baixo do capô do Web Browse e dos protocolos, e vamos ver várias formas de se fazer muita coisa ! Espero que este conteúdo sempre lhe seja útil, e novamente desejo a todos TERABYTES DE SUCESSO ❤

Referências

O Protocolo HTTP – Parte 04

Introdução

No post anterior, vimos as requisições GET que um Web Browse fez a um Totvs Application Server configurado como um servidor de páginas estáticas, e mais detalhes do protocolo, criando uma página html bem simples, e colocando ela como “tela de entrada” do site. Agora, vamos ver como fazer um programa AdvPL recuperar essa página do Web Site.

Função HTTPGET()

De uma forma simples, a função HttpGet() do AdvPL permite ao servidor de aplicação conectar em um Web Site, e recuperar um conteúdo usando a instrução GET do protocolo HTTP. Vamos ver isso um pouco mais a fundo, usando o Protheus Server como servidor de HTTP, e também dando atenção a funções auxiliares para os tratamentos dos possíveis retornos, entre outros truques interessantes. Para isso, vamos começar com um programinha bem simples:

#include "protheus.ch"

USER Function Myget1()
cHtml := HttpGet("http://localhost/")
conout("HTTP Body retornado:")
conout(cHtml)
return

Antes de mais nada, remova ou comente as configurações de log de requisições da seção http, senão o log vai ficar muito cheio … Ao compilar e executar o programa acima — pelo SmartClient , e verificar o log de console do Application Server, no nosso ambiente HTTP montado no post anterior, devemos obter o seguinte resultado:

HTTP Body retornado:
<html>
<body>
Meu primeiro Web Site 
</body>
</html>

Então, onde estão os Headers de retorno ?

A função HttpGet() retorna o “Html Body” retornado pelo Servidor Http acessado, mas também é possível retornar o Header da requisição usando o 5o parâmetro por referência.. vamos melhorar o programa, criando agora a função MyGet2():

USER Function Myget2()
cHeadRet := ''
cHtml := HttpGet("http://localhost/",,,,@cHEadRet)
conout("HTTP Header retornado:")
conout(cHEadRet)
conout("HTTP Body retornado:")
conout(cHtml)
return

Agora, ao executar o programa, vamos conferir o retorno:

HTTP Header retornado:
HTTP/1.1 200 OK
Date: Sat, 23 Jan 2021 02:33:11 GMT
Server: Application Web Server
MIME-version: 1.0
Content-type: text/html
Last-modified: Fri, 22 Jan 2021 23:33:29 GMT
Content-length: 55
X-Frame-Options: SAMEORIGIN

HTTP Body retornado:
<html>
<body>
Meu primeiro Web Site 
</body>
</html>

E se houver um erro ? O que pode dar errado ?

Então, muita coisa pode dar errado, desde não ser possível estabelecer a conexão HTTP com o host/servidor informado na URL, até ser possível estabelecer a conexão, mas o servidor não entender o pedido, ou não conseguir processar a requisição … Vamos começar com uma requisição de algo que não existe, alterando a linha 2 desse fonte, para pedir o arquivo “naoexiste.htm”:

cHtml := HttpGet("http://localhost/naoexiste.htm",,,,@cHEadRet)

Agora, vamos ver o que é retornado ao executar esse código, no log de console:

HTTP Header retornado:

HTTP Body retornado:
NIL

Então, com esse retorno a gente fica simplesmente “a pé”, não temos idéia do que aconteceu … Vimos apenas que o Header retornado por referencia está vazio, e o corpo HTTP retornado para a variável cHTML não contém uma string, mas o valor “NIL” (nulo) AdvPL.

Agora, vamos usar algumas funções auxiliares de tratamento de retorno de requisição.. e criar a função MyGet3():

USER Function Myget3()
  cHeadRet := ''
  cHtml := HttpGet("http://localhost/naoexiste.htm",,,,@cHEadRet)

  // Apos chamar a função HttpGet(), podemos obter alguns resultados \
  // usando algumas funções auxiliares 
  cHttpStatus := ''
  nHttpCode := HTTPGETSTATUS(@cHttpStatus)

  conout("HTTP Return Code ... "+cValToChar(nHttpCode))
  conout("HTTP Return Status ... "+cValToChar(cHttpStatus))

  If empty(cHEadRet)
    conout("HTTP Header nao retornado...")
  Else
    conout("HTTP Header retornado:")
    conout(cHEadRet)
  Endif

  if empty(cHtml)
    conout("HTTP Body nao retornado...")
  Else
    conout("HTTP Body retornado:")
    conout(cHtml)
  Endif

return

E, agora, ao executar esse código, temos algumas novas informações:

HTTP Return Code … 404
HTTP Return Status … Requested file not found
HTTP Header nao retornado:
HTTP Body nao retornado…

Repare que agora a gente sabe o que aconteceu … o status 404 retornado indica que a página solicitada (naoexiste.htm) realmente não existe no servidor … risos … Agora, vamos mexer no fonte novamente, para buscar explicitamente a página de entrada do site, chamada “default.htm”.

cHtml := HttpGet("http://localhost/default.htm",,,,@cHEadRet)

Agora, ao rodar o U_MYGET3 com esta alteração, obtemos o seguinte resultado:

HTTP Return Code … 200
HTTP Return Status … OK

( o resto está igual ) 

E se der erro de conexão ?

Vamos ver … alteramos o programa para buscar a página “default.htm”, mas agora vamos colocar uma porta DIFERENTE da 80 … Simples assim:

cHtml := HttpGet("http://localhost:8080/default.htm",,,,@cHEadRet)

Se não tiver nenhum serviço HTTP na porta 8080 do seu computador, essa requisição deve falhar .. então, vamos ver o resultado da execução do programa com esta alteração no log de console:

HTTP Return Code … 10061
HTTP Return Status … Connection refused.
HTTP Header nao retornado…
HTTP Body nao retornado…

O erro retornado não é do HTTP, mas da interface de SOCKETS do sistema operacional … A conexão foi “recusada” pois não tem nenhum serviço “ouvindo” na porta 8080. Agora, vamos colocar um nome de host ou de um site que não existe …

cHtml := HttpGet("http://naoexiste/default.htm",,,,@cHEadRet)

E, temos então o seguinte retorno:

HTTP Return Code … 11001
HTTP Return Status … Host not found.
HTTP Header nao retornado…
HTTP Body nao retornado… ... 

E os outros parâmetros ?

Sim, tem vários …. Vamos ver primeiro o segundo parâmetro da função. Ele chama-se “cGetParms” — vide documentação completa na TDN no link https://tdn.totvs.com/display/tec/HTTPGet — e serve para a passagem de parâmetros pela URL.

A operação de GET do HTTP permite eu solicitar um arquivo ou página ao servidor HTTP, e na própria URL podemos colocar um ou mais parâmetros, na forma “?chave=valor[,chave2=valor2…]” . Estes parametros são conhecidos como parâmetros de busca ou “search parameters”. E existem duas formas de passar estes parâmetros no AdvPL : Eles podem estar diretamente na URL — informada no primeiro parâmetro — ou separados da URL, no segundo parâmetro da chamada da função HttpGet() — mas não podem estar em ambas ao mesmo tempo.

A passagem de parâmetros via URL deve ser feita após a página solicitada, usando o separador “?”(interrogação), seguido de um identificador ou chave, o sinal de igual “=” e um determinado valor. Para especificar mais combinações de chave e valor, use o separador “&” (e comercial). Por exemplo:

http://localhost/default.htm?id=no&page=1

Dessa forma, estou solicitando uma requisição para o protocolo HTTP, para o host “localhost”, solicitando a página “default.htm”, e informando pela URL os parâmetros “id” com o valor “no”, e “page” com o valor “1”. A nossa página default não tem avaliação de parâmetros, então qualquer coisa informada será “ignorada”, mas será transmitida ao HTTP Server.

Para passar a URL acima para a função HTTPGET, podemos passar os parâmetros juntos da URL ou separados. Vale lembrar que, não é necessário colocar a “?” interrogação para passar os parâmetros separados, veja abaixo os exemplos, o resultado final será o mesmo :

cReturn := HttpGet("http://localhost/default.htm?id=no&page=1")
cReturn := HttpGet("http://localhost/default.htm","id=no&page=1")

Observações muito importantes

Além dos separadores a serem utilizados para indicar o inicio dos parâmetros de busca (“?”), o separador entre o identificador (ou chave) “=” e o separador para informar mais parâmetros “&”, existem alguns pontos muito importantes:

  • Nos parâmetros de busca, em uma chave deve-se evitar usar caracteres especiais, símbolos e acentuação.
  • Caso seja necessário o uso de caracteres deferentes do alfabeto alfanumérico (letras maiúsculas e/ou minúsculas não acentuadas e números), quaisquer caracteres diferentes disso devem ser escritos em um formato chamado “URL Encoding” ou “Percent Encoding”

PERCENT QUEM ?

Cada caractere especial ou diferenciado pode ser convertido no formato “%HH”, onde HH é a representação HEXADECIMAL — com letras maiúsculas — do código ASCII do caractere a ser enviado. Por exemplo, se eu quero passar a chave “nome” com o valor “C&A” — lembrando que “&” é um caractere separador da lista de parâmetros, eu devo obter o código ASCII da letra “&” — que é 38 — e converter para hexadecimal ( 26 ), então informar na URL http://localhost/default.htm?nome=C%26A

Outro detalhe importante, é que o caractere “espaço em branco”, pode ser passado de duas formas como parâmetro da URL: Pode ser %20, ou o sinal de soma “+”.

E como isso entra no HTTP Header ?

Simples assim, todos os parâmetros passados por GET são colocados na primeira linha do HTTP Header:

GET /default.htm?id=no&page=1 HTTP/1.1
Host: localhost
(... other fields ...)

Isso tem limite ?

Essa é uma pergunta importante… sim, têm limite sim. Mas o limite é arbitrário, depende da capacidade do servidor HTTP e do Web Browser utilizado. Em linhas gerais, a recomendação é que não se use mais do que 2000 caracteres em uma URL. A maioria dos Web Browses e HTTP Servers lida tranqüilamente dentro desse limite. Veja mais informações das referências.

Posso passar dados sensíveis pela URL ?

Então, poder você pode, mas não deve … afinal os dados ficam expostos na URL … para passar dados sensíveis, e maiores que 2000 bytes, veremos mais pra frente o comando POST do protocolo HTTP, que permite receber parâmetros pela URL (igual ao GET), mas também permite receber parâmetros pelo HTTP Body 😀

Conclusão

Aos poucos vamos mergulhando no HTTP, e ao mesmo tempo vamos ver o que o AdvPL nos oferece para lidar com essa camada. Espero que as informações lhe sejam muito úteis, e como de costume lhes desejo TERABYTES DE SUCESSSO !!!

Referências