Utilizando THF com AdvPL – Parte 01

Introdução

O THF (Totvs Html Framework) é um projeto aberto e colaborativo de um Framework para aplicações WEB responsivas e dispositivos móveis. Baseado em Angular e Typescript, disponibiliza uma série de componentes que facilitam a criação de aplicações. O site oficial com tudo sobre o THF é https://thf.totvs.com.br/

É altamente recomendável que você tenha algum conhecimento sobre Angular e TypeScript, senão você corre o risco de ficar meio perdido — assim como eu fiquei — ao tentar usar o THF.

Iniciando

Basta seguir o tutorial do site para instalar o NodeJS e NPM, e o AngularCli. Sem truques, apenas next-next-finish. Uma vez instaladas as aplicações necessárias, uma dica importante na criação de um novo projeto — Ao configurar as dependências do projeto criado para instalação, é disponibilizada uma lista de chaves e valores das dependências no site. Para atualizar o arquivo package.json que foi criado com o novo projeto, procure cada chave informada no site no arquivo gerado para o seu projeto. Caso a chave já exista, atualize o valor da versão, e caso a chave não exista, acrescente ela no arquivo.

Criação do Projeto AgendaTHF

Em posts anteriores, sobre o CRUD AdvPL, foi construída uma agenda simples de contatos para ser acessada via SmartClient. Então, num projeto posterior, foi construída uma interface em AdvPL ASP usando HTML/CSS e JavaScript “puro”, sem framework algum. Vamos ver agora como seria a criação de uma interface para esta Agenda usando o Totvs Html Framework.

Todas as etapas para a criação do projeto foram feitas de acordo com as instruções detalhadas no tópico “Começando com o THF”, inclusive as etapas foram seguidas até o final, com a criação do componente “hello-world” e de um menu do THF para chamá-lo na tela.

Ao criar um novo projeto, devemos abrir um prompt de comando do Windows (cmd) e usar  o comando abaixo: — onde AgendaTHF é o nome do projeto.

ng new AgendaTHF --skip-install

Após reconfigurar as dependências e versões de dependências no arquivo package.json, gerado dentro da nova pasta AgendaTHF — criada automaticamente para o Projeto — executamos o comando de instalação das dependências, e depois a instalação do THS neste projeto, executando os seguintes comandos dentro da pasta AgendaTHF:

npm install
npm install @totvs/thf-ui --save

Não se assuste, a instalação de tudo pode demorar mais de um minuto. As sub-pastas com os componentes instalados somam quase 300 MB de arquivos.

Configuração do módulo principal e demais etapas

Procure o arquivo “app.module.ts“, ele deve estar dentro da pasta “AgendaTHF\src\app“. Neste arquivo é que deve ser acrescentado o import do THFModule — os detalhes estão no Passo 2.2 do tutorial “Começando com o THF”

Os demais procedimentos são relativamente simples, ligados a edição dos arquivos modelo gerados para a aplicação, para adequá-lo ao uso do THF. A criação do componente “hello-world” é feita pelo prompt de comando ma pasta raiz do projeto (AgendaTHF), e o resultado final — depois de colocar um servidor http no ar usando o comando “ng serve”, deve ser algo assim:

THF Hello World

Teste do Hello World

A primeira impressão é muito bonita, e logo percebe-se a responsividade da interface alterando o tamanho da janela do Browser. Em tela maximizada, a página do Hello World é aberta na área de conteúdo e o menu continua visível. Ao reduzir o tamanho da interface, o menu é automaticamente “escondida”, sendo acessível novamente por um link na parte superior esquerda.

THF Hello World Option

Atualmente existem aproximadamente 81 componentes disponíveis para você codificar uma aplicação responsiva usando o THF — Vide índice de API no link “https://thf.totvs.com.br/documentation“. Além dos componentes, são disponibilizados Serviços, Diretivas, Modelos de Dados — para armazenamento em dispositivo local — enfim, têm muita coisa.

A pasta criada com este projeto é para desenvolvimento. No momento de fazer a publicação, deve-se executar uma instrução de build, explicada no portal do THF, que vai gerar uma pasta com os arquivo estáticos e scripts. A publicação da aplicação exige apenas um servidor HTML estático, o Protheus como servidor HTTP atende perfeitamente — exceto por alguns detalhes de configuração de página não encontrada, que precisam ser customizados.

Conclusão

Este artigo ficou na banheira desde o Natal de 2018, provavelmente a documentação já deve ter mais atualizações e componentes. O projeto da Agenda usando o THF vai ser continuado ao longo dos próximos posts, em breve 😀

Novamente agradeço a audiência e desejo a todos TERABYTES DE SUCESSO !!! 

Referências

 

CRUD em AdvPL ASP – Parte 04

Introdução

No post anterior (CRUD em AdvPL ASP – Parte 03), foi implementada a consulta básica da Agenda em ADVPL ASP — pelo menos os quatro botões de navegação (Primeiro, Anterior, Próximo e Último). Agora, vamos ver como mostrar no Browse a imagem de cada contato, gravada no Banco de Dados.

Trocando o retorno para o Browse

Qualquer link apw solicitado ao Web Server do Protheus executará uma função AdvPL definida em uma configuração de pool de processos, que por default devem retornar uma string em AdvPL, que será interpretada pelo Web Browse como sendo texto HTML.

Este comportamento default é implementado pelo Web Server, que por default informa no Header HTTP de retorno da requisição a informação Content-Type: text/html — Quando utilizamos a função AdvPL HTTPCTTYPE(), dentro do processamento de uma requisição AdvPL ASP, nós podemos TROCAR o tipo do conteúdo de retorno. Por exemplo, se eu quiser retornar uma imagem do tipo PNG para o Web Browse, a partir de uma requisição de link .apw, basta eu chamar a função HttpCTType(“image/png”), e ao invés de retornar ao Browse um HTML, eu retorno o conteúdo (bytes) do arquivo da imagem.

Logo, vamos implementar o retorno da imagem de forma BEM SIMPLES. Primeiro, vamos aproveitar a requisição “agenda.apw”, e verificar se ela recebeu um identificador adicional na URL, que vamos chamar de IMGID. Este identificador vai conter o numero do registro do contato da agenda que nós gostaríamos de recuperar a imagem. E, no fonte WAGENDA.PRW, vamos acrescentar o seguinte tratamento, logo no inicio da função WAGENDA() — pouco depois de abrir a tabela ‘AGENDA” no Banco de Dados.

If !empty(HTTPGET->IMGID)
  DbSelectArea("AGENDA")
  Dbgoto( val(HTTPGET->IMGID) )
  cBuffer := AGENDA->IMAGE
  HTTPCTType('image/png') 
  Return cBuffer
Endif

Simples assm, caso seja recebida a URL agenda.apw?IMGID=nnn, o programa vai posicionar no registro correspondente da tabela de agenda, ler o conteúdo da imagem gravada no campo memo “IMAGE”, e retornar ela ao Browser, avisando antes pela função HTTPCTType() que o Browse deve interpretar este conteúdo como uma IMAGEM.

Agora, dentro do fonte WAGENDA.APH, que compõe a página da agenda, vamos fazer uma alteração na tag “img”, responsável por mostrar a imagem do contato atual da agenda.

<tr><td>
<%If HTTPPOST->_SHOWRECORD .and. !Empty(AGENDA->IMAGE) %>
<img style="width: 120px;height: 160px;" src="agenda.apw?IMGID=<%=cValToChar(recno())%>">
<% else %>
<img style="width: 120px;height: 160px;" src="./images/Agenda_3x4.png">
<% Endif %>
</td></tr>

Dentro do APH em questão, eu já estou com a tabela da Agenda aberta. Caso eu vá mostrar o registro de algum contato no Browse, e o campo de imagem deste contato possua conteúdo, eu coloco que a imagem deve ser buscada no endereço

agenda.apw?IMGID=<%=cValToChar(recno())%>

Desta forma, quando o Browse receber o HTML de retorno de uma página da Agenda, o Browse vai desenhar a tela, e na hora de mostrar a imagem, o Browse internamente dispara mais uma requisição para a a URL agenda.apw, informando via URL o identificador da imagem desejada.

Vou fazer um teste no Browse, por exemplo retornando a foto do registro 2 da agenda no meu ambiente, digitando direto a URL “http://localhost/agenda.apw?IMGID=2

WEB Agenda IMGID

Ao navegar na agenda, e posicionar no contato, a imagem passa a ser atualizada na tela, veja exemplo abaixo:

WEB Agenda Imagem

Conclusão

Utilizando a troca do retorno, eu consigo informar ao browser o que eu estou retornando, e isso me permite eu retornar por exemplo XML, Imagens, Documentos, o céu (e a String AdvPL) são o limite!

Agradeço novamente a audiência, e desejo a todos TERABYTES DE SUCESSO !!! 

Referências

 

CRUD em AdvPL ASP – Parte 03

Introdução

No post anterior (CRUD em AdvPL ASP – Parte 02), foi mostrado um pouco da forma como as páginas de uma aplicação WEB foram e são atualizadas e o que é o AJAX, e a primeira verão — apenas desing — da página da Agenda em HTML. Agora, vamos começar a adaptar alguns fontes da Agenda para utilizá-los no AdvPL ASP.

Separar processamento da interface

Este é um dos mandamentos do bom desenvolvimento de sistemas. O projeto do CRUD em AdvPL, com foco em uma agenda de contatos, foi escrito da forma mais simples de desenvolver um sistema: O mesmo código é responsável pela interface e pelo processamento e acesso aos dados.

Embora seja uma das formas mais simples e rápidas de desenvolver, o processamento depende das interface, e isso acaba dobrando o trabalho para você criar uma nova interface para fazer as mesmas tarefas. No exemplo do CRUD, o processo em execução é persistente, isto é, o SmartClient possui uma conexão com o Protheus Server, e nesta conexão o arquivo da agenda é mantido aberto e posicionado, e a conexão da aplicação é mantida junto com o seu contexto até o usuário finalizar a interface.

Já em uma aplicação Web, cada chamada de atualização de interface cria uma nova conexão com o servidor de aplicação, que pode ser atendida por qualquer um dos processos (também chamados de Working Threads, ou processos de trabalho), então não há persistência do contexto de execução. Existem apenas as variáveis de seção (HTTPSESSION), onde você pode armazenar informações que são exclusivas daquela seção de navegação, que como vimos nos tópicos sobre AdvPL ASP, podem ser usadas por exemplo para guardar a existência por exemplo de uma seção de login.

Logo, como o paradigma da nova interface não parte de uma conexão persistente, precisamos desamarrar o processamento da interface, e criar algum mecanismo de persistência de um mínimo de contexto, ou emular a persistência de um contexto, para que uma ação disparada pela interface saiba de onde partir para ser executada.

A separação entre processamento e interface será feita em doses homeopáticas, vamos primeiro fazer a implementação funcionar com o que temos disponível implementando alguns pontos por vez.

Iniciando o processo de consulta

Como já temos a agenda criada em AdvPL, e com uma base preenchida, vamos começar a interface WEB pela consulta dos dados da agenda. Partimos de uma tabela no banco de dados, com dois índices previamente criados. Vamos iniciar a visualização dos dados a partir do primeiro registro da tabela em ordem alfabética. Para isso, inicialmente vamos replicar (duplicar) alguns códigos na interface WEB, para realizar as operações de consulta.

Uma vez que seja mostrada ao usuário uma tela com os dados preenchidos, o processamento daquela requisição terminou. Logo, quando eu clicar por exemplo no botão “Próximo” — para mostrar o próximo registro da agenda — eu preciso pelo menos saber qual era o registro que estava sendo visualizado, para eu saber de onde partir para buscar o próximo registro.

Eu até poderia usar variáveis de seção (HTTSESSION), mas como o objetivo é minimizar o uso destas variáveis, eu posso criar dentro do meu formulário — por exemplo — um input em HTML, do tipo “hidden” — ou escondido — onde eu coloco o numero do registro atual que eu estou visualizando, no momento que eu gerei a página a ser retornada para o Browse.

Conectando com o DBAccess

Antes de mais nada, eu preciso que as minhas Working Threads tenham acesso ao Banco de Dados. Lembram-se da função U_ASPINIT(), executada para preparar cada um dos processos para AdvPL ASP? Vamos alterá-la para ela estabelecer logo de cara uma conexão com o DBAccess, que será mantida no processo e aproveitada por todas as requisições que precisarem usar o SGDB. Esta função fica no fonte ASPThreads.prw

User Function ASPInit()
Local nTopHnd

SET DATE BRITISH
SET CENTURY ON

nTopHnd := TCLInk()
If nTopHnd < 0 
  ConsoleMsg("ASPINIT - Falha de conexão "+cValToChar(nTopHnd))
  Return .F.
Endif

SET DELETED ON

ConsoleMsg("ASPINIT - Thread Advpl ASP ["+cValToChar(ThreadID())+"] Iniciada")

Return .T.

Reparem que apenas o trecho em negrito foi acrescentado na função. Agora, vamos implementar alguns controles no programa responsável pela agenda na interface AdvPL ASP — Fonte wagenda.prw

Implementando a consulta nos fontes

Dentro da User Function WAGENDA(), responsável pela interface HTML da Agenda para AdvPL ASP, vamos abrir o alias da tabela da Agenda, caso ele ainda não esteja aberto dentro do contexto do processo de trabalho (Working Thread) atual.

If Select("AGENDA") == 0 
   USE (cFile) ALIAS AGENDA SHARED NEW VIA "TOPCONN"
   DbSetIndex(cFile+'1')
   DbSetIndex(cFile+'2')
Endif
DbSelectArea("AGENDA")

Partimos de duas premissas. A primeira é que a tabela e os índices existem. E, a segunda é que, uma vez que eu deixe o alias da tabela aberto neste processo, as próximas requisições que precisarem consultar esta tabela vão encontrar ela já aberta.

As operações feitas no CRUD em AdvPL foram numeradas. Vamos seguir a mesma numeração. Como por hora estamos implementando apenas a consulta de dados, as operações são: 4 – Primeiro contato, 5 = Contato anterior, 6 = Próximo contato e 7 = Último contato. Para este controle, vamos usar um campo input de formulário escondido no HTML, chamado “OP”. Caso este campo não seja recebido na requisição, eu assumo que a agenda está sendo aberta, e que a operação default é “4” = Primeiro registro.

If empty(HTTPPOST->OP)
  // Caso nao tenha sido informada operação, a operação 
  // default é posicionar no primeiro registro da tabela
  // em ordem alfabérica
  HTTPPOST->OP := '4'
Endif

Antes do fonte chamar a montagem da tela da agenda, encapsulada no arquivo wagenda.aph — correspondendo a chamada de função H_WAGENDA() em AdvPL — vamos tratar as quatro opções possíveis.

If HTTPPOST->OP == '4' // Primeiro

  DBSetOrder(2)
  Dbgotop()
  HTTPPOST->_SHOWRECORD := .T.

ElseIf HTTPPOST->OP == '5' // Anterior

  DBSetOrder(2)
  Dbgoto( val(HTTPPOST->RECNO) )
  DbSkip(-1)
  IF bof()
    HTTPPOST->_ERRORMSG := 'Este é o primeiro contato.'
  Endif
  HTTPPOST->_SHOWRECORD := .T.

ElseIf HTTPPOST->OP == '6' // Próximo

  DbSetOrder(2) 
  Dbgoto( val(HTTPPOST->RECNO) )
  DbSkip() 
  If Eof()
    Dbgobottom()
    HTTPPOST->_ERRORMSG := 'Este é o último contato.'
  Endif
  HTTPPOST->_SHOWRECORD := .T.

ElseIf HTTPPOST->OP == '7' // Último

  DBSetOrder(2)
  Dbgobottom() 
  HTTPPOST->_SHOWRECORD := .T.

Endif

Dentro deste fonte nós criamos duas variáveis dentro do alias virtual HTTPPOST, chamadas de _SHOWRECORD e _ERRORMSG. Estas variáveis foram criadas desta fora para servirem como containers de troca de valores entre o programa AdvPL que estamos rodando agora, e a página AdvPL ASP que vamos rodar em seguida.

Cada operação já assume que o Alias da tabela de AGENDA está aberto, e realiza o posicionamento do registro correspondente. Para fazer o posicionamento no registro anterior ou no próximo do registro mostrado no Web Browse, vamos no AdvPL ASP criar um campo do tipo INPUT HIDDEN, escondido em um formulário de controle invisível, e cada requisição de POST feita para trazer um novo registro vai receber o registro que estava sendo visto no Browse naquele momento.

Implementando no AdvPL ASP

Agora que já preparamos as operações dentro do fonte AdvPL, vamos preparar o fonte AdvPL ASP para tratar a existência destes dados e as operações solicitadas. Primeiro, dentro do fonte WAGENDA.APH, na parte de Scripts no inicio da página, vamos acrescentar duas funções novas em JavaScript.

function onLoad()
{
<%If HTTPPOST->_SHOWRECORD %>
<% aStru := DbStruct() %>
<% For nI := 1 to fCount() %>
<% If aStru[ni][2] != 'M' %>
document.getElementById("I_<%=alltrim(fieldname(nI))%>").value = "<%=cValToChar(fieldget(nI))%>";
<% Endif %>
<% Next %>
<% Endif %>
<% IF !empty(HTTPPOST->_ERRORMSG) %>
window.alert("<%=HTTPPOST->_ERRORMSG%>");
<% Endif %>
};

function CallOp(nOp)
{
document.getElementById("I_OP").value = nOp;
var f = document.getElementById("F_STATUS"); 
f.action="agenda.apw"
f.submit();
}

A primeira função chama-se onLoad(), e vamos fazer o Browse chamá-la imediatamente após a carga da página, inserindo na tag de abertura do corpo da página HTML (body) a chamada desta função:

<body onload="onLoad()" style="font-family:Courier New;font-size: 12px;background-color:rgb(128,128,128);">

O corpo da função onLoad() será montado dinamicamente para cada requisição. Seu objetivo é, quando houver a visualização de um determinado registro, este script vai encontrar todos os campos INPUT da página, destinados a mostrar os valores dos campos da Agenda, e preencher estes campos com os valores lidos do Banco de Dados.

Já a função CallOp() será chamada em JavaScript pelos botões de navegação de consulta na página HTML. Os novos botões vão ficar assim:

<tr><td><a class="agbutton" href="javascript:void(0)" onclick="CallOp(4)">Primeiro</a></td></tr>
<tr><td><a class="agbutton" href="javascript:void(0)" onclick="CallOp(5)">Anterior</a></td></tr>
<tr><td><a class="agbutton" href="javascript:void(0)" onclick="CallOp(6)">Próximo</a></td></tr>
<tr><td><a class="agbutton" href="javascript:void(0)" onclick="CallOp(7)">Último</a></td></tr>

E, finalmente, antes do final do corpo do HTML (/body), vamos acrescentar um formulário de controle, para através dele fazer as requisições via POST — para não deixar rastro, cache ou histórico no Web Browse — das operações solicitadas de navegação.

<form id="F_STATUS" action="agenda.apw" method="post">
<input type="hidden" id="I_OP" name="OP" type="text" value="">
<input type="hidden" id="I_RECNO" name="RECNO" type="text" value="<%=cValToChar(recno())%>">
</form>

Neste formulário vamos criar apenas dois campos de INPUT HIDDEN, um para guardar a operação a ser solicitada, e outro para guardar o registro que está atualmente sendo visualizado. No momento que eu pedir por exemplo o próximo registro, o botão “Próximo” vai executar a função javascript CallOp(6), que vai preencher o input “I_OP” com o valor “6” e submeter novamente o formulário para o link “agenda.apw

Desta forma, o programa U_WAGENDA() vai receber a operação e o registro anteriormente posicionado, reposicionar no registro, fazer a navegação, e se por um acaso não houver registro anterior ou próximo, será mostrada na tela uma mensagem de alerta no Browse, repare no finalzinho da função onLoad().

Implementação funcionando

Com tudo atualizado e compilado, o comportamento esperado do acesso ao link http://localhost/agenda.apw deve ser:

  • Caso o usuário não esteja logado na seção atual, será exibida a tela de login
  • Após o login, deve ser exibida a tela da agenda com o primeiro contato em ordem alfabética.
  • Os quatro botões de navegação devem funcionar atualizando a página inteira e trazendo o contato correspondente.

WEB Agenda 3

Houve mais uma alteração importante no arquivo WAGENDA.APH: Todos os campos INPUT do formulário, que antes estavam com a propriedade “disabled”, passaram para “readonly”. Caso um campo não esteja habilitado, ele sequer pode receber ou mostrar um valor novo.

Conclusão

Aos poucos vamos implementando as funcionalidades da agenda original. Normalmente eu começaria pelo mais complicado — API de Cadastro — porém, como o estado default da interface é a consulta, vamos começar por ela. No próximo post, vamos fazer a consulta atualizar a foto do contato da agenda, sem precisar gravar a imagem que está no banco de dados na pasta de publicações Web.

Agradeço a todos novamente as curtidas, compartilhamentos, comentários e afins, e lhes desejo TERABYTES DE SUCESSO !!!

Referências

 

CRUD em AdvPL ASP – Parte 02

Introdução

No post anterior (CRUD em AdvPL ASP – Parte 01), montamos um controle de login, usando um formulário em AdvPL ASP, e uma variável de seção (HTTPSESSION). E, para servir de base para a continuação do CRUD, foram publicados uma sequência de posts para abordar o “básico” do AdvPL ASP:

Agora, vamos começar a montar a interface em HTML e JavaScript para montar as funcionalidades da Agenda via WEB.

Design da Interface

Poderíamos usar algum framework Web, por exemplo o THF – Totvs Html Framework , mas por hora vamos fazer a tela usando o arroz-com-feijão das páginas WEB com AdvPL ASP, e de quebra ver um pouco mais sobre como as coisas funcionam por dentro, e ver algumas alternativas de desenvolvimento. A interface da agenda está sendo desenhada originalmente para ter um layout muito parecido com o layout do programa original, que roda pelo SmartClient.

A ideia é que a interface HTML permita realizar todas as operações a partir da mesma tela, porém cada operação que requer atualização de dados desta tela deve submeter uma requisição via POST para o Web Server, que receberá novamente a tela inteira para executar a ação desejada.

Atualização de Páginas

Nos primórdios da Internet, navegar em qualquer Web Site na Internet construído com HTML, não tinha a possibilidade de alteração ou atualização dinâmica de conteúdo. Imagine um Web Site com um layout de menu lateral, com vários links e opções, uma área de topo ou Header com um logotipo e algumas informações sobre a parte do site que você está navegando, e uma área de conteúdo mostrando duas ou três notícias. Caso você clicasse em um link para ver a próxima página de notícias, a tela inteira é apagada e recarregada, com os mesmos menus laterais, a mesma área de topo, e uma página nova de notícias.

Isso acabava tornando pesada a navegação em alguns Web Sites. Com a possibilidade de criação de páginas de FRAMES em HTML —  acho que a partir do Internet Explorer 4 — você poderia definir um lay-out com frames — por exemplo um superior, um lateral e um central, onde a carga de uma página de notícias apenas recarregava uma página do frame, colocando as notícias desejadas.

Com as melhorias feitas no JavaScript, e sendo possível alterar dinamicamente o conteúdo de um HTML já desenhado na tela do Web Browser, vários Web Sites usavam um frame “escondido” na tela, onde através dele era feita uma requisição de uma nova página. Nesta página, que na verdade não era visível — frame escondido — era carregado um JavaScript retornado pelo Web Server, para atualizar dinamicamente o conteúdo da página sendo mostrada no frame de conteúdo — esse sim visível — sem a necessidade de recarregar (ou fazer REFRESH) da página inteira.

Algum tempo depois, foram descobertas vulnerabilidades nos navegadores Web relacionados ao uso de Frames, que poderiam mascarar um Web Site malicioso que poderia usar o JavaScript para interagir — e roubar dados e credenciais por exemplo — com frames de outros domínios, e a mecânica de atualização dos Frames tornava mais complicado desde o desenvolvimento do site, até a usabilidade do usuário — como usar o botão Voltar do Browse ou mesmo fazer um BookMark.

AJAX – Seus problemas terminaram

Mais melhorias e implementações foram feitas no JavaScript, e surgiu  o AJAX  —  Asynchronous Javascript And XML. NA verdade, este recurso é a união de duas funcionalidades: Primeira, uma função assíncrona do JavaScript foi criada para enviar ou solicitar informações para um Web Server, sem a necessidade de recarga da página atual. E, quando o Web Server retornar os dados solicitados (não necessariamente precisa ser um XML, pode ser usado texto plano, JSON, …), um JavaScript é acionado para processar os dados da requisição, que podem ser usados para atualizar o conteúdo HTML da página sendo atualmente exibida ao usuário.

Vamos pegar por exemplo o FACEBOOK — Uma vez que você  entra no seu “Feed de Notícias”, a URL permanece a mesma, e conforme você vai rolando a tela para baixo, usando a barra de rolagem do lado direito do Browse, ou mesmo o botão “Scroll” presente hoje até nos modelos mais simples de Mouse, novos posts vão sendo trazidos para a sua tela, sem a necessidade de repintar a tela inteira.

É claro que podemos usar AJAX com AdvPL ASP, inclusive vamos abordar este assunto mais para a frente, no decorrer do desenvolvimento do CRUD em AdvPL ASP, por hora estamos entrando neste assunto para fins informativos e didáticos.

Página AdvPL ASP da AGENDA

Sem mais delongas, vamos ver como está a primeira versão — ainda não funcional, mas apresentável — da interface AdvPL ASP da Agenda — futuro arquivo “agenda.aph”

<!DOCTYPE html>
<html>
<head>
<meta charset="ANSI">
<title>Agenda em AdvPL ASP</title>
<style>
.agbutton {
display: inline-block;
text-decoration : none;
width: 120px;
height: 18px;
background: rgb(240, 240, 240);
text-align: center;
color: black;
padding-top: 4px;
}
.agget { 
display: block;
width: 110px;
height: 22px; 
color: black; 
padding-top: 6px; 
text-align: right;
}
.aginput { 
width: 320px;
height: 22px; 
color: black; 
padding-top: 0px; 
padding-right: 10px; 
text-align: left;
}
</style>
</head>

function doLogoff() { 
var f = document.getElementById("F_AGENDA"); 
f.action="/logoff.apw"
f.submit();
};

<body style="font-family:Courier New;font-size: 12px;background-color:rgb(128,128,128);">

<table align="left">
<tr>

<!-- Primeira Tabela - Opções e Imagem 3x4 -->
<td align="left" valign="top">
<table>
<tr><td><a class="agbutton" href="?Op=1">Incluir</a></td></tr>
<tr><td><a class="agbutton" href="?Op=2">Alterar</a></td></tr>
<tr><td><a class="agbutton" href="?Op=3">Excluir</a></td></tr>
<tr><td style="height: 22px;">&nbsp;</td></tr>
<tr><td><a class="agbutton" href="javascript:void(0)" onclick="doLogoff()">Sair</a></td></tr>
<tr><td style="height: 22px;">&nbsp;</td></tr>
<tr><td><img style="width: 120px;height: 160px;" src="./images/Agenda_3x4.png"></td></tr>
</table>
</td>

<!-- Segunda Tabela - Mais Opções -->
<td align="left" valign="top">
<table>
<tr><td><a class="agbutton" href="?Op=4">Primeiro</a></td></tr>
<tr><td><a class="agbutton" href="?Op=5">Anterior</a></td></tr>
<tr><td><a class="agbutton" href="?Op=6">Próximo</a></td></tr>
<tr><td><a class="agbutton" href="?Op=7">Último</a></td></tr>
<tr><td><a class="agbutton" href="?Op=8">Pesquisa</a></td></tr>
<tr><td><a class="agbutton" href="?Op=9">Ordem</a></td></tr>
<tr><td><a class="agbutton" href="?Op=10">Mapa</a></td></tr>
<tr><td><a class="agbutton" href="?Op=11">G-Mail</a></td></tr>
<tr><td><a class="agbutton" href="?Op=12">Foto 3x4</a></td></tr>
</table>
</td>

<!-- Terceira Tabela - Dados do Contato -->
<td align="left" valign="top"> 
<form id="F_AGENDA" action="#" method="post">
<table style="border-spacing: 1px; background: rgb(192,192,192);">
<tr><td colspan="2" style="width: 500px; height: 22px; color: white; padding-top: 4px; background: rgb(0,0,128);">&nbsp;Ordem ...</td></tr>
<tr><td class="agget">ID</td> <td class="aginput"><input id="I_D" type="text" name="ID" disabled size="6" ></td></tr>
<tr><td class="agget">Nome</td> <td class="aginput"><input id="I_NOME" type="text" name="NOME" disabled size="50" ></td></tr>
<tr><td class="agget">Endereço</td> <td class="aginput"><input id="I_ENDER" type="text" name="ENDER" disabled size="50" ></td></tr>
<tr><td class="agget">Complemento</td><td class="aginput"><input id="I_COMPL" type="text" name="COMPL" disabled size="20" ></td></tr>
<tr><td class="agget">Bairro</td> <td class="aginput"><input id="I_BAIRRO" type="text" name="BAIRRO" disabled size="30" ></td></tr>
<tr><td class="agget">Cidade</td> <td class="aginput"><input id="I_CIDADE" type="text" name="CIDADE" disabled size="40" ></td></tr>
<tr><td class="agget">UF</td> <td class="aginput"><input id="I_UF" type="text" name="UF" disabled size="2" ></td></tr>
<tr><td class="agget">CEP</td> <td class="aginput"><input id="I_CEP" type="text" name="CEP" disabled size="9" ></td></tr>
<tr><td class="agget">Fone 1</td> <td class="aginput"><input id="I_FONE1" type="text" name="FONE1" disabled size="20" ></td></tr>
<tr><td class="agget">Fone 2</td> <td class="aginput"><input id="I_FONE2" type="text" name="FONE2" disabled size="20" ></td></tr>
<tr><td class="agget">e-Mail</td> <td class="aginput"><input id="I_EMAIL" type="text" name="EMAIL" disabled size="40" ></td></tr>
<tr>
<td class="agget">&nbsp;</td>
<td>
<a class="agbutton" id="btnConfirm" href="?Op=13">Confirmar</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a class="agbutton" id="btnVoltar" href="?Op=14">Voltar</a>
</td>
</tr>
</table>
</form>

</td>
</tr>
</table>

</body>
</html>

Por hora, o resultado da apresentação desta tela no Web Browse pode ser visto abaixo:

Web Agenda 2

Ainda não há conteúdo dinâmico, as ações dos botões ainda estão sendo escritas, e a foto mostrada na tela é o arquivo “Agenda_3x4.png”, que por hora precisa estar dentro de uma pasta chamada “images” a partir da pasta raiz de publicação WEB (No caso do meu ambiente, a pasta images deve estar dentro do Path especificado na seção HTTP).

Pontos de Atenção

  • Por se tratar de uma rotina de consulta dinâmica, não há interesse em manter ou permitir que sejam feitos “BookMarks” das operações da Agenda, mesmo a de consulta. Por isso, boa parte das operações desta rotina vão requisitar o próprio link da agenda.apw passando parâmetros usando POST, e para isso os campos INPUT do formulário. E, inclusive, existe um tipo de campo INPUT que pode ser usado dentro do código HTML, mas que não é mostrado para o usuário, permitindo submeter valores adicionais dentro da requisição POST feita pelo Web Browse, onde podemos por exemplo usar JavaScript para colocar valor nestes campos. Veremos isso em detalhes no decorrer desta sequência de posts.
  • Mesmo que o JavaScript possa nos permitir colocar algumas validações no lado do Web Browse, como não submeter uma página com campos obrigatórios faltando, ou até mesmo implementar algumas verificações de consistência, não devemos confiar cegamente que isto será respeitado. Isto é, como qualquer script retornado ao Web Browse pode e vai ser interpretado pelo mesmo, qualquer um com um pouco de maldade no coração pode abrir uma interface de desenvolvedor no Web Browse, alterar ou desligar dinamicamente uma validação da página em questão, e burlar a validação ou consistência do lado do Client. Logo, sempre critique os dados vindos do Browse, pois eles não necessariamente podem vir da forma que você está esperando. Quer um exemplo simples? Você cria uma página de busca de itens em uma loja virtual, e permite no Browse que o cliente escolha receber os dados em páginas de 10, 20 ou 50 registros, e exige que o usuário informe um termo de busca com pelo menos três letras. Estes parâmetros são enviados junto da requisição de busca, que no servidor monta uma Query no banco, e limita os resultados baseado no número de linhas por página informado. Se esta validação estiver apenas no lado do cliente, um cidadão com más intenções pode burlar esta validação, e submeter uma busca pela letra “A”, estipulando um tamanho de página de  30 mil registros. Pronto, o Web Server vai disparar uma Query contra o banco de dados que vai retornar um volume medonho de registros, e o Web Server vai sofrer pra montar uma página de retorno que pode passar de 1 MB para retornar isso ao Browse.
  • Para evitar dores de cabeça desnecessárias, independente da metodologia ou ferramental aplicado, procure conhecer e seguir o principio “Keep It Simple” — MANTENHA SIMPLES — valorização da simplicidade e descarte de toda a complexidade desnecessária.

Conclusão

Devagar a gente chega lá. Fazer uma tela de login com dois campos é moleza… agora fazer uma tela com atualização dinâmica de um cadastro de 11 campos, 14 opções e uma foto 3×4, vamos por partes.

Referências

 

Protheus e AdvPL ASP – Parte 05

Introdução

No post anterior (Protheus e AdvPL ASP – Parte 04), vimos mais dois alias virtuais — HTTPHEADIN e HTTPHEADOUT. Agora, vamos ver o alias virtual HTTPCOOKIES, que permite lidar diretamente com Cookies de seção, e depois algumas propriedades estendidas ou campos reservados de cada um dos alias virtuais do AdvPL ASP.

Quer ler sobre esse assunto desde o início?
Veja os links de referência no final do post.

O que é um Cookie ?

Bem superficialmente, um Cookie é um fragmento de dados enviado por um Web Server ao Web Browser (ou Client), que é transmitido de volta pelo Client ao Web Server em próximas requisições. Recomendo a leitura do documento “Cookies HTTP“, ele explica bem os tipos de Cookies, escopo, persistência, utilização, e inclusive como evitar vulnerabilidades.

Um Cookie também é uma tupla chave/valor, e podemos transferir um ou mais Cookies do Servidor HTTP ao Web Browser, e receber seus valores de volta. Estas informações são trafegadas em chaves específicas dentro do Header da requisição HTTP. Para tornar mais fácil a manutenção destas informações, e você não precisar buscar as informações de Cookies e fazer a interpretação delas dentro da sua aplicação, o Protheus como Web Server faz este trabalho, e permite ler as informações de Cookies recebidos, e gravar novos cookies ou redefinir os valores dos Cookies a serem enviados como retorno ao Web Browse.

Alias Virtual HTTPCOOKIES

De forma similar aos demais alias virtuais, podemos recuperar valores de Cookies previamente retornados ao Web Browse, e e criar novas chaves com novos valores a serem armazenados no Web Browser, bem como trocar valores de cookies já existentes — desde que eles tenha sido criados pela nossa aplicação, e pertençam pelo menos ao mesmo host ou domínio — veja sobre escopo de Cookies no link “Cookies HTTP“.

No exemplo de requisição HTTP mostrado no post anterior (Protheus e AdvPL ASP – Parte 04), um dos campos do Header HTTP continha a seguinte informação:

Cookie: SESSIONID=41a79408567715479073622c71988001

Este Cookie foi criado internamente pelo Web Server do Protheus, para identificar a seção atual do usuário. Por isso ele foi recebido. Na primeira requisição que este usuário fez para o Web Server, após iniciar o Web Browser, este Cookie não existia, então o Web Server Protheus sabe que trata-se de uma nova seção, e cria um identificador para aquela seção, e retorna ele no header de retorno (HTTPHEADOUT) do HTTP.

Set-cookie: SESSIONID=41a79408567715479073622c71988001

Utilizando o alias virtual HTTPCOOKIES, eu poderia ler diretamente este cookie sem precisar fazer o tratamento do valor de HTTPHEADIN->COOKIE, que poderia inclusive conter mais de um Cookie. Por exemplo, para ler o cookie SESSIONID, eu utilizaria HTTPCOOKIES->SESSIONID. O Cookie SESSIONID é de uso interno do mecanismo de controle de sessões de usuário — inclusive das variáveis de seção ( HTTPSESSION ). Logo, não devemos mexer neste valor.

Vale lembrar que a soma de chaves e valores de Cookies têm capacidade limitada de armazenamento — eu pessoalmente não usaria nada maior que 256 bytes — lembrando que a transmissão destes valores em cada requisição podem acabar interferindo no peso e desempenho das requisições.

Propriedades estendidas dos Alias Virtuais

Agora que nós já vimos todos os alias virtuais disponíveis para o AdvPL ASP, vamos ver algumas propriedades estendidas de cada um deles, que podem ser muito úteis em várias situações.

HTTPGET->AGETS

O alias virtual HTTPGET possui um campo reservado, chamado “AGETS”. Trata-se de um array de strings, contendo em cada elemento o nome de um identificador de parâmetro informado na URL da requisição. Este campo montado dinamicamente pelo servidor Protheus antes de alocar um processo (Working Thread) para processar uma requisição de link .apw

Por exemplo, vejamos a URL abaixo:

http://localhost/info.apw?a=1&b=2&c

Ao processar esta requisição em AdvPL ASP, podemos recuperar o valor de cada um dos parâmetros informados no AdvPL, usando o alias virtual HTTPGET, desde que saibamos os nomes dos parâmetros, usando por exemplo:

cValorA := HTTPGET->A
cValorB := HTTPGET->B

Caso eu queira saber quais foram os identificadores usados na URL, eu posso consultar o campo HTTPGET->AGETS, por exemplo:

For nI := 1 to len(HTTPGET->AGETS)
  conout("GET "+cValToChar(nI)+" Identificador "+HTTPGET->AGETS[nI]
Next
// o resultado esperado no log de console do 
// servidor de aplicação é:
// GET 1 Identificador a
// GET 2 Identificador b
// GET 3 Identificador c

Reparem que “c” foi um identificador passado na URL, porém ele não teve valor atribuído. Agora, existe uma forma de recuperarmos os conteúdos destes identificadores, a partir dos nomes, usando macro-execução:

For nI := 1 to len(HTTPGET->AGETS)
  cIdent := HTTPGET->AGETS[nI]
  conout("GET "+cValToChar(nI)+" Id "+ cIdent + ;
  " Valor "+cValToChar( &("HTTPGET->"+cIdent) ) )
Next
// o resultado esperado no log de console do 
// servidor de aplicação é:
// GET 1 Id a Valor 1
// GET 2 Id b Valor 2
// GET 3 Id c Valor

HTTPPOST->APOST

Para o alias virtual HTTPPOST, existe o campo reservado “APOST“, com o mesmo propósito e funcionamento do AGETS. A diferença é que somente são informados valores nestes campos caso a requisição HTTP seja enviado o comando POST, o tipo de conteúdo da requisição seja “application/x-www-form-urlencoded“, e o corpo da requisição contenha uma ou mais tuplas chave/valor codificadas.

A mesma técnica utilizada para recuperar os valores associados aos identificadores recebidos em AGETS pelo alias virtual HTTPGET pode ser utilizada para recuperar os valores recebidos em APOST pelo alias virtual HTTPPOST.

HTTPHEADIN->AHEADERS

Para o alias virtual HTTPHEADIN, existe o campo reservado AHEADERS, criado com o propósito de tornar possível receber todas as linhas do cabeçalho da requisição HTTP vindas do Web Browse ou do Web Client que fez a solicitação. No caso, cada linha do cabeçalho HTTP, formado por uma tupla chave/valor, é retornada como um elemento deste array — e não apenas o nome do identificador, como é retornado por AGETS (do Alias HTTPGET) ou APOST (do alias HTTPPOST). Por exemplo:

<pre>
<% For nI := 1 to len(HTTPHEADIN->AHEADERS) %>
<%= HTTPHEADIN->AHEADERS[nI] %>
<% Next %>
</pre>

O trecho acima, dentro de um fonte APH, retornaria ao Browse em texto pré-formatado (ou fonte de tamanho fixa) todas as linhas do Header HTTP recebido pelo Web Server do Protheus para a requisição atual.

HTTPHEADIN->COMMAND

A primeira linha do HTTP Header enviada por um Web Browser ou Web Client do protocolo HTTP contém um comando — normalmente GET ou POST.  Para saber qual foi o comando recebido, podemos usar o campo reservado “COMMAND”.

HTTPHEADIN->MAIN

Ao receber uma requisição de link .apw, para processamento de AdvPL ASP, usamos o campo reservado “main”, do alias virtual HTTPHEADIN, alimentado apenas com o nome da página que foi requisitada, desconsiderando URL, domínio, path e parâmetros. Por exemplo, ao solicitar http://seuhostouip/suapasta/meulink.apw?a=1&b=2, o resultado de HTTPHEADIN->MAIN será apenas “meulink

HTTPHEADIN->CMDPARMS

De forma similar ao campo reservado MAIN, o campo CMDPARMS recebe o nome do link .apw chamado, e os eventuais parâmetros recebidos na URL, no formato de uma única string, onde apenas é removida a extensão “.apw” da requisição. Por exemplo, ao solicitar http://seuhostouip/suapasta/meulink.apw?a=1&b=2, o resultado de HTTPHEADIN->CMDPARMS será apenas “meulink?a=1&b=2

HTTPCOOKIES->ACOOKIES

Este campo reservado do alias virtual HTTPCOOKIES funciona exatamente igual ao AGETS e APOST. Ele traz apenas os nomes dos Cookies enviado pelo Cliente Web ao Web Server Protheus em uma requisição de AdvPL ASP.

<pre>
<% For nI := 1 to len(HTTPCOOKIES->ACOOKIES) %>
<%= HTTPCOOKIES->ACOOKIES[nI] %>
<% Next %>
</pre>

O trecho acima, dentro de um fonte APH, mostrará todos os nomes dos identificadores de Cookies recebidos pela requisição atual.

HTTPSESSION->SESSIONID

Funciona de forma similar ao HTTPCOOKIES->SESSIONID, porém com uma diferença: Na primeira requisição recebida pelo AdvPL ASP de uma nova seção do Web Browser, o identificador do SESSIONID vêm em branco, pois o Cookie identificador de seção ainda não foi retornado ao Browse. Já o HTTPSESSION->SESSIONID já vem preenchido com o identificador criado para esta seção antes mesmo dele ser retornado ao browse.

Exemplo COMPLETO

Vamos ver o que a podemos recuperar de uma requisição HTTP vinda de um Web Browse em AdvPL ASP, agora em um único exemplo? Primeiro, criamos um desvio na função U_ASPConn(), para executar a função H_ASPINFO() caso seja informado no browse a chamada para o link “aspinfo.apw“.

case cAspPage == 'aspinfo'
  cReturn := H_ASPINFO()

Agora, criamos o arquivo “aspinfo.aph“, com o seguinte conteúdo:

<html><body>
<pre>
HTTP HEADER INFO
<hr>
<% For nI := 1 to len(HTTPHEADIN->AHEADERS) %>
<%= HTTPHEADIN->AHEADERS[nI] %>
<% Next %>

Command .....: <%=HTTPHEADIN->COMMAND%>
Main ........: <%=HTTPHEADIN->MAIN%>
CmdParms ....: <%=HTTPHEADIN->CMDPARMS%>
Remote Addr .: <%=HTTPHEADIN->REMOTE_ADDR%>
Remote Port .: <%=HTTPHEADIN->REMOTE_PORT%>

<% If len(HTTPCOOKIES->ACOOKIES) > 0 %>
COOKIES
<hr>
<% For nI := 1 to len(HTTPCOOKIES->ACOOKIES) %>
<% cVar := HTTPCOOKIES->ACOOKIES[nI] %>
<%= cVar + " = [" + &("HTTPCOOKIES->"+cVar) + "]" %>
<% Next %>
<% Endif %>

<% If len(HTTPGET->AGETS) > 0 %>
GET PARAMS
<hr>
<% For nI := 1 to len(HTTPGET->AGETS) %>
<% cVar := HTTPGET->AGETS[nI] %>
<%= cVar + " = [" + &("HTTPGET->"+cVar) + "]" %>
<% Next %>
<% EndIf %>

<% If len(HTTPPOST->APOST) > 0 %> 
POST PARAMS
<hr>
<% For nI := 1 to len(HTTPPOST->APOST) %>
<% cVar := HTTPPOST->APOST[nI] %>
<%= cVar + " = [" + &("HTTPPOST->"+cVar) + "]" %>
<% Next %>
<% EndIf %>

REQUEST DETAILS
<hr>
HttpOtherContent() ......: <%=HttpOtherContent()%>
HttpRCTType() ...........: <%=HttpRCTType()%>
HttpRCTDisp() ...........: <%=HttpRCTDisp()%>
HttpRCTLen() ............: <%=HttpRCTLen()%>
</pre>
</body></html>

Agora vamos ver a mágica funcionando. Com tudo compilado e o Protheus no ar, entramos com a seguinte URL: http://localhost/aspinfo.apw?a=1Veja abaixo o resultado esperado no Web Browse:

HTTP HEADER INFO

GET /aspinfo.apw?a=1 HTTP/1.1
Host: localhost
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
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
Cookie: SESSIONID=72b298eea3eed9544ad95459e833e7c2

Command .....: GET
Main ........: aspinfo
CmdParms ....: aspinfo?a=1
Remote Addr .: 127.0.0.1
Remote Port .: 52764

COOKIES

SESSIONID = [f62f4ac8afc9f1b3456faafc93551bc5]

GET PARAMS

A = [1]

REQUEST DETAILS

HttpOtherContent() ......: 
HttpRCTType() ...........: 
HttpRCTDisp() ...........: 
HttpRCTLen() ............: -1

E se fosse um POST ?

Claro, vamos ver o que eu receberia em um post. Aproveitando o exemplo de um post anterior, vamos editar o arquivo formpost.aph, e trocar o ACTION do formulário de “/postinfo.apw” para “/aspinfo,apw”, e ver  o que acontece.

<html><body>
<p>Formulário de POST</p>
<form action="/aspinfo.apw" method="post">
First name:<br>
<input type="text" name="firstname"><br>
Last name:<br>
<input type="text" name="lastname">
<hr>
<input type="submit" value="Enviar">
</form>
</body></html>

Agora, vamos abrir a URL http://localhost/formpost.apw, e no caso, eu vou digitar meu nome e sobrenome nos campos:

Web formpost

Após clicar no botão ENVIAR, o retorno esperado no Browse deve ser algo assim:

HTTP HEADER INFO

POST /aspinfo.apw HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 34
Cache-Control: max-age=0
Origin: http://localhost
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://localhost/formpost.apw
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
Cookie: SESSIONID=fe5ab7f15a1b9788fd945bf14581af82

Command .....: POST
Main ........: ASPINFO
CmdParms ....: ASPINFO
Remote Addr .: 127.0.0.1
Remote Port .: 53500

COOKIES

SESSIONID = [f9d8c4a25dc307251bf05300f59bf3e4]

POST PARAMS

FIRSTNAME = [Júlio]
LASTNAME = [Wittwer]

REQUEST DETAILS

HttpOtherContent() ......: 
HttpRCTType() ...........: application/x-www-form-urlencoded
HttpRCTDisp() ...........: 
HttpRCTLen() ............: 34

Conclusão

Com este post, cobrimos o básico do AdvPL ASP. Nos próximos posts deste assunto, vamos abordar algumas capacidades de retorno diferenciado da interface HTTP, e como utilizá-las quando necessário. Afinal, podemos retornar ao Web Browser ou ao Client HTTP utilizado muito mais do que apenas HTML 😀

Agradeço novamente a audiência, e desejo a todos TERABYTES DE SUCESSO !!!! 

Referências

 

 

Protheus e AdvPL ASP – Parte 04

Introdução

Nos posts anteriores sobre AdvPL ASP, vimos até o momento três alias virtuais usados para receber parâmetros ( HTTPGET / HTTPPOST ) e controlar variáveis de seção de usuário (HTTPSESSION). Agora, vamos ver mais dois alias virtuais: HTTPHEADIN e HTTPHEADOUT.

Alias Virtual HTTPHEADIN

Cada requisição HTTP possui um formato interno, separada em cabeçalho (ou Header) e corpo (ou Body). Atenção, estamos falando do protocolo HTTP, e não do header e body de um formulário HTML.

Quando um Web Browse ou um cliente de aplicação WEB faz uma requisição via HTTP, esta requisição é acompanhada de algumas tuplas chave/valor, colocadas no cabeçalho da requisição, que indicam por exemplo o tipo da requisição (GET, POST, …), o tipo do conteúdo enviado na requisição (texto, html, imagem, …), a codificação do conteúdo (ANSI, UTF-8 ou outro CodePage), algumas informações sobre o Web Browse ou da aplicação Cliente que está fazendo a solicitação, entre outros identificadores de uso específico.

Usando o Google Chrome, por exemplo, temos uma janela especial chamada DevTools, onde temos ferramentas que permitem investigar por dentro uma requisição feita pelo Web Browse a um determinado servidor, os parâmetros da requisição e seu retorno. Após abrir a janela do DevTools e digitar a URL http://localhost/ , a requisição enviada ao Web Server foi:

GET / HTTP/1.1 
Host: localhost 
Connection: keep-alive 
Cache-Control: max-age=0 
Upgrade-Insecure-Requests: 1 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36 
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 
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 
Cookie: SESSIONID=41a79408567715479073622c71988001 
If-Modified-Since: Fri, 30 Nov 2018 00:30:20 GMT

Só nesta requisição existe um universo de informações. Não vou entrar nos méritos internos de cada uma, mas apenas nas que realmente importam para o momento, e na forma de recuperá-las.

Da mesma forma que os demais alias virtuais, o alias virtual HTTPHEADIN permite fazer a leitura de um determinado valor a partir do seu nome. Por exemplo:

HTTPHEADIN->CONNECTION deve retornar a string “keep-alive”
HTTPHEADIN->HOST deve retornar a string “localhost”

Agora, o que acontece se eu tentar recuperar o valor de “User-agent” ? HTTPHEADIN->USER-AGENT não é um formato válido para o AdvPL, o compilador vai entender que você quer pegar o valor HTTPHEADIN->USER e fazer uma subtração operação aritmética de subtração da variável “AGENT” , que não existe.

Pensando nesta possibilidade, qualquer caractere que faz parte do nome de uma chave no alias virtual HTTPHEADIN, que não seja válido como nome de variável ou de identificador, será trocado pelo caractere “_” (underline). Desse modo, para recuperar o valor de User-Agent, você deve usar HTTPHEADIN->USER_AGENT

HTTPHEADIN – Valores adicionais

Quando o Web Browse ou a aplicação cliente fez uma requisição HTTP ao Web Server, esta conexão foi aberta sobre o protocolo TCP/IP. Logo, mesmo que a requisição HTTP possa ser minimalista e não ter muitos detalhes, é possível obter dentro do prococolo TCP/IP qual é o IP de Origem da conexão, e qual é a porta TCP usada na conexão aberta pelo Cliente. O IP da Conexão, por exemplo, pode ser recuperado e gravado em um LOG de requisições ou de atividade do Web Site, para uso posterior em estatísticas ou mesmo auditoria de operações. Para obter estes valores, usamos respectivamente:

HTTPEADIN->REMOTE_ADDR
HTTPEADIN->REMOTE_PORT

Estes campos retornam respectivamente um valor “C” caractere, contendo o IP Client da Conexão, e um valor “N” numérico contendo a porta dinâmica TCP da máquina Client que fez a requisição.

Seguindo a mesma linha dos posts anteriores, vamos criar um arquivo chamado headinfo.aph, e dentro dele colocar o seguinte conteúdo:

<html><body>
<pre>
Host ........: <%=HTTPHEADIN->HOST%>
Connection...: <%=HTTPHEADIN->CONNECTION%>
User-Agent ..: <%=HTTPHEADIN->User_Agent%>
Remote Addr..: <%=HTTPHEADIN->REMOTE_ADDR%>
Remote Port..: <%=HTTPHEADIN->REMOTE_PORT%>
</pre>
</body></html>

E, dentro do fonte ASPThreads.prw, inserir mais uma entrada na lista de páginas, para ao receber o link “headinfo.apw”, processar e retornar a função H_HeadInfo() — que corresponde ao arquivo headinfo.aph compilado.

case cAspPage == 'headinfo'
  cReturn := H_HEADINFO()

E, ao testar esta chamada no meu ambiente, usando http://localhost/headinfo.apw, eu obtive o seguinte resultado na tela do Web Browse:

WEB HeadInfo

Agora, vamos ver o que acontece se eu acessar esta página usando um iPhone? Vejamos, primeiro eu preciso achar o IP da minha máquina na rede de casa. Como estou usando Wi-fi, basta eu usar o prompt de comando do Windows (cmd) e usar o comando IPCONFIG, e procurar o endereço IPV4 da interface de rede Wi-fi. Como meu sistema operacional está em Inglês, eu consigo usar alguns comandos adicionais para filtrar os resultados:

ipconfig | findstr /i "Address Adapter"

ipconfig 1

A interface e o valor que eu quero estão destacados em vermelho. Agora, acessando o Chrome do meu telefone, eu digito a URL

http://192.168.0.4/headinfo.apw

E com ela eu recupero as seguintes informações:

IMG_3661

Alias Virtual HTTPHEADOUT

Bem, da mesma forma que uma requisição HTTP é dividida em cabeçalho e corpo, uma resposta de uma requisição também têm esta mesma divisão. Quando você solicita através de um Web Browse ou Cliente HTTP um link de uma página em AdvPL ASP, o próprio Web Server do Protheus monta um cabeçalho de retorno da requisição, com alguns valores pré-definidos. Vou usar a mesma requisição do ultimo teste, e rodar ela com a janela do DevTools aberta. As informações que eu quero estão em uma aba chamada “Response Headers”, que devem mostrar algo assim:

HTTP/1.1 200 OK
Date: Fri, 30 Nov 2018 01:34:01 GMT
Server: Application Web Server
MIME-version: 1.0
Content-type: text/html
Last-modified: Fri, 30 Nov 2018 01:34:01 GMT
Set-cookie: SESSIONID=68c0be02ceaec73ddfcf14281c4f1909 
X-Frame-Options: SAMEORIGIN
Content-Length: 284

Este cabeçalho de retorno foi montado pelo Web Server do Protheus, logo que a página AdvPL ASP foi procecssada, e a função ASPConn() retornou a string cHTML. A requisição foi processada com sucesso (código de retorno HTTP 200), a data de processamento, o retorno deve ser tratado pelo Browser como um texto html (definido pelo Content-type), e o tamanho do HTML retornado foi de 284 bytes.

Lembram-se no post anterior, quando eu falei de SESSIONS de usuário? O Web Server do Protheus retorna um Cookie de memória chamado SESSIONID, gerado para esta instância de Browse, para saber nas próximas requisições que se trata da mesma instância e/ou usuário. Pode comparar este valor com o valor recebido demonstrado no exemplo dos Headers da requisição HTTP — os valores estão diferentes, pois entre um teste e outro, eu fechei o Browser e abri novamente. Mais para frente vamos ver um alias virtual que permite lidar com Cookies de memória, então vamos entrar neste assunto novamente e com maior profundidade.

Voltando ao alias virtual HTTPHEADOUT, ele foi criado para que você possa, durante o processamento de uma requisição de AdvPL ASP, criar um novo valor de retorno, para ser acrescentado no Header de retorno da requisição HTTP para o Web Browser ou Cliente que fez a solicitação. PAra ver a lista de campos mais usadas, veja os links de referência no final do post.

Normalmente você não precisa utilizar diretamente o alias HTTPHEADOUT, inclusive por que ele têm a restrição de não permitir a criação de campos de retorno usando por exemplo o símbolo “-“, existem outras funções feitas especificamente para mexer em valores default. O uso projetado para este alias virtual é a possibilidade de criar um header com um nome de identificador exclusivo, para uma integração entre sistemas por exemplo.

Conclusão

Falta pouco para cobrir o básico do AdvPL ASP. Se você começou a ler sobre este assunto agora, pegue os posts desde o primeiro da sequência de AdvPL ASP, o entendimento de boa parte do conteúdo dos posts daqui para a frente requer esta leitura.

Agradeço novamente a audiência, e desejo a todos TERABYTES DE SUCECSSO !!! 

Referências

CRUD em AdvPL ASP – Parte 01

Introdução

Nos posts anteriores sobre o CRUD em AdvPL, o programa de exemplo partiu de uma agenda de contatos, escrita originalmente para ser executado via SmartClient. Agora, vamos aproveitar algumas partes do “núcleo” do programa agenda.prw, e criar uma interface de manutenção para WEB, usando AdvPL ASP. Eu recomendo fortemente que você, caso ainda não tenha lido, leia os posts sobre AdvPL ASP e CRUD, eles estão acessíveis através das respectivas categorias no menu inicial superior do BLOG, e também nas referências no final deste post.

Agenda em AdvPL ASP

Inicialmente, vamos aproveitar o programa aspthreads.prw, que serve de casca para execução das nossas aplicações em AdvPL ASP, para desviar a execução do código para um novo fonte, chamado wagenda.prw, quando a URL informada for http://localhost/agenda.apw

Para isso, de posse do fonte aspthreads.prw, dentro da função U_ASPConn(), criamos uma nova entrada no DO CASE para a agenda, acrescentando as linhas abaixo:

  case cAspPage == 'agenda'
    // Executa a agenda em AdvPL ASP 
    // Os controles e páginas estão encapsulados pela função U_WAgenda()
    cReturn := U_WAGENDA()

Agora, vamos criar o fonte wagenda.prw, por partes, para ele funcionar como uma máquina de estado de tela única, de forma similar ao que fizemos com o programa no SmartClient. Algumas funções do fonte agenda.prw deverão ser alteradas, para serem visíveis para este novo fonte. Inicialmente, precisamos de uma tela APH para desenhar a interface da agenda. Vamos começar o fonte wagenda.prw de forma simples, e ir incrementando ele aos poucos.

#include "protheus.ch"

User Function WAgenda()
Local cHtml := ''
If empty(HTTPSESSION->LOGIN)
  // Usuário ainda não está logado. 
  // Retorna para ele a tela de login
  cHtml := H_WLogin()
Else
  cHtml := H_WAgenda()
Endif
Return cHtml

Aqui fazemos uso de uma variável de SESSION que o programa mesmo vai criar, para exigir que apenas um usuário autenticado — que passou primeiro pela página de LOGIN — tenha acesso à agenda. Para maiores detalhes sobre o funcionamento das SESSIONS no AdvPL ASP, consulte o post Protheus e AdvPL ASP – Parte 03.

Caso o usuário abra diretamente a página da agenda (http://localhost/agenda,apw), na verdade ele var receber uma tela HTML com um formulário de Login. Vamos ver como esta tela seria — arquivo wLogin.aph

*** OBSERVAÇÃO : Por hora os arquivos APH dos exemplos abaixo estão todos usando o CODEPAGE ANSI (ou CP1252) , e foram criadas manualmente usando o IDE do Protheus. Estas páginas também utilizam o padrão HTML5. PAra pbter mais detalhes sobre como o Web Browse trata as versões de HTML e JavaScript, uma excelente fonte de informações é o site W3SCHOOLS

<% 
/* ----------------------------------------------------------------
Login da Agenda
---------------------------------------------------------------- */ 
%> 
<!DOCTYPE html>
<html>
<head>
<meta charset="ANSI">
<title>LOGIN</title>
<style>
html, body { height: 100%; } 
html { display: table; margin: auto; }
body { display: table-cell; vertical-align: middle; }
.agbutton {
display: inline-block;
text-decoration : none;
width: 120px;
height: 18px;
background: rgb(240, 240, 240);
text-align: center;
color: black;
padding-top: 4px;
}
.agget { 
display: block;
width: 110px;
height: 22px; 
color: black; 
padding-top: 6px; 
text-align: right;
}
.aginput { 
width: 320px;
height: 22px; 
color: black; 
padding-top: 0px; 
padding-right: 10px; 
text-align: left;
}
</style>

function doLogin() { document.getElementById("F_LOGIN").submit(); };

</head>
<body style="font-family:Courier New;font-size: 12px;background-color:rgb(128,128,128);">

<form id="F_LOGIN" action="/login.apw" method="post">
<table style="border-spacing: 1px; background: rgb(192,192,192);">
<tr><td colspan="2" style="width: 500px; height: 22px; color: white; padding-top: 4px; background: rgb(128,0,0);">
<center>LOGIN</center></td></tr>
<tr><td class="agget">Usuário</td> <td class="aginput"><input id="I_USER" type="text" name="USER" size="50" ></td></tr>
<tr><td class="agget">Senha</td> <td class="aginput"><input id="I_PASS" type="password" name="PASS" size="32" ></td></tr>
<% If !empty(HTTPPOST->_ERRORMSG) %>
<tr><td colspan="2" style="width: 500px; height: 22px; color: white; padding-top: 4px; background: rgb(128,0,0);">
<center><%=HTTPPOST->_ERRORMSG%></center>
</td></tr>
<% Endif %>
<tr>
<td class="agget">&nbsp;</td>
<td>
<a class="agbutton" id="btnConfirm" href="javascript:void(0)" 
  onclick="doLogin()">Confirmar</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a class="agbutton" id="btnVoltar" href="/">Voltar</a>
</td>
</tr>
</table>
</form>
</body>
</html>

Somente nesta tela nós temos a utilização de vários recursos do Web Browser, inclusive JavaScript, e recursos do AdvPL ASP. Vamos ver um por um, e por quê foi optado por ser feito desta forma.

Formulário HTML

Inicialmente, para ser possível o Web Browse realizar um POST informando campos e valores informados pelo usuário para o Web Server, precisamos criar um formulário HTML usando a tag form, e dentro do corpo do formulário colocar tags de input para permitir o usuário informar valores para os campos determinados.

<form id="F_LOGIN" action="/login.apw" method="post">

Damos a este formulário um identificador (id), para poder nomear este formulário para ele ser tornar-se acessível de forma nomeada para um script em JavaScript, que vai ser executado dentro da página. Você escolhe o nome do formulário, eu resolvi usar um prefixo “F_” para saber dentro dos meus fontes que este componente é um form.

Outra escolha importante foi o método do formulário. Eu poderia usar um formulário do tipo GET, mas isto não seria nada elegante para uma tela de login, por várias razões. Primeira, os formulários com método GET colocariam os nomes e valores dos campos input na URL. Isso expõe a senha do usuário na URL. E, os Web Browses costumam fazer cache e permitir Bookmark de URL passando parâmetros via GET. Nada bom para uma operação de autenticação.

Quando usamos POST, o Browse não faz cache dos dados submetidos, e os dados são enviados internamente do Web Browse ao Web Server, dentro do corpo da requisição HTTP.

Links e JavaScript

Existem componentes no HTML, como o button, que permitem a criação de botões em um Web Site. Os botões normalmente disparam ações em JavaScript, e podem ter seu layout alterado usando CSS (ou folhas de estilo). Eu poderia usar botões, porém opter por usar o componente âncora (a) do HTML, usando CSS para dar a aparência de um botão, e internamente usar o evento de disparo (onclick) do componente para chamar uma função JavaScript declarada dentro da minha página, vide exemplo do botão de Login.

<a class="agbutton" id="btnConfirm" href="javascript:void(0)" onclick="doLogin()"

Já o botão para voltar à página principal do site, usa o componente âncora (a) apontando diretamente para a URL raiz do site “/”, sem usar javascript.

<a class="agbutton" id="btnVoltar" href="/">Voltar</a>

Outro detalhe interessante é o uso de CSS. Para você que já ouviu falar nisso, basicamente eu posso criar uma classe de layout, ou estender classes já existentes dos componentes padrão, para mudar a forma de apresentação destes componentes. As definições de estilo são feitas dentro da tag style, onde a definição pode estar dentro do póprio HTML, ente as tuplas <style> e </style>, ou mesmo em um arquivo separado, onde usamos um parâmetro da tag style para indicar onde está o arquivo. No momento, o estilo está dentro da página atual. Assim que acrescentarmos mais páginas ao projeto, colocamos as definições de estilo comuns em um arquivo de estilo separado, e fazemos referência a este arquivo nas páginas da aplicação.

Validação do Login

A ação do formulário, isto é, a URL para a qual a requisição de POST gerada no momento que o formulário for submetido também é importante. No caso, vamos chamar a aplicação login,apw, responsável por receber os campos do POST (Usuário e Senha)  deste formulário, para verificar se o usuário deve ter acesso ou não para a página da agenda. Para isso, acrescentamos mais uma entrada no programa ASPThreads.prw, vide abaixo:

case cAspPage == 'login'
  cReturn := U_WLOGIN()

E, para realizar a tarefa de validação do login, vamos criar o arquivo wlogin.prw, que vai conter a função U_WLOGIN() — e inclusive uma de Logoff.

#include "protheus.ch"

User Function WLogin()
Local cHtml := ''
Local cUser := ''
Local cPass := ''

If HTTPPOST->USER != NIL .and. HTTPPOST->PASS != NIL
  // Houve um POST , informando usuario e senha 
  // Valida usuario e senha informados 
  cUser := Upper(alltrim(HTTPPOST->USER))
  cPass := HTTPPOST->PASS
  If cUser == "ADMIN" .AND. cPass == ""
    // Usuário logado com sucesso 
    // Alimenta a session LOGIN 
    HTTPSESSION->LOGIN := cUser
  Else
    // Informa mensagem de erro usando o alias virtual HTTPPOST
    HTTPPOST->_ERRORMSG := "Usuário ou senha inválidos."
  Endif
Endif

If empty(HTTPSESSION->LOGIN)
  // Usuário ainda não está logado, retorna a tela de login
  cHtml := H_WLogin()
Else
  // Usuário atual está logado. Redireciona ele para a agenda
  HTTPSRCODE(307,"Login Redirection")
  HTTPHEADOUT->Location := "/agenda.apw"
  Return ""
Endif
Return cHtml

/* -------------------------------------------------------
Logoff de Usuario 
Retorna Limpa as variaveis de session do usuario 
e retorna a página de indice do site 
------------------------------------------------------- */
USER Function WLogOff()
HTTPFREESESSION()
return H_INDEX()

Olhando ao mesmo tempo a página wlogin.aph, e a função u_wlogin(), reparem que ambas usaram um campo do alias virtual HTTPPOST, que não estava em nenhum formilário — HTTPPOST->_ERRORMSG — onde a criação deste campo é feita diretamente no alias virtual HTTPPOST, dentro da aplicação, e a verificação da existência e do conteúdo deste campo é feita dentro do arquivo windex.aph.

Sim, eu posso criar em tempo de programação um identificador e um conteúdo, nos alias curtuais HTTPGET e HTTPPOST, e depois veremos outros casos onde isto também é possível. Desta forma, ao invés de precisarmos declarar variáveis PRIVATE dentro de um fonte prw para enviar dados de programa para um aph, criamos um ou mais campos no alias virtual HTTPPOST por exemplo, tomando o cuidado destes campos não conflitarem com nomes de campos vindos do Web Browse ou de formulários.

Como isso vai funcionar — passo a passo

O usuário abre o Web Browse e entra com a URL http://localhost/agenda.apw . A função U_ASPConn() do fonte ASPThreads.prw será chamada, e vai executar a função U_WAgenda()

case cAspPage == 'agenda'
  cReturn := U_WAGENDA()

A função U_WAgenda() vai consultar a HTTPSESSION->LOGIN, que não existe ainda, indicando que o usuário ainda não foi autenticado, retornando para ele o conteúdo do arquivo wlogin.aph . Este arquivo retorna uma página de login centralizada no Web Browse, solicitando informar usuário e senha.

Web Login

Após informar o usuário ADMIN e a senha em branco, e clicar no botão Confirmar, a ação do formulário será submeter os campos digitados em uma requisição POST, para a URL http://localhost/login.apw, que por sua vez vai chamar a função U_WLOGIN()

case cAspPage == 'login'
  cReturn := U_WLOGIN()

Dentro da função U_WLOGIN(), uma vez verificado que houve o envio de parâmetros de POST na requisição, verificado pelo recebimento dos campos USER e PASS, declarados no formulário HTML, caso o usuário e senha informados sejam aptos de realizar a operação, a HTTPSESSION->LOGIN será criada e alimentada com o ID do usuário. Caso contrário, a variável de POST _ERRORMSG será criada com uma mensagem de erro de Login. Uma vez que o usuário esteja logado com sucesso, retornamos para o Browse mediante instruções especiais — que ainda não foram vistas por aqui — para redirecionar o Browse a abrir a página da agenda no link “/agenda.apw”, que por sua vez retornará a página ainda em construção wAgenda.aph.

Web Agenda.png

Revisão de Conceitos

  1. Qualquer página com link .apw  será processada pela função U_ASPCONN(), que recebe o nome da página no alias virtual HTTPHEADIN->MAIN. No nosso caso, existe um “DO CASE” com uma lista de páginas e as suas respectivas funções para chamada.
  2. Qualquer requisição feita do Browse via URL é do tipo GET, e pode passar parâmetros via URL.
  3. Um formulário HTML, ao ser submetido, pode fazer um GET ou um POST, dependendo do método configurado no form.
  4. Um formulário do tipo POST também pode passar parâmetros via URL, colocados no action do formulário.
  5. O Web Browse apenas estabelece uma conexão HTTP com o Application Server para fazer uma requisição, encerrando a conexão automaticamente após o retorno — ou antes do retorno em caso de time-out ou cancelamento da requisição pelo usuário.
  6. Mesmo que você use alguma validação no Client — por exemplo funções JavaScript — para evitar que dados inconsistentes sejam enviados ao Servidor, não deixe de fazer as consistências da recepção dos dados no Advpl ASP. Pessoas mal intencionadas podem tentar submeter conteúdos inválidos para tentar burlar comportamentos, causar danos ou mesmo indisponibilidade de serviço.
  7. Um processamento AdvPL ASP dentro de um arquivo APH deve ser usado para montar conteúdo dinâmico para ser apresentado e/ou processado no Web Browse. Tudo o que estiver entre as tags <% , <%= e %> será processado somente no momento em que o servidor receber a requisição, cujo retorno será enviado ao Web Browse como uma página HTML.

Conclusão

Os posts anteriores sobre AdvPL ASP são a base para esta nova etapa do Crud, recomendo a leitura deles para uma melhor compreensão dos conceitos aqui apresentados, bem como uma leitura extra sobre o protocolo HTTP. A página da agenda ainda não foi publicada, pois está em construção, aguardem que eu ainda não coloquei todos os ingredientes na cumbuca … risos …

Agradeço a todos os comentários, compartilhamentos e likes, e desejo a todos(as) TERABYTES DE SUCESSO !!! 

Referências